From 6d30e053c598417b949baed4175c0c43fbbe0eeb Mon Sep 17 00:00:00 2001 From: Abdhesh Nayak Date: Mon, 5 Feb 2024 17:34:04 +0530 Subject: [PATCH 01/10] :sparkles: Added podname in logs info --- apps/websocket-server/internal/domain/logs.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/websocket-server/internal/domain/logs.go b/apps/websocket-server/internal/domain/logs.go index 13e49a10c..de8887831 100644 --- a/apps/websocket-server/internal/domain/logs.go +++ b/apps/websocket-server/internal/domain/logs.go @@ -155,11 +155,15 @@ func (d *domain) HandleWebSocketForLogs(ctx context.Context, c *websocket.Conn) MessageTypeInfo MessageType = "info" MessageTypeLog MessageType = "log" ) + type MsgSpec struct { + PodName string `json:"podName"` + ContainerName string `json:"containerName"` + } type MessageResponse struct { Timestamp time.Time `json:"timestamp"` Message string `json:"message"` - Container *string `json:"container,omitempty"` + Spec *MsgSpec `json:"spec,omitempty"` Type MessageType `json:"type"` } @@ -249,7 +253,10 @@ func (d *domain) HandleWebSocketForLogs(ctx context.Context, c *websocket.Conn) } resp.Type = MessageTypeLog sp := strings.Split(msg.Subject, ".") - resp.Container = &sp[len(sp)-1] + resp.Spec = &MsgSpec{ + PodName: sp[len(sp)-1], + ContainerName: sp[len(sp)-2], + } if err := c.WriteJSON(resp); err != nil { log.Warnf("websocket write: %w", err) } From f1f32c71bd833ef786f8d6528697f9721040c897 Mon Sep 17 00:00:00 2001 From: Abdhesh Nayak Date: Mon, 5 Feb 2024 17:38:52 +0530 Subject: [PATCH 02/10] :bug: Fixed issue with pod and container name parsing --- apps/websocket-server/internal/domain/logs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/websocket-server/internal/domain/logs.go b/apps/websocket-server/internal/domain/logs.go index de8887831..232a0dd69 100644 --- a/apps/websocket-server/internal/domain/logs.go +++ b/apps/websocket-server/internal/domain/logs.go @@ -254,8 +254,8 @@ func (d *domain) HandleWebSocketForLogs(ctx context.Context, c *websocket.Conn) resp.Type = MessageTypeLog sp := strings.Split(msg.Subject, ".") resp.Spec = &MsgSpec{ - PodName: sp[len(sp)-1], - ContainerName: sp[len(sp)-2], + PodName: sp[len(sp)-2], + ContainerName: sp[len(sp)-1], } if err := c.WriteJSON(resp); err != nil { log.Warnf("websocket write: %w", err) From 86474b3fe66ac8c5366c13c2b9f8e302714db003 Mon Sep 17 00:00:00 2001 From: nxtcoder17 Date: Tue, 6 Feb 2024 16:07:07 +0530 Subject: [PATCH 03/10] fix(apps/iam): now, IAM `can` accounts for an account member separately - There are no RoleBindings with `account-member` role, so, in case of resources eligible for users with `account-member` role, it used to end up in UnAuthorized. This commit fixes it. --- apps/iam/internal/app/grpc-server.go | 51 +++++++++++++++++++--- apps/iam/internal/entities/role-binding.go | 3 ++ apps/iam/types/types.go | 14 +++++- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/apps/iam/internal/app/grpc-server.go b/apps/iam/internal/app/grpc-server.go index 94173dd2c..b6b9f46a9 100644 --- a/apps/iam/internal/app/grpc-server.go +++ b/apps/iam/internal/app/grpc-server.go @@ -10,6 +10,7 @@ import ( t "github.com/kloudlite/api/apps/iam/types" "github.com/kloudlite/api/grpc-interfaces/kloudlite.io/rpc/iam" "github.com/kloudlite/api/pkg/errors" + fn "github.com/kloudlite/api/pkg/functions" "github.com/kloudlite/api/pkg/logging" "github.com/kloudlite/api/pkg/repos" ) @@ -42,7 +43,7 @@ func (s *GrpcService) UpdateMembership(ctx context.Context, in *iam.UpdateMember rb.Role = t.Role(in.Role) if _, err = s.rbRepo.UpdateById(ctx, rb.Id, rb); err != nil { - return nil, errors.NewE(err) + return nil, errors.NewE(err) } return &iam.UpdateMembershipOut{ @@ -111,14 +112,48 @@ func (s *GrpcService) ListMembershipsForResource(ctx context.Context, in *iam.Me func (s *GrpcService) Can(ctx context.Context, in *iam.CanIn) (*iam.CanOut, error) { if strings.HasPrefix(in.UserId, "sys-user") { - return &iam.CanOut{Status: true}, nil + return &iam.CanOut{Status: true}, nil + } + + rb, ok := s.roleBindingMap[t.Action(in.Action)] + if !ok { + return &iam.CanOut{Status: false}, nil + } + + var hasAccountMemberRole bool + + canFilter := repos.Filter{ + "resource_ref": map[string]any{"$in": in.ResourceRefs}, + "user_id": in.UserId, + } + + for i := range rb { + if rb[i] == t.RoleAccountMember { + hasAccountMemberRole = true + + rr := make([]map[string]any, 0, len(in.ResourceRefs)) + + for i := range in.ResourceRefs { + accountName, _, _, err := t.ParseResourceRef(in.ResourceRefs[i]) + if err != nil { + return nil, err + } + nf := s.rbRepo.MergeMatchFilters(repos.Filter{}, map[string]repos.MatchFilter{ + "resource_ref": { + MatchType: repos.MatchTypeRegex, + Regex: fn.New(t.NewResourceRef(accountName, "*", "*")), + }, + }) + rr = append(rr, map[string]any{"resource_ref": nf["resource_ref"]}) + } + + delete(canFilter, "resource_ref") + canFilter["$or"] = rr + } } rbs, err := s.rbRepo.Find( - ctx, repos.Query{Filter: repos.Filter{ - "resource_ref": map[string]any{"$in": in.ResourceRefs}, - "user_id": in.UserId, - }}, + ctx, repos.Query{Filter: canFilter}, ) if err != nil { return nil, errors.NewEf(err, "could not find rolebindings for (resourceRefs=%s)", strings.Join(in.ResourceRefs, ",")) @@ -128,6 +163,10 @@ func (s *GrpcService) Can(ctx context.Context, in *iam.CanIn) (*iam.CanOut, erro return nil, errors.Newf("no rolebinding found for (userId=%s, resourceRefs=%s)", in.UserId, strings.Join(in.ResourceRefs, ",")) } + if hasAccountMemberRole && len(rbs) > 0 { + return &iam.CanOut{Status: true}, nil + } + for i := range rbs { // 2nd loop, but very small length (always < #roles), so it's not exactly O(n^2), much like XO(n) for _, role := range s.roleBindingMap[t.Action(in.Action)] { diff --git a/apps/iam/internal/entities/role-binding.go b/apps/iam/internal/entities/role-binding.go index 81dfbfa68..2b2e11c4d 100644 --- a/apps/iam/internal/entities/role-binding.go +++ b/apps/iam/internal/entities/role-binding.go @@ -20,12 +20,15 @@ func (rb *RoleBinding) Validate() error { if rb.UserId == "" { verr.Errors = append(verr.Errors, "user_id is required") } + if rb.ResourceType == "" { verr.Errors = append(verr.Errors, "resource_type is required") } + if rb.ResourceRef == "" { verr.Errors = append(verr.Errors, "resource_ref is required") } + if rb.Role == "" { verr.Errors = append(verr.Errors, "role is required") } diff --git a/apps/iam/types/types.go b/apps/iam/types/types.go index 1a15a4cea..7bde03e4f 100644 --- a/apps/iam/types/types.go +++ b/apps/iam/types/types.go @@ -1,6 +1,9 @@ package types -import "fmt" +import ( + "fmt" + "strings" +) type ResourceType string @@ -148,3 +151,12 @@ const ( func NewResourceRef(accountName string, resourceType ResourceType, resourceName string) string { return fmt.Sprintf("%s/%s/%s", accountName, resourceType, resourceName) } + +func ParseResourceRef(rref string) (accountName, resourceType, resourceName string, err error) { + sp := strings.SplitN(rref, "/", 3) + if len(sp) != 3 { + return "", "", "", fmt.Errorf("invalid resource ref %s", rref) + } + + return sp[0], sp[1], sp[2], nil +} From 4a0a979ef1d21daab0655a94ef75f704fd5ae043 Mon Sep 17 00:00:00 2001 From: nxtcoder17 Date: Tue, 6 Feb 2024 16:23:49 +0530 Subject: [PATCH 04/10] fix(apps/console): fixes promql metrics querying gh-issue: #249 --- .../console/logs-and-metrics.rest.yml | 7 ++-- .../__http__/infra/vpn-devices.graphql.yml | 30 ++++++++++++++-- apps/console/internal/app/app.go | 35 +++++-------------- .../internal/app/observability-handler.go | 21 +++-------- apps/infra/internal/env/env.go | 2 -- 5 files changed, 44 insertions(+), 51 deletions(-) diff --git a/.tools/nvim/__http__/console/logs-and-metrics.rest.yml b/.tools/nvim/__http__/console/logs-and-metrics.rest.yml index d5aad41ab..96d75c0cc 100644 --- a/.tools/nvim/__http__/console/logs-and-metrics.rest.yml +++ b/.tools/nvim/__http__/console/logs-and-metrics.rest.yml @@ -7,8 +7,8 @@ global: clusterName: "nova-303453" clusterNamespace: "kl-account-nova" - endTime: '1700803914' - startTime: '1700717575' + endTime: '1707205625' + startTime: '1707119214' --- label: Get Logs for App @@ -27,8 +27,7 @@ query: label: Get Memory Metrics for App query: method: GET - # url: 'http://console-api.kl-core.svc.cluster.local:9100/observability/metrics/memory?workspace_name={{.workspaceName}}&start_time={{.startTime}}&end_time={{.endTime}}' - url: 'http://console-api.karthik-testing.svc.cluster.local:9100/observability/metrics/memory?cluster_name=sample-cluster&tracking_id=app-k-zmtg0km7epjj-fq89uvao14-3-l' + url: 'http://console-api.kloudlite.svc.cluster.local:9100/observability/metrics/memory?cluster_name=ab-cluster-3&tracking_id=app-3ez2fpr-3oc8gqjib-ii5-pbat6d&step=5m' --- diff --git a/.tools/nvim/__http__/infra/vpn-devices.graphql.yml b/.tools/nvim/__http__/infra/vpn-devices.graphql.yml index 2aa6701bc..099a5329f 100644 --- a/.tools/nvim/__http__/infra/vpn-devices.graphql.yml +++ b/.tools/nvim/__http__/infra/vpn-devices.graphql.yml @@ -2,8 +2,6 @@ global: namespace: sample name: s1 - namespace: sample-nxtcoder17 - clusterName: sample-cluster2 --- label: List VPN Devices @@ -51,6 +49,34 @@ query: |+ } } +--- +label: "Get VPN Device" +query: |+ + query Core_getVPNDevices($name: String!) { + core_getVPNDevice(name: $name) { + displayName + metadata { + name + } + clusterName + projectName + spec { + activeNamespace + disabled + ports { + port + targetPort + } + } + wireguardConfig { + encoding + value + } + } + } +variables: + name: "baby-589403" + --- label: Create VPN Device diff --git a/apps/console/internal/app/app.go b/apps/console/internal/app/app.go index c2f8f0d06..bf759be36 100644 --- a/apps/console/internal/app/app.go +++ b/apps/console/internal/app/app.go @@ -2,8 +2,8 @@ package app import ( "context" + "fmt" "net/http" - "strconv" "time" "github.com/kloudlite/api/pkg/errors" @@ -22,7 +22,6 @@ import ( "github.com/kloudlite/api/constants" "github.com/kloudlite/api/grpc-interfaces/kloudlite.io/rpc/iam" "github.com/kloudlite/api/grpc-interfaces/kloudlite.io/rpc/infra" - fn "github.com/kloudlite/api/pkg/functions" "github.com/kloudlite/api/pkg/grpc" httpServer "github.com/kloudlite/api/pkg/http-server" "github.com/kloudlite/api/pkg/kv" @@ -94,12 +93,12 @@ var Module = fx.Module("app", clusterName := c.Query("cluster_name") if clusterName == "" { - return errors.New("query param (cluster_name) must be provided") + return c.Status(http.StatusBadRequest).JSON(map[string]any{"error": "query param (cluster_name) must be provided"}) } trackingId := c.Query("tracking_id") if trackingId == "" { - return errors.New("query param (tracking_id) must be provided") + return c.Status(http.StatusBadRequest).JSON(map[string]any{"error": "query param (tracking_id) must be provided"}) } can, err := iamCli.Can(c.Context(), &iam.CanIn{ @@ -114,38 +113,20 @@ var Module = fx.Module("app", } if !can.Status { - return &fiber.Error{Code: http.StatusUnauthorized, Message: errors.NewEf(err, "unauthorized to view metrics for resources belonging to account (%s)", cc.AccountName).Error()} + return &fiber.Error{Code: http.StatusUnauthorized, Message: fmt.Sprintf("unauthorized to view metrics for resources belonging to account (%s)", cc.AccountName)} } metricType := c.Params("metric_type") - st := c.Query("start_time") - et := c.Query("end_time") - - var startTime *time.Time - var endTime *time.Time - - if st != "" { - st, err := strconv.ParseInt(st, 10, 64) - if err != nil { - return errors.NewE(err) - } - startTime = fn.New(time.Unix(st, 0)) - } - - if et != "" { - et, err := strconv.ParseInt(et, 10, 64) - if err != nil { - return errors.NewE(err) - } - endTime = fn.New(time.Unix(et, 0)) - } + st := c.Query("start_time", fmt.Sprintf("%d", time.Now().Add(-3*time.Hour).Unix())) + et := c.Query("end_time", fmt.Sprintf("%d", time.Now().Unix())) + step := c.Query("step", "5m") return queryProm(ev.PromHttpAddr, PromMetricsType(metricType), map[string]string{ "kl_account_name": cc.AccountName, "kl_cluster_name": clusterName, "kl_tracking_id": trackingId, - }, startTime, endTime, c.Response().BodyWriter()) + }, st, et, step, c.Response().BodyWriter()) }) }, ), diff --git a/apps/console/internal/app/observability-handler.go b/apps/console/internal/app/observability-handler.go index 46d82a48c..91881fd67 100644 --- a/apps/console/internal/app/observability-handler.go +++ b/apps/console/internal/app/observability-handler.go @@ -10,7 +10,6 @@ import ( "time" "github.com/kloudlite/api/pkg/errors" - fn "github.com/kloudlite/api/pkg/functions" ) type ObservabilityArgs struct { @@ -109,10 +108,9 @@ func buildPromQuery(resType PromMetricsType, filters map[string]string) (string, default: return "", errors.New("unknown prom metrics type provided") } - } -func queryProm(promAddr string, resType PromMetricsType, filters map[string]string, startTime *time.Time, endTime *time.Time, writer io.Writer) error { +func queryProm(promAddr string, resType PromMetricsType, filters map[string]string, startTime string, endTime string, step string, writer io.Writer) error { promQuery, err := buildPromQuery(resType, filters) if err != nil { return errors.NewE(err) @@ -128,18 +126,9 @@ func queryProm(promAddr string, resType PromMetricsType, filters map[string]stri qp := u.Query() qp.Add("query", promQuery) - t := time.Now() - if startTime == nil { - startTime = fn.New(t.Add(-2 * 24 * time.Hour)) - } - if endTime == nil { - endTime = &t - } - - qp.Add("start", fmt.Sprintf("%d", startTime.Unix())) - qp.Add("end", fmt.Sprintf("%d", endTime.Unix())) - // qp.Add("step", "700") // 15 minute - qp.Add("step", "345") // 15 minute + qp.Add("start", startTime) + qp.Add("end", endTime) + qp.Add("step", step) u.RawQuery = qp.Encode() @@ -148,7 +137,7 @@ func queryProm(promAddr string, resType PromMetricsType, filters map[string]stri return errors.NewE(err) } - // fmt.Printf("[DEBUG]: prometheus actual request: %s\n", req.URL.String()) + fmt.Printf("[DEBUG]: prometheus actual request: %s\n", req.URL.String()) resp, err := http.DefaultClient.Do(req) if err != nil { diff --git a/apps/infra/internal/env/env.go b/apps/infra/internal/env/env.go index 3256b1c75..75e8f703b 100644 --- a/apps/infra/internal/env/env.go +++ b/apps/infra/internal/env/env.go @@ -43,8 +43,6 @@ type Env struct { MsvcTemplateFilePath string `env:"MSVC_TEMPLATE_FILE_PATH" required:"true"` - DeviceNamespace string `env:"DEVICE_NAMESPACE" required:"true"` - KloudliteRelease string `env:"KLOUDLITE_RELEASE" required:"true"` } From 7b277ba04c2688622cd1609ff26d2c2c239d62c3 Mon Sep 17 00:00:00 2001 From: Piyush Kumar Date: Tue, 6 Feb 2024 17:08:58 +0530 Subject: [PATCH 05/10] feature(infra): added delete pv api --- apps/console/internal/domain/environment.go | 4 +- .../internal/app/graph/generated/generated.go | 134 +++++++++++++++++- apps/infra/internal/app/graph/schema.graphqls | 2 + .../internal/app/graph/schema.resolvers.go | 14 +- apps/infra/internal/domain/api.go | 1 + apps/infra/internal/domain/clusters.go | 13 ++ apps/infra/internal/domain/pv.go | 24 ++++ 7 files changed, 187 insertions(+), 5 deletions(-) diff --git a/apps/console/internal/domain/environment.go b/apps/console/internal/domain/environment.go index 669b8d098..f9abf7d67 100644 --- a/apps/console/internal/domain/environment.go +++ b/apps/console/internal/domain/environment.go @@ -187,7 +187,7 @@ func (d *domain) CloneEnvironment(ctx ConsoleContext, projectName string, source }, Spec: crdsv1.EnvironmentSpec{ ProjectName: projectName, - TargetNamespace: fmt.Sprintf("env-%s", destinationEnvName), + TargetNamespace: d.getEnvironmentTargetNamespace(projectName, destinationEnvName), Routing: &crdsv1.EnvironmentRouting{ Mode: envRoutingMode, }, @@ -279,7 +279,7 @@ func (d *domain) CloneEnvironment(ctx ConsoleContext, projectName string, source resourceMetadata := func(dn string) common.ResourceMetadata { return common.ResourceMetadata{ - DisplayName: fmt.Sprintf("clone of %s", dn), + DisplayName: dn, CreatedBy: common.CreatedOrUpdatedBy{ UserId: ctx.UserId, UserName: ctx.UserName, diff --git a/apps/infra/internal/app/graph/generated/generated.go b/apps/infra/internal/app/graph/generated/generated.go index fa3596a7e..ad27988a4 100644 --- a/apps/infra/internal/app/graph/generated/generated.go +++ b/apps/infra/internal/app/graph/generated/generated.go @@ -969,6 +969,7 @@ type ComplexityRoot struct { InfraDeleteHelmRelease func(childComplexity int, clusterName string, releaseName string) int InfraDeleteNodePool func(childComplexity int, clusterName string, poolName string) int InfraDeleteProviderSecret func(childComplexity int, secretName string) int + InfraDeletePv func(childComplexity int, clusterName string, pvName string) int InfraUpdateCluster func(childComplexity int, cluster entities.Cluster) int InfraUpdateClusterManagedService func(childComplexity int, clusterName string, service entities.ClusterManagedService) int InfraUpdateDomainEntry func(childComplexity int, domainEntry entities.DomainEntry) int @@ -1286,6 +1287,7 @@ type MutationResolver interface { InfraCreateHelmRelease(ctx context.Context, clusterName string, release entities.HelmRelease) (*entities.HelmRelease, error) InfraUpdateHelmRelease(ctx context.Context, clusterName string, release entities.HelmRelease) (*entities.HelmRelease, error) InfraDeleteHelmRelease(ctx context.Context, clusterName string, releaseName string) (bool, error) + InfraDeletePv(ctx context.Context, clusterName string, pvName string) (bool, error) } type NamespaceResolver interface { CreationTime(ctx context.Context, obj *entities.Namespace) (string, error) @@ -5331,6 +5333,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.InfraDeleteProviderSecret(childComplexity, args["secretName"].(string)), true + case "Mutation.infra_deletePV": + if e.complexity.Mutation.InfraDeletePv == nil { + break + } + + args, err := ec.field_Mutation_infra_deletePV_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.InfraDeletePv(childComplexity, args["clusterName"].(string), args["pvName"].(string)), true + case "Mutation.infra_updateCluster": if e.complexity.Mutation.InfraUpdateCluster == nil { break @@ -6894,6 +6908,8 @@ type Mutation { infra_createHelmRelease(clusterName: String!, release: HelmReleaseIn!): HelmRelease @isLoggedInAndVerified @hasAccount infra_updateHelmRelease(clusterName: String!, release: HelmReleaseIn!): HelmRelease @isLoggedInAndVerified @hasAccount infra_deleteHelmRelease(clusterName: String!, releaseName: String!): Boolean! @isLoggedInAndVerified @hasAccount + + infra_deletePV(clusterName: String!, pvName: String!): Boolean! @isLoggedInAndVerified @hasAccount } type EncodedValue { @@ -8968,6 +8984,30 @@ func (ec *executionContext) field_Mutation_infra_deleteNodePool_args(ctx context return args, nil } +func (ec *executionContext) field_Mutation_infra_deletePV_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["clusterName"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("clusterName")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["clusterName"] = arg0 + var arg1 string + if tmp, ok := rawArgs["pvName"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("pvName")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["pvName"] = arg1 + return args, nil +} + func (ec *executionContext) field_Mutation_infra_deleteProviderSecret_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -35802,6 +35842,87 @@ func (ec *executionContext) fieldContext_Mutation_infra_deleteHelmRelease(ctx co return fc, nil } +func (ec *executionContext) _Mutation_infra_deletePV(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_infra_deletePV(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().InfraDeletePv(rctx, fc.Args["clusterName"].(string), fc.Args["pvName"].(string)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.IsLoggedInAndVerified == nil { + return nil, errors.New("directive isLoggedInAndVerified is not implemented") + } + return ec.directives.IsLoggedInAndVerified(ctx, nil, directive0) + } + directive2 := func(ctx context.Context) (interface{}, error) { + if ec.directives.HasAccount == nil { + return nil, errors.New("directive hasAccount is not implemented") + } + return ec.directives.HasAccount(ctx, nil, directive1) + } + + tmp, err := directive2(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(bool); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be bool`, tmp) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_infra_deletePV(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_infra_deletePV_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return + } + return fc, nil +} + func (ec *executionContext) _Namespace_accountName(ctx context.Context, field graphql.CollectedField, obj *entities.Namespace) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Namespace_accountName(ctx, field) if err != nil { @@ -57154,6 +57275,15 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) return ec._Mutation_infra_deleteHelmRelease(ctx, field) }) + if out.Values[i] == graphql.Null { + invalids++ + } + case "infra_deletePV": + + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_infra_deletePV(ctx, field) + }) + if out.Values[i] == graphql.Null { invalids++ } @@ -61628,7 +61758,7 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a return res } -func (ec *executionContext) unmarshalOAny2interface(ctx context.Context, v interface{}) (interface{}, error) { +func (ec *executionContext) unmarshalOAny2interface(ctx context.Context, v interface{}) (any, error) { if v == nil { return nil, nil } @@ -61636,7 +61766,7 @@ func (ec *executionContext) unmarshalOAny2interface(ctx context.Context, v inter return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalOAny2interface(ctx context.Context, sel ast.SelectionSet, v interface{}) graphql.Marshaler { +func (ec *executionContext) marshalOAny2interface(ctx context.Context, sel ast.SelectionSet, v any) graphql.Marshaler { if v == nil { return graphql.Null } diff --git a/apps/infra/internal/app/graph/schema.graphqls b/apps/infra/internal/app/graph/schema.graphqls index d6ad9a83c..ac2ab170d 100644 --- a/apps/infra/internal/app/graph/schema.graphqls +++ b/apps/infra/internal/app/graph/schema.graphqls @@ -137,6 +137,8 @@ type Mutation { infra_createHelmRelease(clusterName: String!, release: HelmReleaseIn!): HelmRelease @isLoggedInAndVerified @hasAccount infra_updateHelmRelease(clusterName: String!, release: HelmReleaseIn!): HelmRelease @isLoggedInAndVerified @hasAccount infra_deleteHelmRelease(clusterName: String!, releaseName: String!): Boolean! @isLoggedInAndVerified @hasAccount + + infra_deletePV(clusterName: String!, pvName: String!): Boolean! @isLoggedInAndVerified @hasAccount } type EncodedValue { diff --git a/apps/infra/internal/app/graph/schema.resolvers.go b/apps/infra/internal/app/graph/schema.resolvers.go index 13b9059cd..482cb1712 100644 --- a/apps/infra/internal/app/graph/schema.resolvers.go +++ b/apps/infra/internal/app/graph/schema.resolvers.go @@ -7,7 +7,6 @@ package graph import ( "context" "encoding/base64" - "github.com/kloudlite/api/apps/infra/internal/app/graph/generated" "github.com/kloudlite/api/apps/infra/internal/app/graph/model" "github.com/kloudlite/api/apps/infra/internal/domain" @@ -226,6 +225,19 @@ func (r *mutationResolver) InfraDeleteHelmRelease(ctx context.Context, clusterNa return true, nil } +// InfraDeletePv is the resolver for the infra_deletePV field. +func (r *mutationResolver) InfraDeletePv(ctx context.Context, clusterName string, pvName string) (bool, error) { + ictx, err := toInfraContext(ctx) + if err != nil { + return false, errors.NewE(err) + } + + if err := r.Domain.DeletePV(ictx, clusterName, pvName); err != nil { + return false, errors.NewE(err) + } + return true, nil +} + // InfraCheckNameAvailability is the resolver for the infra_checkNameAvailability field. func (r *queryResolver) InfraCheckNameAvailability(ctx context.Context, resType domain.ResType, clusterName *string, name string) (*domain.CheckNameAvailabilityOutput, error) { ictx, err := toInfraContext(ctx) diff --git a/apps/infra/internal/domain/api.go b/apps/infra/internal/domain/api.go index 7a8295220..71b513a7d 100644 --- a/apps/infra/internal/domain/api.go +++ b/apps/infra/internal/domain/api.go @@ -133,6 +133,7 @@ type Domain interface { GetPV(ctx InfraContext, clusterName string, pvName string) (*entities.PersistentVolume, error) OnPVUpdateMessage(ctx InfraContext, clusterName string, pv entities.PersistentVolume, status types.ResourceStatus, opts UpdateAndDeleteOpts) error OnPVDeleteMessage(ctx InfraContext, clusterName string, pv entities.PersistentVolume) error + DeletePV(ctx InfraContext, clusterName string, pvName string) error OnIngressUpdateMessage(ctx InfraContext, clusterName string, ingress networkingv1.Ingress, status types.ResourceStatus, opts UpdateAndDeleteOpts) error OnIngressDeleteMessage(ctx InfraContext, clusterName string, ingress networkingv1.Ingress) error diff --git a/apps/infra/internal/domain/clusters.go b/apps/infra/internal/domain/clusters.go index a6e1cfe92..0f07054ba 100644 --- a/apps/infra/internal/domain/clusters.go +++ b/apps/infra/internal/domain/clusters.go @@ -405,6 +405,19 @@ func (d *domain) DeleteCluster(ctx InfraContext, name string) error { return errors.NewE(err) } + filter := map[string]repos.MatchFilter{} + pagination := &repos.DefaultCursorPagination + + npList, err := d.ListNodePools(ctx, name, filter, *pagination) + if npList.TotalCount != 0 { + return errors.Newf("delete nodepool first, aborting cluster deletion") + } + + pvList, err := d.ListPVs(ctx, name, filter, *pagination) + if pvList.TotalCount != 0 { + return errors.Newf("delete pvs first, aborting cluster deletion") + } + ucluster, err := d.clusterRepo.Patch( ctx, repos.Filter{ diff --git a/apps/infra/internal/domain/pv.go b/apps/infra/internal/domain/pv.go index 2531435dd..ebbea698f 100644 --- a/apps/infra/internal/domain/pv.go +++ b/apps/infra/internal/domain/pv.go @@ -1,7 +1,9 @@ package domain import ( + iamT "github.com/kloudlite/api/apps/iam/types" "github.com/kloudlite/api/apps/infra/internal/entities" + "github.com/kloudlite/api/common" "github.com/kloudlite/api/common/fields" "github.com/kloudlite/api/pkg/errors" "github.com/kloudlite/api/pkg/repos" @@ -35,6 +37,28 @@ func (d *domain) ListPVs(ctx InfraContext, clusterName string, search map[string return d.pvRepo.FindPaginated(ctx, d.nodePoolRepo.MergeMatchFilters(filter, search), pagination) } +func (d *domain) DeletePV(ctx InfraContext, clusterName string, pvName string) error { + if err := d.canPerformActionInAccount(ctx, iamT.DeleteNodepool); err != nil { + return errors.NewE(err) + } + + upv, err := d.pvRepo.Patch( + ctx, + repos.Filter{ + fields.ClusterName: clusterName, + fields.AccountName: ctx.AccountName, + fields.MetadataName: pvName, + }, + common.PatchForMarkDeletion(), + ) + if err != nil { + return errors.NewE(err) + } + + d.resourceEventPublisher.PublishResourceEvent(ctx, clusterName, ResourceTypeNodePool, upv.Name, PublishUpdate) + return d.resDispatcher.DeleteFromTargetCluster(ctx, clusterName, &upv.PersistentVolume) +} + // OnPVDeleteMessage implements Domain. func (d *domain) OnPVDeleteMessage(ctx InfraContext, clusterName string, pv entities.PersistentVolume) error { if err := d.pvRepo.DeleteOne(ctx, repos.Filter{ From a9d8f9216fbb9bcf87c98cac7506305f989a023f Mon Sep 17 00:00:00 2001 From: Piyush Kumar Date: Tue, 6 Feb 2024 17:27:17 +0530 Subject: [PATCH 06/10] added repo count method for nodepool and pv count --- apps/infra/internal/domain/clusters.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/infra/internal/domain/clusters.go b/apps/infra/internal/domain/clusters.go index 0f07054ba..b66ddf655 100644 --- a/apps/infra/internal/domain/clusters.go +++ b/apps/infra/internal/domain/clusters.go @@ -405,17 +405,25 @@ func (d *domain) DeleteCluster(ctx InfraContext, name string) error { return errors.NewE(err) } - filter := map[string]repos.MatchFilter{} - pagination := &repos.DefaultCursorPagination + filter := repos.Filter{ + fields.AccountName: ctx.AccountName, + fields.ClusterName: name, + } - npList, err := d.ListNodePools(ctx, name, filter, *pagination) - if npList.TotalCount != 0 { + npCount, err := d.nodePoolRepo.Count(ctx, filter) + if err != nil { + return errors.NewE(err) + } + if npCount != 0 { return errors.Newf("delete nodepool first, aborting cluster deletion") } - pvList, err := d.ListPVs(ctx, name, filter, *pagination) - if pvList.TotalCount != 0 { - return errors.Newf("delete pvs first, aborting cluster deletion") + pvCount, err := d.nodePoolRepo.Count(ctx, filter) + if err != nil { + return errors.NewE(err) + } + if pvCount != 0 { + return errors.Newf("delete nodepool first, aborting cluster deletion") } ucluster, err := d.clusterRepo.Patch( From 985a24e5ba680b42391339ed04bbc7ba15426f7f Mon Sep 17 00:00:00 2001 From: Piyush Kumar Date: Tue, 6 Feb 2024 17:33:58 +0530 Subject: [PATCH 07/10] added repo count method for nodepool and pv count --- apps/infra/internal/domain/clusters.go | 4 ++-- apps/infra/internal/domain/pv.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/infra/internal/domain/clusters.go b/apps/infra/internal/domain/clusters.go index b66ddf655..325688c4a 100644 --- a/apps/infra/internal/domain/clusters.go +++ b/apps/infra/internal/domain/clusters.go @@ -418,12 +418,12 @@ func (d *domain) DeleteCluster(ctx InfraContext, name string) error { return errors.Newf("delete nodepool first, aborting cluster deletion") } - pvCount, err := d.nodePoolRepo.Count(ctx, filter) + pvCount, err := d.pvRepo.Count(ctx, filter) if err != nil { return errors.NewE(err) } if pvCount != 0 { - return errors.Newf("delete nodepool first, aborting cluster deletion") + return errors.Newf("delete pvs first, aborting cluster deletion") } ucluster, err := d.clusterRepo.Patch( diff --git a/apps/infra/internal/domain/pv.go b/apps/infra/internal/domain/pv.go index ebbea698f..b01e438bf 100644 --- a/apps/infra/internal/domain/pv.go +++ b/apps/infra/internal/domain/pv.go @@ -38,6 +38,7 @@ func (d *domain) ListPVs(ctx InfraContext, clusterName string, search map[string } func (d *domain) DeletePV(ctx InfraContext, clusterName string, pvName string) error { + // FIXME: (IAM role binding for DeletePV) if err := d.canPerformActionInAccount(ctx, iamT.DeleteNodepool); err != nil { return errors.NewE(err) } From e8080adbf38c807d156275aa8f6b6c6c5ad3f544 Mon Sep 17 00:00:00 2001 From: nxtcoder17 Date: Tue, 6 Feb 2024 19:23:09 +0530 Subject: [PATCH 08/10] fix(apps/console): updates mres '.spec.resourceName' when cloning managed resources gh-issue: #252 --- apps/console/internal/domain/environment.go | 31 ++++++++++++++------- apps/console/internal/domain/mres.go | 6 +++- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/apps/console/internal/domain/environment.go b/apps/console/internal/domain/environment.go index f9abf7d67..ee1d7f674 100644 --- a/apps/console/internal/domain/environment.go +++ b/apps/console/internal/domain/environment.go @@ -215,6 +215,15 @@ func (d *domain) CloneEnvironment(ctx ConsoleContext, projectName string, source return nil, errors.NewE(err) } + if _, err := d.iamClient.AddMembership(ctx, &iam.AddMembershipIn{ + UserId: string(ctx.UserId), + ResourceType: string(iamT.ResourceEnvironment), + ResourceRef: iamT.NewResourceRef(ctx.AccountName, iamT.ResourceEnvironment, destEnv.Spec.TargetNamespace), + Role: string(iamT.RoleResourceOwner), + }); err != nil { + d.logger.Errorf(err, "error while adding membership") + } + destEnv, err = d.environmentRepo.Create(ctx, destEnv) if err != nil { return nil, errors.NewE(err) @@ -224,6 +233,15 @@ func (d *domain) CloneEnvironment(ctx ConsoleContext, projectName string, source return nil, errors.NewE(err) } + if err := d.applyK8sResource(ctx, sourceEnv.ProjectName, &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Namespace"}, + ObjectMeta: metav1.ObjectMeta{ + Name: destEnv.Spec.TargetNamespace, + }, + }, destEnv.RecordVersion); err != nil { + return nil, errors.NewE(err) + } + if err := d.applyK8sResource(ctx, sourceEnv.ProjectName, &destEnv.Environment, destEnv.RecordVersion); err != nil { return nil, errors.NewE(err) } @@ -370,11 +388,13 @@ func (d *domain) CloneEnvironment(ctx ConsoleContext, projectName string, source } for i := range managedResources { + spec := managedResources[i].Spec + spec.ResourceName = genMresResourceName(destEnv.Name, managedResources[i].Name) if _, err := d.createAndApplyManagedResource(resCtx, &entities.ManagedResource{ ManagedResource: crdsv1.ManagedResource{ TypeMeta: managedResources[i].TypeMeta, ObjectMeta: objectMeta(managedResources[i].ObjectMeta, destEnv.Spec.TargetNamespace), - Spec: managedResources[i].Spec, + Spec: spec, Enabled: managedResources[i].Enabled, }, AccountName: ctx.AccountName, @@ -386,15 +406,6 @@ func (d *domain) CloneEnvironment(ctx ConsoleContext, projectName string, source } } - if _, err := d.iamClient.AddMembership(ctx, &iam.AddMembershipIn{ - UserId: string(ctx.UserId), - ResourceType: string(iamT.ResourceEnvironment), - ResourceRef: iamT.NewResourceRef(ctx.AccountName, iamT.ResourceEnvironment, destEnv.Spec.TargetNamespace), - Role: string(iamT.RoleResourceOwner), - }); err != nil { - d.logger.Errorf(err, "error while adding membership") - } - return destEnv, nil } diff --git a/apps/console/internal/domain/mres.go b/apps/console/internal/domain/mres.go index 5fa339439..8503b58ef 100644 --- a/apps/console/internal/domain/mres.go +++ b/apps/console/internal/domain/mres.go @@ -154,11 +154,15 @@ func (d *domain) CreateManagedResource(ctx ResourceContext, mres entities.Manage mres.ProjectName = ctx.ProjectName mres.EnvironmentName = ctx.EnvironmentName - mres.Spec.ResourceName = fmt.Sprintf("env-%s-%s", ctx.EnvironmentName, mres.Name) + mres.Spec.ResourceName = genMresResourceName(ctx.EnvironmentName, mres.Name) return d.createAndApplyManagedResource(ctx, &mres) } +func genMresResourceName(envName string, mresName string) string { + return fmt.Sprintf("env-%s-%s", envName, mresName) +} + func (d *domain) createAndApplyManagedResource(ctx ResourceContext, mres *entities.ManagedResource) (*entities.ManagedResource, error) { mres.SyncStatus = t.GenSyncStatus(t.SyncActionApply, 0) From 740d6c018a1b2224cc579b33a1d15daac2876402 Mon Sep 17 00:00:00 2001 From: Abdhesh Nayak Date: Wed, 7 Feb 2024 10:56:00 +0530 Subject: [PATCH 09/10] :bug: Fixed issue with get-device handled error for the get-device in case of no cluster or no vpn device --- apps/console/internal/domain/vpn-device.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/console/internal/domain/vpn-device.go b/apps/console/internal/domain/vpn-device.go index 7d944b170..a2d7d00bb 100644 --- a/apps/console/internal/domain/vpn-device.go +++ b/apps/console/internal/domain/vpn-device.go @@ -113,11 +113,11 @@ func (d *domain) GetVPNDevice(ctx ConsoleContext, name string) (*entities.Consol clusterName, err := d.getClusterFromDevice(ctx, device) if err != nil { - return nil, errors.NewE(err) + return device, nil } if device.WireguardConfigs == nil || device.WireguardConfigs[clusterName].Value == "" { - return nil, errors.Newf("no wireguard configs found") + return device, nil } device.WireguardConfig = device.WireguardConfigs[clusterName] From d3ed3bfef0eadfd2bc3183ccbe4a7010a7cbf8b7 Mon Sep 17 00:00:00 2001 From: nxtcoder17 Date: Wed, 7 Feb 2024 13:22:31 +0530 Subject: [PATCH 10/10] docs: updates CHANGELOG --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75ecccc87..a41da9bb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v1.0.1] - 2024-02-07 + +### Added + +- [apps/console] fixes managed resources created during environment cloning, `.spec.resourceName` is now generated differently for cloned environment +- [apps/iam] fixes resolution of role `account-member` for actions `read-logs`, and `read-metrics` +- [apps/infra] adds support for PV deletion +- [apps/infra] fixes `getDevice` API. In case of unavailablity of wireguard config, it threw error, which caused [kloudlite/kl] to exit with non-zero code. + ## [v1.0.0] - 2024-02-04 ### Added - [apps/infra] tenant clusters installation of `charts/kloudlite-agent` is now installed and managed by infra API. It is done to ensure that kloudlite can upgrade those releases, as new releases arrive + +[v1.0.1]: https://github.com/kloudlite/api/compare/v1.0.0...v1.0.1 +[kloudite/kl]: https://github.com/kloudlite/ki