diff --git a/.tools/nvim/__http__/infra/byoc-clusters.graphql.yml b/.tools/nvim/__http__/infra/byoc-clusters.graphql.yml index d3e565919..a3c74b9ab 100644 --- a/.tools/nvim/__http__/infra/byoc-clusters.graphql.yml +++ b/.tools/nvim/__http__/infra/byoc-clusters.graphql.yml @@ -10,33 +10,8 @@ global: label: List BYOC Clusters query: |+ - query Infra_listBYOCClusters { - infra_listBYOCClusters { - edges { - cursor - node { - creationTime - apiVersion - # clusterToken - kind - metadata { - name - namespace - generation - } - syncStatus { - state - action - recordVersion - } - } - } - pageInfo { - endCursor - hasNextPage - hasPreviousPage - startCursor - } + query Infra_listBYOKClusters { + infra_listBYOKClusters { totalCount } } diff --git a/.tools/nvim/__http__/infra/cmsvc.graphql.yml b/.tools/nvim/__http__/infra/cmsvc.graphql.yml new file mode 100644 index 000000000..58bd16595 --- /dev/null +++ b/.tools/nvim/__http__/infra/cmsvc.graphql.yml @@ -0,0 +1,20 @@ +--- +label: "Clone Cluster Managed Service" +query: |+ + mutation Infra_cloneClusterManagedService($clusterName: String!, $sourceMsvcName: String!, $destinationMsvcName: String!, $displayName: String!) { + infra_cloneClusterManagedService(clusterName: $clusterName, sourceMsvcName: $sourceMsvcName, destinationMsvcName: $destinationMsvcName, displayName: $displayName) { + id + displayName + metadata { + name + } + } + } +variables: + { + "clusterName": "test-cluster1", + "sourceMsvcName": "msvc-one", + "destinationMsvcName": "msvc-one-clone2", + "displayName": "msvc one clone2" + } +--- diff --git a/apps/iam/internal/app/action-role-binding.go b/apps/iam/internal/app/action-role-binding.go index a3c1be372..fed10ef4d 100644 --- a/apps/iam/internal/app/action-role-binding.go +++ b/apps/iam/internal/app/action-role-binding.go @@ -48,6 +48,7 @@ var roleBindings RoleBindingMap = RoleBindingMap{ // for clusterManagedService t.CreateClusterManagedService: []t.Role{t.RoleAccountOwner, t.RoleAccountAdmin, t.RoleAccountMember}, + t.CloneClusterManagedService: []t.Role{t.RoleAccountOwner, t.RoleAccountAdmin, t.RoleAccountMember}, t.DeleteClusterManagedService: []t.Role{t.RoleAccountOwner, t.RoleAccountAdmin, t.RoleAccountMember}, t.UpdateClusterManagedService: []t.Role{t.RoleAccountOwner, t.RoleAccountAdmin, t.RoleAccountMember}, t.ListClusterManagedServices: []t.Role{t.RoleAccountOwner, t.RoleAccountAdmin, t.RoleAccountMember}, diff --git a/apps/iam/types/types.go b/apps/iam/types/types.go index 859c93580..451cf5698 100644 --- a/apps/iam/types/types.go +++ b/apps/iam/types/types.go @@ -70,6 +70,7 @@ const ( // cluster managed services CreateClusterManagedService Action = "create-cluster-managed-service" + CloneClusterManagedService Action = "clone-cluster-managed-service" DeleteClusterManagedService Action = "delete-cluster-managed-service" ListClusterManagedServices Action = "list-cluster-managed-services" GetClusterManagedService Action = "get-cluster-managed-service" diff --git a/apps/infra/internal/app/graph/byokcluster.resolvers.go b/apps/infra/internal/app/graph/byokcluster.resolvers.go index 3b7ff0082..d3afd0b77 100644 --- a/apps/infra/internal/app/graph/byokcluster.resolvers.go +++ b/apps/infra/internal/app/graph/byokcluster.resolvers.go @@ -6,9 +6,8 @@ package graph import ( "context" - "time" - "github.com/kloudlite/api/pkg/errors" + "time" "github.com/kloudlite/api/apps/infra/internal/app/graph/generated" "github.com/kloudlite/api/apps/infra/internal/app/graph/model" @@ -72,7 +71,5 @@ func (r *Resolver) BYOKCluster() generated.BYOKClusterResolver { return &bYOKClu // BYOKClusterIn returns generated.BYOKClusterInResolver implementation. func (r *Resolver) BYOKClusterIn() generated.BYOKClusterInResolver { return &bYOKClusterInResolver{r} } -type ( - bYOKClusterResolver struct{ *Resolver } - bYOKClusterInResolver struct{ *Resolver } -) +type bYOKClusterResolver struct{ *Resolver } +type bYOKClusterInResolver struct{ *Resolver } diff --git a/apps/infra/internal/app/graph/generated/generated.go b/apps/infra/internal/app/graph/generated/generated.go index fb8d1c5e0..db9a5a014 100644 --- a/apps/infra/internal/app/graph/generated/generated.go +++ b/apps/infra/internal/app/graph/generated/generated.go @@ -1152,6 +1152,7 @@ type ComplexityRoot struct { } Mutation struct { + InfraCloneClusterManagedService func(childComplexity int, clusterName string, sourceMsvcName string, destinationMsvcName string, displayName string) int InfraCreateBYOKCluster func(childComplexity int, cluster entities.BYOKCluster) int InfraCreateCluster func(childComplexity int, cluster entities.Cluster) int InfraCreateClusterManagedService func(childComplexity int, service entities.ClusterManagedService) int @@ -1541,6 +1542,7 @@ type MutationResolver interface { InfraCreateClusterManagedService(ctx context.Context, service entities.ClusterManagedService) (*entities.ClusterManagedService, error) InfraUpdateClusterManagedService(ctx context.Context, service entities.ClusterManagedService) (*entities.ClusterManagedService, error) InfraDeleteClusterManagedService(ctx context.Context, name string) (bool, error) + InfraCloneClusterManagedService(ctx context.Context, clusterName string, sourceMsvcName string, destinationMsvcName string, displayName string) (*entities.ClusterManagedService, error) 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) @@ -6298,6 +6300,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.MsvcTemplate.Items(childComplexity), true + case "Mutation.infra_cloneClusterManagedService": + if e.complexity.Mutation.InfraCloneClusterManagedService == nil { + break + } + + args, err := ec.field_Mutation_infra_cloneClusterManagedService_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.InfraCloneClusterManagedService(childComplexity, args["clusterName"].(string), args["sourceMsvcName"].(string), args["destinationMsvcName"].(string), args["displayName"].(string)), true + case "Mutation.infra_createBYOKCluster": if e.complexity.Mutation.InfraCreateBYOKCluster == nil { break @@ -8309,6 +8323,7 @@ type Mutation { infra_createClusterManagedService(service: ClusterManagedServiceIn!): ClusterManagedService @isLoggedInAndVerified @hasAccount infra_updateClusterManagedService(service: ClusterManagedServiceIn!): ClusterManagedService @isLoggedInAndVerified @hasAccount infra_deleteClusterManagedService(name: String!): Boolean! @isLoggedInAndVerified @hasAccount + infra_cloneClusterManagedService(clusterName: String!, sourceMsvcName: String!, destinationMsvcName: String!, displayName: String!): ClusterManagedService @isLoggedInAndVerified @hasAccount infra_createHelmRelease(clusterName: String!, release: HelmReleaseIn!): HelmRelease @isLoggedInAndVerified @hasAccount infra_updateHelmRelease(clusterName: String!, release: HelmReleaseIn!): HelmRelease @isLoggedInAndVerified @hasAccount @@ -10499,6 +10514,48 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...) // region ***************************** args.gotpl ***************************** +func (ec *executionContext) field_Mutation_infra_cloneClusterManagedService_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["sourceMsvcName"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sourceMsvcName")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["sourceMsvcName"] = arg1 + var arg2 string + if tmp, ok := rawArgs["destinationMsvcName"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("destinationMsvcName")) + arg2, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["destinationMsvcName"] = arg2 + var arg3 string + if tmp, ok := rawArgs["displayName"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("displayName")) + arg3, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["displayName"] = arg3 + return args, nil +} + func (ec *executionContext) field_Mutation_infra_createBYOKCluster_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -44083,6 +44140,118 @@ func (ec *executionContext) fieldContext_Mutation_infra_deleteClusterManagedServ return fc, nil } +func (ec *executionContext) _Mutation_infra_cloneClusterManagedService(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_infra_cloneClusterManagedService(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().InfraCloneClusterManagedService(rctx, fc.Args["clusterName"].(string), fc.Args["sourceMsvcName"].(string), fc.Args["destinationMsvcName"].(string), fc.Args["displayName"].(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.(*entities.ClusterManagedService); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/kloudlite/api/apps/infra/internal/entities.ClusterManagedService`, tmp) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*entities.ClusterManagedService) + fc.Result = res + return ec.marshalOClusterManagedService2ᚖgithubᚗcomᚋkloudliteᚋapiᚋappsᚋinfraᚋinternalᚋentitiesᚐClusterManagedService(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_infra_cloneClusterManagedService(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) { + switch field.Name { + case "accountName": + return ec.fieldContext_ClusterManagedService_accountName(ctx, field) + case "apiVersion": + return ec.fieldContext_ClusterManagedService_apiVersion(ctx, field) + case "clusterName": + return ec.fieldContext_ClusterManagedService_clusterName(ctx, field) + case "createdBy": + return ec.fieldContext_ClusterManagedService_createdBy(ctx, field) + case "creationTime": + return ec.fieldContext_ClusterManagedService_creationTime(ctx, field) + case "displayName": + return ec.fieldContext_ClusterManagedService_displayName(ctx, field) + case "id": + return ec.fieldContext_ClusterManagedService_id(ctx, field) + case "kind": + return ec.fieldContext_ClusterManagedService_kind(ctx, field) + case "lastUpdatedBy": + return ec.fieldContext_ClusterManagedService_lastUpdatedBy(ctx, field) + case "markedForDeletion": + return ec.fieldContext_ClusterManagedService_markedForDeletion(ctx, field) + case "metadata": + return ec.fieldContext_ClusterManagedService_metadata(ctx, field) + case "recordVersion": + return ec.fieldContext_ClusterManagedService_recordVersion(ctx, field) + case "spec": + return ec.fieldContext_ClusterManagedService_spec(ctx, field) + case "status": + return ec.fieldContext_ClusterManagedService_status(ctx, field) + case "syncStatus": + return ec.fieldContext_ClusterManagedService_syncStatus(ctx, field) + case "updateTime": + return ec.fieldContext_ClusterManagedService_updateTime(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ClusterManagedService", field.Name) + }, + } + 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_cloneClusterManagedService_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Mutation_infra_createHelmRelease(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Mutation_infra_createHelmRelease(ctx, field) if err != nil { @@ -69578,6 +69747,10 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "infra_cloneClusterManagedService": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_infra_cloneClusterManagedService(ctx, field) + }) case "infra_createHelmRelease": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_infra_createHelmRelease(ctx, field) diff --git a/apps/infra/internal/app/graph/schema.graphqls b/apps/infra/internal/app/graph/schema.graphqls index 8a8ffcf6b..7c3af587c 100644 --- a/apps/infra/internal/app/graph/schema.graphqls +++ b/apps/infra/internal/app/graph/schema.graphqls @@ -174,6 +174,7 @@ type Mutation { infra_createClusterManagedService(service: ClusterManagedServiceIn!): ClusterManagedService @isLoggedInAndVerified @hasAccount infra_updateClusterManagedService(service: ClusterManagedServiceIn!): ClusterManagedService @isLoggedInAndVerified @hasAccount infra_deleteClusterManagedService(name: String!): Boolean! @isLoggedInAndVerified @hasAccount + infra_cloneClusterManagedService(clusterName: String!, sourceMsvcName: String!, destinationMsvcName: String!, displayName: String!): ClusterManagedService @isLoggedInAndVerified @hasAccount infra_createHelmRelease(clusterName: String!, release: HelmReleaseIn!): HelmRelease @isLoggedInAndVerified @hasAccount infra_updateHelmRelease(clusterName: String!, release: HelmReleaseIn!): HelmRelease @isLoggedInAndVerified @hasAccount diff --git a/apps/infra/internal/app/graph/schema.resolvers.go b/apps/infra/internal/app/graph/schema.resolvers.go index 2920f6560..d546ffd12 100644 --- a/apps/infra/internal/app/graph/schema.resolvers.go +++ b/apps/infra/internal/app/graph/schema.resolvers.go @@ -331,6 +331,21 @@ func (r *mutationResolver) InfraDeleteClusterManagedService(ctx context.Context, return true, nil } +// InfraCloneClusterManagedService is the resolver for the infra_cloneClusterManagedService field. +func (r *mutationResolver) InfraCloneClusterManagedService(ctx context.Context, clusterName string, sourceMsvcName string, destinationMsvcName string, displayName string) (*entities.ClusterManagedService, error) { + ictx, err := toInfraContext(ctx) + if err != nil { + return nil, errors.NewE(err) + } + + return r.Domain.CloneClusterManagedService(ictx, domain.CloneManagedServiceArgs{ + ClusterName: clusterName, + SourceMsvcName: sourceMsvcName, + DestinationMsvcName: destinationMsvcName, + DisplayName: displayName, + }) +} + // InfraCreateHelmRelease is the resolver for the infra_createHelmRelease field. func (r *mutationResolver) InfraCreateHelmRelease(ctx context.Context, clusterName string, release entities.HelmRelease) (*entities.HelmRelease, error) { ictx, err := toInfraContext(ctx) diff --git a/apps/infra/internal/domain/api.go b/apps/infra/internal/domain/api.go index 0388ce7d0..bf71a2996 100644 --- a/apps/infra/internal/domain/api.go +++ b/apps/infra/internal/domain/api.go @@ -134,6 +134,7 @@ type Domain interface { ListClusterManagedServices(ctx InfraContext, search map[string]repos.MatchFilter, pagination repos.CursorPagination) (*repos.PaginatedRecord[*entities.ClusterManagedService], error) GetClusterManagedService(ctx InfraContext, serviceName string) (*entities.ClusterManagedService, error) CreateClusterManagedService(ctx InfraContext, cmsvc entities.ClusterManagedService) (*entities.ClusterManagedService, error) + CloneClusterManagedService(ctx InfraContext, args CloneManagedServiceArgs) (*entities.ClusterManagedService, error) UpdateClusterManagedService(ctx InfraContext, cmsvc entities.ClusterManagedService) (*entities.ClusterManagedService, error) DeleteClusterManagedService(ctx InfraContext, name string) error diff --git a/apps/infra/internal/domain/cluster-managed-service.go b/apps/infra/internal/domain/cluster-managed-service.go index 8db4223a8..26edf0717 100644 --- a/apps/infra/internal/domain/cluster-managed-service.go +++ b/apps/infra/internal/domain/cluster-managed-service.go @@ -1,6 +1,7 @@ package domain import ( + "fmt" iamT "github.com/kloudlite/api/apps/iam/types" "github.com/kloudlite/api/apps/infra/internal/entities" fc "github.com/kloudlite/api/apps/infra/internal/entities/field-constants" @@ -9,7 +10,9 @@ import ( "github.com/kloudlite/api/pkg/errors" "github.com/kloudlite/api/pkg/repos" t "github.com/kloudlite/api/pkg/types" + crdsv1 "github.com/kloudlite/operator/apis/crds/v1" "github.com/kloudlite/operator/operators/resource-watcher/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func (d *domain) ListClusterManagedServices(ctx InfraContext, search map[string]repos.MatchFilter, pagination repos.CursorPagination) (*repos.PaginatedRecord[*entities.ClusterManagedService], error) { @@ -114,6 +117,75 @@ func (d *domain) CreateClusterManagedService(ctx InfraContext, cmsvc entities.Cl return ncms, nil } +type CloneManagedServiceArgs struct { + SourceMsvcName string + DestinationMsvcName string + DisplayName string + ClusterName string +} + +func (d *domain) getClusterManagedServiceTargetNamespace(msvcName string) string { + return fmt.Sprintf("cmsvc-%s", msvcName) +} + +func (d *domain) CloneClusterManagedService(ctx InfraContext, args CloneManagedServiceArgs) (*entities.ClusterManagedService, error) { + + if err := d.canPerformActionInAccount(ctx, iamT.CloneClusterManagedService); err != nil { + return nil, errors.NewE(err) + } + + sourceMsvc, err := d.findClusterManagedService(ctx, args.SourceMsvcName) + if err != nil { + return nil, errors.NewE(err) + } + + destMsvc := &entities.ClusterManagedService{ + ClusterManagedService: crdsv1.ClusterManagedService{ + TypeMeta: sourceMsvc.TypeMeta, + ObjectMeta: metav1.ObjectMeta{ + Name: args.DestinationMsvcName, + Namespace: sourceMsvc.Namespace, + }, + Spec: crdsv1.ClusterManagedServiceSpec{ + TargetNamespace: d.getClusterManagedServiceTargetNamespace(args.DestinationMsvcName), + MSVCSpec: sourceMsvc.Spec.MSVCSpec, + }, + }, + AccountName: ctx.AccountName, + ClusterName: args.ClusterName, + SyncedOutputSecretRef: sourceMsvc.SyncedOutputSecretRef, + ResourceMetadata: common.ResourceMetadata{ + DisplayName: args.DisplayName, + CreatedBy: common.CreatedOrUpdatedBy{ + UserId: ctx.UserId, + UserName: ctx.UserName, + UserEmail: ctx.UserEmail, + }, + LastUpdatedBy: common.CreatedOrUpdatedBy{ + UserId: ctx.UserId, + UserName: ctx.UserName, + UserEmail: ctx.UserEmail, + }, + }, + SyncStatus: t.GenSyncStatus(t.SyncActionApply, 0), + } + + if err := d.k8sClient.ValidateObject(ctx, &destMsvc.ClusterManagedService); err != nil { + return nil, errors.NewE(err) + } + + destMsvc, err = d.clusterManagedServiceRepo.Create(ctx, destMsvc) + if err != nil { + return nil, errors.NewE(err) + } + + if err := d.applyClusterManagedService(ctx, destMsvc); err != nil { + return nil, errors.NewE(err) + } + + return destMsvc, nil +} + func (d *domain) UpdateClusterManagedService(ctx InfraContext, cmsvc entities.ClusterManagedService) (*entities.ClusterManagedService, error) { if err := d.canPerformActionInAccount(ctx, iamT.UpdateClusterManagedService); err != nil { return nil, errors.NewE(err)