diff --git a/.local.env b/.local.env index b6826e715..c902bdc49 100644 --- a/.local.env +++ b/.local.env @@ -13,4 +13,7 @@ GITNESS_SSH_HOST=localhost GITNESS_SSH_PORT=2222 GITNESS_REGISTRY_STORAGE_TYPE=filesystem -GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY=/tmp \ No newline at end of file +GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY=/tmp + +#GITNESS_DATABASE_DRIVER=postgres +#GITNESS_DATABASE_DATASOURCE=postgres://postgres:postgres@localhost:5432/gitness?sslmode=disable \ No newline at end of file diff --git a/app/api/controller/gitspace/create.go b/app/api/controller/gitspace/create.go index 5308560b1..2e3fa2b9d 100644 --- a/app/api/controller/gitspace/create.go +++ b/app/api/controller/gitspace/create.go @@ -45,19 +45,20 @@ var ( // CreateInput is the input used for create operations. type CreateInput struct { - Identifier string `json:"identifier"` - Name string `json:"name"` - SpaceRef string `json:"space_ref"` // Ref of the parent space - IDE enum.IDEType `json:"ide"` - ResourceIdentifier string `json:"resource_identifier"` - ResourceSpaceRef string `json:"resource_space_ref"` - CodeRepoURL string `json:"code_repo_url"` - CodeRepoType enum.GitspaceCodeRepoType `json:"code_repo_type"` - CodeRepoRef *string `json:"code_repo_ref"` - Branch string `json:"branch"` - DevcontainerPath *string `json:"devcontainer_path"` - Metadata map[string]string `json:"metadata"` - SSHTokenIdentifier string `json:"ssh_token_identifier"` + Identifier string `json:"identifier"` + Name string `json:"name"` + SpaceRef string `json:"space_ref"` // Ref of the parent space + IDE enum.IDEType `json:"ide"` + InfraProviderConfigIdentifier string `json:"infra_provider_config_identifier"` + ResourceIdentifier string `json:"resource_identifier"` + ResourceSpaceRef string `json:"resource_space_ref"` + CodeRepoURL string `json:"code_repo_url"` + CodeRepoType enum.GitspaceCodeRepoType `json:"code_repo_type"` + CodeRepoRef *string `json:"code_repo_ref"` + Branch string `json:"branch"` + DevcontainerPath *string `json:"devcontainer_path"` + Metadata map[string]string `json:"metadata"` + SSHTokenIdentifier string `json:"ssh_token_identifier"` } // Create creates a new gitspace. @@ -132,7 +133,15 @@ func (c *Controller) Create( enum.PermissionInfraProviderAccess); err != nil { return nil, err } - infraProviderResource, err := c.createOrFindInfraProviderResource(ctx, resourceSpace, resourceIdentifier, now) + + // TODO: Temp fix to ensure the gitspace creation doesnt fail. Once the FE starts sending this field in the + // request, remove this. + if in.InfraProviderConfigIdentifier == "" { + in.InfraProviderConfigIdentifier = defaultResourceIdentifier + } + + infraProviderResource, err := c.createOrFindInfraProviderResource(ctx, resourceSpace, resourceIdentifier, + in.InfraProviderConfigIdentifier, now) if err != nil { return nil, err } @@ -183,13 +192,16 @@ func (c *Controller) createOrFindInfraProviderResource( ctx context.Context, resourceSpace *types.SpaceCore, resourceIdentifier string, + infraProviderConfigIdentifier string, now int64, ) (*types.InfraProviderResource, error) { var resource *types.InfraProviderResource var err error - resource, err = c.infraProviderSvc.FindResourceByIdentifier(ctx, resourceSpace.ID, resourceIdentifier) - if err != nil && errors.Is(err, store.ErrResourceNotFound) && resourceIdentifier == defaultResourceIdentifier { + resource, err = c.infraProviderSvc.FindResourceByConfigAndIdentifier(ctx, resourceSpace.ID, + infraProviderConfigIdentifier, resourceIdentifier) + if ((err != nil && errors.Is(err, store.ErrResourceNotFound)) || resource == nil) && + resourceIdentifier == defaultResourceIdentifier { resource, err = c.autoCreateDefaultResource(ctx, resourceSpace, now) if err != nil { return nil, err @@ -237,12 +249,13 @@ func (c *Controller) autoCreateDefaultResource( } defaultDockerConfig.Resources = []types.InfraProviderResource{defaultResource} - err = c.infraProviderSvc.CreateInfraProvider(ctx, defaultDockerConfig) + err = c.infraProviderSvc.CreateConfigAndResources(ctx, defaultDockerConfig) if err != nil { return nil, fmt.Errorf("could not auto-create the infra provider: %w", err) } - resource, err := c.infraProviderSvc.FindResourceByIdentifier(ctx, rootSpace.ID, defaultResourceIdentifier) + resource, err := c.infraProviderSvc.FindResourceByConfigAndIdentifier(ctx, rootSpace.ID, + defaultDockerConfig.Identifier, defaultResourceIdentifier) if err != nil { return nil, fmt.Errorf("could not find infra provider resource : %q %w", defaultResourceIdentifier, err) } diff --git a/app/api/controller/infraprovider/controller.go b/app/api/controller/infraprovider/controller.go index 3b8b9b676..8fbe8b49c 100644 --- a/app/api/controller/infraprovider/controller.go +++ b/app/api/controller/infraprovider/controller.go @@ -18,8 +18,44 @@ import ( "github.com/harness/gitness/app/auth/authz" "github.com/harness/gitness/app/services/infraprovider" "github.com/harness/gitness/app/services/refcache" + "github.com/harness/gitness/types/enum" ) +const NoResourceIdentifier = "" + +type ConfigInput struct { + Identifier string `json:"identifier" yaml:"identifier"` + SpaceRef string `json:"space_ref" yaml:"space_ref"` + Name string `json:"name" yaml:"name"` + Type enum.InfraProviderType `json:"type" yaml:"type"` + Metadata map[string]any `json:"metadata" yaml:"metadata"` +} + +type ResourceInput struct { + Identifier string `json:"identifier" yaml:"identifier"` + Name string `json:"name" yaml:"name"` + InfraProviderType enum.InfraProviderType `json:"infra_provider_type" yaml:"infra_provider_type"` + CPU *string `json:"cpu" yaml:"cpu"` + Memory *string `json:"memory" yaml:"memory"` + Disk *string `json:"disk" yaml:"disk"` + Network *string `json:"network" yaml:"network"` + Region []string `json:"region" yaml:"region"` + Metadata map[string]string `json:"metadata" yaml:"metadata"` + GatewayHost *string `json:"gateway_host" yaml:"gateway_host"` + GatewayPort *string `json:"gateway_port" yaml:"gateway_port"` +} + +type AutoCreateInput struct { + Config ConfigInput `json:"config" yaml:"config"` + Resources []ResourceInput `json:"resources" yaml:"resources"` +} + +type TemplateInput struct { + Identifier string `json:"identifier" yaml:"identifier"` + Description string `json:"description" yaml:"description"` + Data string `json:"data" yaml:"data"` +} + type Controller struct { authorizer authz.Authorizer spaceFinder refcache.SpaceFinder diff --git a/app/api/controller/infraprovider/create.go b/app/api/controller/infraprovider/create.go deleted file mode 100644 index 940d5f622..000000000 --- a/app/api/controller/infraprovider/create.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2023 Harness, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package infraprovider - -import ( - "context" - "fmt" - "time" - - apiauth "github.com/harness/gitness/app/api/auth" - "github.com/harness/gitness/app/auth" - "github.com/harness/gitness/types" - "github.com/harness/gitness/types/check" - "github.com/harness/gitness/types/enum" -) - -const NoResourceIdentifier = "" - -type CreateInput struct { - Identifier string `json:"identifier" yaml:"identifier"` - SpaceRef string `json:"space_ref" yaml:"space_ref"` // Ref of the parent space - Name string `json:"name" yaml:"name"` - Type enum.InfraProviderType `json:"type" yaml:"type"` - Metadata map[string]any `json:"metadata" yaml:"metadata"` - Resources []ResourceInput `json:"resources" yaml:"resources"` -} - -type ResourceInput struct { - Identifier string `json:"identifier" yaml:"identifier"` - Name string `json:"name" yaml:"name"` - InfraProviderType enum.InfraProviderType `json:"infra_provider_type" yaml:"infra_provider_type"` - CPU *string `json:"cpu" yaml:"cpu"` - Memory *string `json:"memory" yaml:"memory"` - Disk *string `json:"disk" yaml:"disk"` - Network *string `json:"network" yaml:"network"` - Region []string `json:"region" yaml:"region"` - Metadata map[string]string `json:"metadata" yaml:"metadata"` - GatewayHost *string `json:"gateway_host" yaml:"gateway_host"` - GatewayPort *string `json:"gateway_port" yaml:"gateway_port"` -} - -type TemplateInput struct { - Identifier string `json:"identifier" yaml:"identifier"` - Description string `json:"description" yaml:"description"` - Data string `json:"data" yaml:"data"` -} - -// Create creates a new infra provider. -func (c *Controller) Create( - ctx context.Context, - session auth.Session, - in CreateInput, -) (*types.InfraProviderConfig, error) { - if err := c.sanitizeCreateInput(in); err != nil { - return nil, fmt.Errorf("invalid input: %w", err) - } - parentSpace, err := c.spaceFinder.FindByRef(ctx, in.SpaceRef) - if err != nil { - return nil, fmt.Errorf("failed to find parent by ref %q : %w", in.SpaceRef, err) - } - if err = apiauth.CheckInfraProvider( - ctx, - c.authorizer, - &session, - parentSpace.Path, - NoResourceIdentifier, - enum.PermissionInfraProviderEdit); err != nil { - return nil, err - } - now := time.Now().UnixMilli() - infraProviderConfig := c.MapToInfraProviderConfig(in, parentSpace, now) - err = c.infraproviderSvc.CreateInfraProvider(ctx, infraProviderConfig) - if err != nil { - return nil, fmt.Errorf("unable to create the infraprovider: %q %w", infraProviderConfig.Identifier, err) - } - return infraProviderConfig, nil -} - -func (c *Controller) MapToInfraProviderConfig( - in CreateInput, - parentSpace *types.SpaceCore, - now int64, -) *types.InfraProviderConfig { - infraProviderConfig := &types.InfraProviderConfig{ - Identifier: in.Identifier, - Name: in.Name, - SpaceID: parentSpace.ID, - SpacePath: parentSpace.Path, - Type: in.Type, - Created: now, - Updated: now, - Metadata: in.Metadata, - } - infraProviderConfig.Resources = mapToResourceEntity(in.Resources, parentSpace, now) - return infraProviderConfig -} - -func (c *Controller) sanitizeCreateInput(in CreateInput) error { - if err := check.Identifier(in.Identifier); err != nil { - return err - } - return nil -} diff --git a/app/api/controller/infraprovider/create_config.go b/app/api/controller/infraprovider/create_config.go new file mode 100644 index 000000000..000c459da --- /dev/null +++ b/app/api/controller/infraprovider/create_config.go @@ -0,0 +1,82 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infraprovider + +import ( + "context" + "fmt" + "time" + + apiauth "github.com/harness/gitness/app/api/auth" + "github.com/harness/gitness/app/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/check" + "github.com/harness/gitness/types/enum" +) + +// CreateConfig creates a new infra provider config. +func (c *Controller) CreateConfig( + ctx context.Context, + session auth.Session, + in ConfigInput, +) (*types.InfraProviderConfig, error) { + if err := c.sanitizeCreateInput(in); err != nil { + return nil, fmt.Errorf("invalid input: %w", err) + } + parentSpace, err := c.spaceFinder.FindByRef(ctx, in.SpaceRef) + if err != nil { + return nil, fmt.Errorf("failed to find parent by ref %q : %w", in.SpaceRef, err) + } + if err = apiauth.CheckInfraProvider( + ctx, + c.authorizer, + &session, + parentSpace.Path, + NoResourceIdentifier, + enum.PermissionInfraProviderEdit); err != nil { + return nil, err + } + now := time.Now().UnixMilli() + infraProviderConfig := c.MapToInfraProviderConfig(in, parentSpace, now) + err = c.infraproviderSvc.CreateConfig(ctx, infraProviderConfig) + if err != nil { + return nil, fmt.Errorf("unable to create the infraprovider: %q %w", infraProviderConfig.Identifier, err) + } + return infraProviderConfig, nil +} + +func (c *Controller) MapToInfraProviderConfig( + in ConfigInput, + space *types.SpaceCore, + now int64, +) *types.InfraProviderConfig { + return &types.InfraProviderConfig{ + Identifier: in.Identifier, + Name: in.Name, + SpaceID: space.ID, + SpacePath: space.Path, + Type: in.Type, + Created: now, + Updated: now, + Metadata: in.Metadata, + } +} + +func (c *Controller) sanitizeCreateInput(in ConfigInput) error { + if err := check.Identifier(in.Identifier); err != nil { + return err + } + return nil +} diff --git a/app/api/controller/infraprovider/create_resources.go b/app/api/controller/infraprovider/create_resources.go index a9670f2d4..02ce46700 100644 --- a/app/api/controller/infraprovider/create_resources.go +++ b/app/api/controller/infraprovider/create_resources.go @@ -83,7 +83,7 @@ func (c *Controller) CreateResources( return nil, fmt.Errorf("invalid input: %w", err) } now := time.Now().UnixMilli() - parentSpace, err := c.spaceFinder.FindByRef(ctx, spaceRef) + space, err := c.spaceFinder.FindByRef(ctx, spaceRef) if err != nil { return nil, fmt.Errorf("failed to find parent by ref: %w", err) } @@ -91,31 +91,35 @@ func (c *Controller) CreateResources( ctx, c.authorizer, &session, - parentSpace.Path, + space.Path, NoResourceIdentifier, enum.PermissionInfraProviderEdit); err != nil { return nil, err } - infraProviderConfig, err := c.infraproviderSvc.Find(ctx, parentSpace, configIdentifier) + infraProviderConfig, err := c.infraproviderSvc.Find(ctx, space, configIdentifier) if err != nil { - return nil, fmt.Errorf("failed to find infraprovider config by ref: %q %w", infraProviderConfig.Identifier, err) + return nil, fmt.Errorf("failed to find infraprovider config by ref: %q %w", configIdentifier, err) } - resources := mapToResourceEntity(in, parentSpace, now) - err = c.infraproviderSvc.CreateResources(ctx, resources, infraProviderConfig.ID, infraProviderConfig.Identifier) + resources := c.MapToResourceEntity(in, space, now) + err = c.infraproviderSvc.CreateResources(ctx, space.ID, resources, infraProviderConfig.ID) if err != nil { return nil, err } return resources, nil } -func mapToResourceEntity(in []ResourceInput, parentSpace *types.SpaceCore, now int64) []types.InfraProviderResource { +func (c *Controller) MapToResourceEntity( + in []ResourceInput, + space *types.SpaceCore, + now int64, +) []types.InfraProviderResource { var resources []types.InfraProviderResource for _, res := range in { infraProviderResource := types.InfraProviderResource{ UID: res.Identifier, InfraProviderType: res.InfraProviderType, Name: res.Name, - SpaceID: parentSpace.ID, + SpaceID: space.ID, CPU: res.CPU, Memory: res.Memory, Disk: res.Disk, @@ -124,7 +128,7 @@ func mapToResourceEntity(in []ResourceInput, parentSpace *types.SpaceCore, now i Metadata: res.Metadata, Created: now, Updated: now, - SpacePath: parentSpace.Path, + SpacePath: space.Path, } resources = append(resources, infraProviderResource) } diff --git a/app/api/handler/infraprovider/create.go b/app/api/handler/infraprovider/create.go index 84cac98da..6f4ef142c 100644 --- a/app/api/handler/infraprovider/create.go +++ b/app/api/handler/infraprovider/create.go @@ -29,14 +29,14 @@ func HandleCreateConfig(infraProviderCtrl *infraprovider.Controller) http.Handle ctx := r.Context() session, _ := request.AuthSessionFrom(ctx) - in := new(infraprovider.CreateInput) + in := new(infraprovider.ConfigInput) err := json.NewDecoder(r.Body).Decode(in) if err != nil { render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err) return } - infraProviderConfig, err := infraProviderCtrl.Create(ctx, *session, *in) + infraProviderConfig, err := infraProviderCtrl.CreateConfig(ctx, *session, *in) if err != nil { render.TranslatedUserError(ctx, w, err) return diff --git a/app/api/openapi/infra_provider.go b/app/api/openapi/infra_provider.go index 30272b8cb..b67a0731d 100644 --- a/app/api/openapi/infra_provider.go +++ b/app/api/openapi/infra_provider.go @@ -25,7 +25,7 @@ import ( ) type createInfraProviderConfigRequest struct { - infraprovider.CreateInput + infraprovider.ConfigInput } type getInfraProviderRequest struct { diff --git a/app/services/infraprovider/create.go b/app/services/infraprovider/create.go deleted file mode 100644 index eedef2b38..000000000 --- a/app/services/infraprovider/create.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2023 Harness, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package infraprovider - -import ( - "context" - "fmt" - "net/http" - - "github.com/harness/gitness/app/api/usererror" - "github.com/harness/gitness/infraprovider" - "github.com/harness/gitness/types" - - "github.com/rs/zerolog/log" -) - -func (c *Service) CreateTemplate( - ctx context.Context, - template *types.InfraProviderTemplate, -) error { - return c.infraProviderTemplateStore.Create(ctx, template) -} - -func (c *Service) CreateInfraProvider( - ctx context.Context, - infraProviderConfig *types.InfraProviderConfig, -) error { - err := c.tx.WithTx(ctx, func(ctx context.Context) error { - err := c.areNewConfigsAllowed(ctx, infraProviderConfig) - if err != nil { - return err - } - - configID, err := c.createConfig(ctx, infraProviderConfig) - if err != nil { - return fmt.Errorf("could not create the config: %q %w", infraProviderConfig.Identifier, err) - } - err = c.createResources(ctx, infraProviderConfig.Resources, configID, infraProviderConfig.Identifier) - if err != nil { - return fmt.Errorf("could not create the resources: %v %w", infraProviderConfig.Resources, err) - } - return nil - }) - if err != nil { - return fmt.Errorf("failed to complete txn for the infraprovider %w", err) - } - return nil -} - -func (c *Service) validateConfigAndResources(infraProviderConfig *types.InfraProviderConfig) error { - infraProvider, err := c.infraProviderFactory.GetInfraProvider(infraProviderConfig.Type) - if err != nil { - return fmt.Errorf("failed to fetch infra provider for type %s: %w", infraProviderConfig.Type, err) - } - - err = infraProvider.ValidateConfigAndResources(infraProviderConfig) - if err != nil { - return err - } - - return nil -} - -func (c *Service) areNewConfigsAllowed(ctx context.Context, infraProviderConfig *types.InfraProviderConfig) error { - existingConfigs, err := c.fetchExistingConfigs(ctx, infraProviderConfig) - if err != nil { - return err - } - if len(existingConfigs) > 0 { - return usererror.NewWithPayload(http.StatusForbidden, fmt.Sprintf( - "%d infra configs for provider %s exist for this account. Only 1 is allowed", - len(existingConfigs), infraProviderConfig.Type)) - } - return nil -} - -func (c *Service) fetchExistingConfigs( - ctx context.Context, - infraProviderConfig *types.InfraProviderConfig, -) ([]*types.InfraProviderConfig, error) { - existingConfigs, err := c.infraProviderConfigStore.FindByType(ctx, infraProviderConfig.SpaceID, - infraProviderConfig.Type) - if err != nil { - return nil, fmt.Errorf("failed to find existing infraprovider config for type %s & space %d: %w", - infraProviderConfig.Type, infraProviderConfig.SpaceID, err) - } - return existingConfigs, nil -} - -func (c *Service) createConfig(ctx context.Context, infraProviderConfig *types.InfraProviderConfig) (int64, error) { - err := c.validateConfigAndResources(infraProviderConfig) - if err != nil { - return 0, err - } - - err = c.infraProviderConfigStore.Create(ctx, infraProviderConfig) - if err != nil { - return 0, fmt.Errorf("failed to create infraprovider config for %s: %w", infraProviderConfig.Identifier, err) - } - - newInfraProviderConfig, err := c.infraProviderConfigStore.FindByIdentifier(ctx, infraProviderConfig.SpaceID, - infraProviderConfig.Identifier) - if err != nil { - return 0, fmt.Errorf("failed to find newly created infraprovider config %s in space %d: %w", - infraProviderConfig.Identifier, infraProviderConfig.SpaceID, err) - } - return newInfraProviderConfig.ID, nil -} - -func (c *Service) CreateResources( - ctx context.Context, - resources []types.InfraProviderResource, - configID int64, - infraProviderConfigIdentifier string, -) error { - err := c.tx.WithTx(ctx, func(ctx context.Context) error { - return c.createResources(ctx, resources, configID, infraProviderConfigIdentifier) - }) - if err != nil { - return fmt.Errorf("failed to complete create txn for the infraprovider resource %w", err) - } - return nil -} - -func (c *Service) createResources( - ctx context.Context, - resources []types.InfraProviderResource, - configID int64, - infraProviderConfigIdentifier string, -) error { - for idx := range resources { - resource := &resources[idx] - resource.InfraProviderConfigID = configID - resource.InfraProviderConfigIdentifier = infraProviderConfigIdentifier - - err := c.validate(ctx, resource) - if err != nil { - return err - } - - err = c.infraProviderResourceStore.Create(ctx, resource) - if err != nil { - return fmt.Errorf("failed to create infraprovider resource for : %q %w", resource.UID, err) - } - } - return nil -} - -func (c *Service) validate(ctx context.Context, resource *types.InfraProviderResource) error { - infraProvider, err := c.infraProviderFactory.GetInfraProvider(resource.InfraProviderType) - if err != nil { - return fmt.Errorf("failed to fetch infra impl for type : %q %w", resource.InfraProviderType, err) - } - - if len(infraProvider.TemplateParams()) > 0 { - err = c.validateTemplates(ctx, infraProvider, *resource) - if err != nil { - return err - } - } - - err = c.validateResourceParams(infraProvider, *resource) - if err != nil { - return err - } - - return err -} - -func (c *Service) validateTemplates( - ctx context.Context, - infraProvider infraprovider.InfraProvider, - res types.InfraProviderResource, -) error { - templateParams := infraProvider.TemplateParams() - for _, param := range templateParams { - key := param.Name - if res.Metadata[key] != "" { - templateIdentifier := res.Metadata[key] - _, err := c.infraProviderTemplateStore.FindByIdentifier( - ctx, res.SpaceID, templateIdentifier) - if err != nil { - log.Warn().Msgf("unable to get template params for ID : %s", - res.Metadata[key]) - } - } - } - return nil -} - -func (c *Service) validateResourceParams( - infraProvider infraprovider.InfraProvider, - res types.InfraProviderResource, -) error { - infraResourceParams := make([]types.InfraProviderParameter, 0) - for key, value := range res.Metadata { - infraResourceParams = append(infraResourceParams, types.InfraProviderParameter{ - Name: key, - Value: value, - }) - } - return infraProvider.ValidateParams(infraResourceParams) -} diff --git a/app/services/infraprovider/create_config.go b/app/services/infraprovider/create_config.go new file mode 100644 index 000000000..62c3e6c3e --- /dev/null +++ b/app/services/infraprovider/create_config.go @@ -0,0 +1,107 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infraprovider + +import ( + "context" + "fmt" + "net/http" + + "github.com/harness/gitness/app/api/usererror" + "github.com/harness/gitness/types" +) + +func (c *Service) CreateConfig( + ctx context.Context, + infraProviderConfig *types.InfraProviderConfig, +) error { + err := c.tx.WithTx(ctx, func(ctx context.Context) error { + err := c.areNewConfigsAllowed(ctx, infraProviderConfig) + if err != nil { + return err + } + + _, err = c.createConfig(ctx, infraProviderConfig) + if err != nil { + return fmt.Errorf("could not create the config: %q %w", infraProviderConfig.Identifier, err) + } + + return nil + }) + if err != nil { + return fmt.Errorf("failed to complete txn for the infraprovider %w", err) + } + return nil +} + +func (c *Service) areNewConfigsAllowed(ctx context.Context, infraProviderConfig *types.InfraProviderConfig) error { + existingConfigs, err := c.fetchExistingConfigs(ctx, infraProviderConfig) + if err != nil { + return err + } + if len(existingConfigs) > 0 { + return usererror.NewWithPayload(http.StatusForbidden, fmt.Sprintf( + "%d infra configs for provider %s exist for this account. Only 1 is allowed", + len(existingConfigs), infraProviderConfig.Type)) + } + return nil +} + +func (c *Service) fetchExistingConfigs( + ctx context.Context, + infraProviderConfig *types.InfraProviderConfig, +) ([]*types.InfraProviderConfig, error) { + existingConfigs, err := c.infraProviderConfigStore.FindByType(ctx, infraProviderConfig.SpaceID, + infraProviderConfig.Type) + if err != nil { + return nil, fmt.Errorf("failed to find existing infraprovider config for type %s & space %d: %w", + infraProviderConfig.Type, infraProviderConfig.SpaceID, err) + } + return existingConfigs, nil +} + +func (c *Service) createConfig(ctx context.Context, infraProviderConfig *types.InfraProviderConfig) (int64, error) { + err := c.validateConfig(infraProviderConfig) + if err != nil { + return 0, err + } + + err = c.infraProviderConfigStore.Create(ctx, infraProviderConfig) + if err != nil { + return 0, fmt.Errorf("failed to create infraprovider config for %s: %w", infraProviderConfig.Identifier, err) + } + + newInfraProviderConfig, err := c.infraProviderConfigStore.FindByIdentifier(ctx, infraProviderConfig.SpaceID, + infraProviderConfig.Identifier) + if err != nil { + return 0, fmt.Errorf("failed to find newly created infraprovider config %s in space %d: %w", + infraProviderConfig.Identifier, infraProviderConfig.SpaceID, err) + } + return newInfraProviderConfig.ID, nil +} + +func (c *Service) validateConfig(infraProviderConfig *types.InfraProviderConfig) error { + infraProvider, err := c.infraProviderFactory.GetInfraProvider(infraProviderConfig.Type) + if err != nil { + return fmt.Errorf("failed to fetch infra provider for type %s: %w", infraProviderConfig.Type, err) + } + + err = infraProvider.ValidateConfig(infraProviderConfig) + if err != nil { + return err + } + + return nil +} diff --git a/app/services/infraprovider/create_config_and_resources.go b/app/services/infraprovider/create_config_and_resources.go new file mode 100644 index 000000000..7741f906d --- /dev/null +++ b/app/services/infraprovider/create_config_and_resources.go @@ -0,0 +1,48 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infraprovider + +import ( + "context" + "fmt" + + "github.com/harness/gitness/types" +) + +func (c *Service) CreateConfigAndResources( + ctx context.Context, + infraProviderConfig *types.InfraProviderConfig, +) error { + err := c.tx.WithTx(ctx, func(ctx context.Context) error { + err := c.areNewConfigsAllowed(ctx, infraProviderConfig) + if err != nil { + return err + } + + configID, err := c.createConfig(ctx, infraProviderConfig) + if err != nil { + return fmt.Errorf("could not create the config: %q %w", infraProviderConfig.Identifier, err) + } + err = c.createMissingResources(ctx, infraProviderConfig.Resources, configID, infraProviderConfig.SpaceID) + if err != nil { + return fmt.Errorf("could not create the resources: %v %w", infraProviderConfig.Resources, err) + } + return nil + }) + if err != nil { + return fmt.Errorf("failed to complete txn for the infraprovider %w", err) + } + return nil +} diff --git a/app/services/infraprovider/create_resource.go b/app/services/infraprovider/create_resource.go new file mode 100644 index 000000000..01fafa03c --- /dev/null +++ b/app/services/infraprovider/create_resource.go @@ -0,0 +1,102 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infraprovider + +import ( + "context" + "errors" + "fmt" + + "github.com/harness/gitness/infraprovider" + "github.com/harness/gitness/store" + "github.com/harness/gitness/types" + + "github.com/rs/zerolog/log" +) + +func (c *Service) CreateResources( + ctx context.Context, + spaceID int64, + resources []types.InfraProviderResource, + configID int64, +) error { + err := c.tx.WithTx(ctx, func(ctx context.Context) error { + return c.createMissingResources(ctx, resources, configID, spaceID) + }) + if err != nil { + return fmt.Errorf("failed to complete create txn for the infraprovider resource %w", err) + } + return nil +} + +func (c *Service) createMissingResources( + ctx context.Context, + resources []types.InfraProviderResource, + configID int64, + spaceID int64, +) error { + for idx := range resources { + resource := &resources[idx] + resource.InfraProviderConfigID = configID + resource.SpaceID = spaceID + if err := c.validateResource(ctx, resource); err != nil { + return err + } + existingResource, err := c.infraProviderResourceStore.FindByConfigAndIdentifier(ctx, resource.SpaceID, + configID, resource.UID) + if (err != nil && errors.Is(err, store.ErrResourceNotFound)) || existingResource == nil { + if err = c.infraProviderResourceStore.Create(ctx, resource); err != nil { + return fmt.Errorf("failed to create infraprovider resource for %s: %w", resource.UID, err) + } + log.Info().Msgf("created new resource %s/%s", resource.InfraProviderConfigIdentifier, resource.UID) + } + } + return nil +} + +func (c *Service) validateResource(ctx context.Context, resource *types.InfraProviderResource) error { + infraProvider, err := c.infraProviderFactory.GetInfraProvider(resource.InfraProviderType) + if err != nil { + return fmt.Errorf("failed to fetch infra impl for type : %q %w", resource.InfraProviderType, err) + } + + if len(infraProvider.TemplateParams()) > 0 { + err = c.validateTemplates(ctx, infraProvider, *resource) + if err != nil { + return err + } + } + + err = c.validateResourceParams(infraProvider, *resource) + if err != nil { + return err + } + + return err +} + +func (c *Service) validateResourceParams( + infraProvider infraprovider.InfraProvider, + res types.InfraProviderResource, +) error { + infraResourceParams := make([]types.InfraProviderParameter, 0) + for key, value := range res.Metadata { + infraResourceParams = append(infraResourceParams, types.InfraProviderParameter{ + Name: key, + Value: value, + }) + } + return infraProvider.ValidateParams(infraResourceParams) +} diff --git a/app/services/infraprovider/create_template.go b/app/services/infraprovider/create_template.go new file mode 100644 index 000000000..0476e8aaa --- /dev/null +++ b/app/services/infraprovider/create_template.go @@ -0,0 +1,52 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infraprovider + +import ( + "context" + + "github.com/harness/gitness/infraprovider" + "github.com/harness/gitness/types" + + "github.com/rs/zerolog/log" +) + +func (c *Service) CreateTemplate( + ctx context.Context, + template *types.InfraProviderTemplate, +) error { + return c.infraProviderTemplateStore.Create(ctx, template) +} + +func (c *Service) validateTemplates( + ctx context.Context, + infraProvider infraprovider.InfraProvider, + res types.InfraProviderResource, +) error { + templateParams := infraProvider.TemplateParams() + for _, param := range templateParams { + key := param.Name + if res.Metadata[key] != "" { + templateIdentifier := res.Metadata[key] + _, err := c.infraProviderTemplateStore.FindByIdentifier( + ctx, res.SpaceID, templateIdentifier) + if err != nil { + log.Warn().Msgf("unable to get template params for ID : %s", + res.Metadata[key]) + } + } + } + return nil +} diff --git a/app/services/infraprovider/delete_resource.go b/app/services/infraprovider/delete_resource.go new file mode 100644 index 000000000..942700d91 --- /dev/null +++ b/app/services/infraprovider/delete_resource.go @@ -0,0 +1,66 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infraprovider + +import ( + "context" + "fmt" + "net/http" + + "github.com/harness/gitness/app/api/usererror" +) + +func (c *Service) DeleteResource( + ctx context.Context, + spaceID int64, + infraProviderConfigIdentifier string, + identifier string, +) error { + err := c.tx.WithTx(ctx, func(ctx context.Context) error { + infraProviderConfig, err := c.infraProviderConfigStore.FindByIdentifier(ctx, spaceID, + infraProviderConfigIdentifier) + if err != nil { + return fmt.Errorf("failed to find infra config %s for deleting resource: %w", + infraProviderConfigIdentifier, err) + } + + infraProviderResource, err := c.infraProviderResourceStore.FindByConfigAndIdentifier(ctx, spaceID, + infraProviderConfig.ID, identifier) + if err != nil { + return fmt.Errorf("failed to find infra resource %s with config %s for deleting resource: %w", + identifier, infraProviderConfigIdentifier, err) + } + + activeGitspaces, err := c.gitspaceConfigStore.ListActiveConfigsForInfraProviderResource(ctx, + infraProviderResource.ID) + if err != nil { + return fmt.Errorf("failed to list active configs for infra resource %s for deleting resource: %w", + identifier, err) + } + + if len(activeGitspaces) > 0 { + return usererror.NewWithPayload(http.StatusForbidden, fmt.Sprintf("There are %d active configs for "+ + "infra resource %s, expected 0", len(activeGitspaces), identifier)) + } + + return c.infraProviderResourceStore.Delete(ctx, infraProviderResource.ID) + }) + + if err != nil { + return fmt.Errorf("failed to complete txn for deleting the infra resource %s: %w", identifier, err) + } + + return nil +} diff --git a/app/services/infraprovider/find.go b/app/services/infraprovider/find.go new file mode 100644 index 000000000..f45767454 --- /dev/null +++ b/app/services/infraprovider/find.go @@ -0,0 +1,81 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infraprovider + +import ( + "context" + "fmt" + "slices" + + "github.com/harness/gitness/types" +) + +func (c *Service) Find( + ctx context.Context, + space *types.SpaceCore, + identifier string, +) (*types.InfraProviderConfig, error) { + infraProviderConfig, err := c.infraProviderConfigStore.FindByIdentifier(ctx, space.ID, identifier) + if err != nil { + return nil, fmt.Errorf("failed to find infraprovider config: %q %w", identifier, err) + } + resources, err := c.infraProviderResourceStore.List(ctx, infraProviderConfig.ID, types.ListQueryFilter{}) + if err != nil { + return nil, fmt.Errorf("failed to find infraprovider resources for config: %q %w", + infraProviderConfig.Identifier, err) + } + infraProviderConfig.SpacePath = space.Path + if len(resources) > 0 { + providerResources := make([]types.InfraProviderResource, len(resources)) + for i, resource := range resources { + if resource != nil { + providerResources[i] = *resource + providerResources[i].SpacePath = space.Path + } + } + slices.SortFunc(providerResources, types.CompareInfraProviderResource) + infraProviderConfig.Resources = providerResources + } + return infraProviderConfig, nil +} + +func (c *Service) FindTemplate( + ctx context.Context, + space *types.SpaceCore, + identifier string, +) (*types.InfraProviderTemplate, error) { + infraProviderTemplate, err := c.infraProviderTemplateStore.FindByIdentifier(ctx, space.ID, identifier) + if err != nil { + return nil, fmt.Errorf("failed to find infraprovider template: %q %w", identifier, err) + } + return infraProviderTemplate, nil +} + +func (c *Service) FindResourceByConfigAndIdentifier( + ctx context.Context, + spaceID int64, + infraProviderConfigIdentifier string, + identifier string, +) (*types.InfraProviderResource, error) { + infraProviderConfig, err := c.infraProviderConfigStore.FindByIdentifier(ctx, spaceID, infraProviderConfigIdentifier) + if err != nil { + return nil, fmt.Errorf("failed to find infraprovider config %s: %w", infraProviderConfigIdentifier, err) + } + return c.infraProviderResourceStore.FindByConfigAndIdentifier(ctx, spaceID, infraProviderConfig.ID, identifier) +} + +func (c *Service) FindResource(ctx context.Context, id int64) (*types.InfraProviderResource, error) { + return c.infraProviderResourceStore.Find(ctx, id) +} diff --git a/app/services/infraprovider/infraprovider.go b/app/services/infraprovider/infraprovider.go index 055cc2ca9..cf0478a33 100644 --- a/app/services/infraprovider/infraprovider.go +++ b/app/services/infraprovider/infraprovider.go @@ -15,19 +15,15 @@ package infraprovider import ( - "context" - "fmt" - "slices" - "github.com/harness/gitness/app/services/refcache" "github.com/harness/gitness/app/store" "github.com/harness/gitness/infraprovider" "github.com/harness/gitness/store/database/dbtx" - "github.com/harness/gitness/types" ) func NewService( tx dbtx.Transactor, + gitspaceConfigStore store.GitspaceConfigStore, resourceStore store.InfraProviderResourceStore, configStore store.InfraProviderConfigStore, templateStore store.InfraProviderTemplateStore, @@ -41,66 +37,16 @@ func NewService( infraProviderTemplateStore: templateStore, infraProviderFactory: factory, spaceFinder: spaceFinder, + gitspaceConfigStore: gitspaceConfigStore, } } type Service struct { tx dbtx.Transactor + gitspaceConfigStore store.GitspaceConfigStore infraProviderResourceStore store.InfraProviderResourceStore infraProviderConfigStore store.InfraProviderConfigStore infraProviderTemplateStore store.InfraProviderTemplateStore infraProviderFactory infraprovider.Factory spaceFinder refcache.SpaceFinder } - -func (c *Service) Find( - ctx context.Context, - space *types.SpaceCore, - identifier string, -) (*types.InfraProviderConfig, error) { - infraProviderConfig, err := c.infraProviderConfigStore.FindByIdentifier(ctx, space.ID, identifier) - if err != nil { - return nil, fmt.Errorf("failed to find infraprovider config: %q %w", identifier, err) - } - resources, err := c.infraProviderResourceStore.List(ctx, infraProviderConfig.ID, types.ListQueryFilter{}) - if err != nil { - return nil, fmt.Errorf("failed to find infraprovider resources for config: %q %w", - infraProviderConfig.Identifier, err) - } - infraProviderConfig.SpacePath = space.Path - if len(resources) > 0 { - providerResources := make([]types.InfraProviderResource, len(resources)) - for i, resource := range resources { - if resource != nil { - providerResources[i] = *resource - providerResources[i].SpacePath = space.Path - } - } - slices.SortFunc(providerResources, types.CompareInfraProviderResource) - infraProviderConfig.Resources = providerResources - } - return infraProviderConfig, nil -} - -func (c *Service) FindTemplate( - ctx context.Context, - space *types.SpaceCore, - identifier string, -) (*types.InfraProviderTemplate, error) { - infraProviderTemplate, err := c.infraProviderTemplateStore.FindByIdentifier(ctx, space.ID, identifier) - if err != nil { - return nil, fmt.Errorf("failed to find infraprovider template: %q %w", identifier, err) - } - return infraProviderTemplate, nil -} - -func (c *Service) FindResourceByIdentifier( - ctx context.Context, - spaceID int64, - identifier string) (*types.InfraProviderResource, error) { - return c.infraProviderResourceStore.FindByIdentifier(ctx, spaceID, identifier) -} - -func (c *Service) FindResource(ctx context.Context, id int64) (*types.InfraProviderResource, error) { - return c.infraProviderResourceStore.Find(ctx, id) -} diff --git a/app/services/infraprovider/update_config.go b/app/services/infraprovider/update_config.go new file mode 100644 index 000000000..a3c04fc6f --- /dev/null +++ b/app/services/infraprovider/update_config.go @@ -0,0 +1,38 @@ +// Copyright 2023 Harness, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infraprovider + +import ( + "context" + "fmt" + "time" + + "github.com/harness/gitness/types" +) + +func (c *Service) UpdateConfig(ctx context.Context, infraProviderConfig *types.InfraProviderConfig) error { + err := c.validateConfig(infraProviderConfig) + if err != nil { + return err + } + + infraProviderConfig.Updated = time.Now().UnixMilli() + err = c.infraProviderConfigStore.Update(ctx, infraProviderConfig) + if err != nil { + return fmt.Errorf("failed to update infraprovider config for %s: %w", infraProviderConfig.Identifier, err) + } + + return nil +} diff --git a/app/services/infraprovider/update.go b/app/services/infraprovider/update_template.go similarity index 53% rename from app/services/infraprovider/update.go rename to app/services/infraprovider/update_template.go index b6da1c1d2..4b849e041 100644 --- a/app/services/infraprovider/update.go +++ b/app/services/infraprovider/update_template.go @@ -17,49 +17,10 @@ package infraprovider import ( "context" "fmt" - "time" "github.com/harness/gitness/types" ) -func (c *Service) updateConfig(ctx context.Context, infraProviderConfig *types.InfraProviderConfig) error { - err := c.validateConfigAndResources(infraProviderConfig) - if err != nil { - return err - } - - infraProviderConfig.Updated = time.Now().UnixMilli() - err = c.infraProviderConfigStore.Update(ctx, infraProviderConfig) - if err != nil { - return fmt.Errorf("failed to update infraprovider config for %s: %w", infraProviderConfig.Identifier, err) - } - - return nil -} - -func (c *Service) UpdateResource(ctx context.Context, resource types.InfraProviderResource) error { - err := c.tx.WithTx(ctx, func(ctx context.Context) error { - space, err := c.spaceFinder.FindByRef(ctx, resource.SpacePath) - if err != nil { - return err - } - infraProviderResource, err := c.FindResourceByIdentifier(ctx, space.ID, resource.UID) - if err != nil { - return err - } - resource.ID = infraProviderResource.ID - resource.Updated = time.Now().UnixMilli() - if err = c.infraProviderResourceStore.Update(ctx, &resource); err != nil { - return err - } - return nil - }) - if err != nil { - return fmt.Errorf("failed to complete update txn for the infraprovider resource %w", err) - } - return nil -} - func (c *Service) UpdateTemplate(ctx context.Context, template types.InfraProviderTemplate) error { err := c.tx.WithTx(ctx, func(ctx context.Context) error { space, err := c.spaceFinder.FindByRef(ctx, template.SpacePath) diff --git a/app/services/infraprovider/upsert.go b/app/services/infraprovider/upsert.go index 3a9c069f9..306d0ef68 100644 --- a/app/services/infraprovider/upsert.go +++ b/app/services/infraprovider/upsert.go @@ -25,9 +25,10 @@ import ( "github.com/rs/zerolog/log" ) -func (c *Service) UpsertInfraProvider( +func (c *Service) UpsertConfigAndResources( ctx context.Context, infraProviderConfig *types.InfraProviderConfig, + infraProviderResources []types.InfraProviderResource, ) error { space, err := c.spaceFinder.FindByRef(ctx, infraProviderConfig.SpacePath) if err != nil { @@ -35,7 +36,7 @@ func (c *Service) UpsertInfraProvider( } err = c.tx.WithTx(ctx, func(ctx context.Context) error { - return c.upsertConfig(ctx, space, infraProviderConfig) + return c.upsertConfigAndResources(ctx, space, infraProviderConfig, infraProviderResources) }) if err != nil { return fmt.Errorf("failed to complete txn for the infraprovider: %w", err) @@ -43,59 +44,35 @@ func (c *Service) UpsertInfraProvider( return nil } -func (c *Service) upsertConfig( +func (c *Service) upsertConfigAndResources( ctx context.Context, space *types.SpaceCore, infraProviderConfig *types.InfraProviderConfig, + infraProviderResources []types.InfraProviderResource, ) error { providerConfigInDB, err := c.Find(ctx, space, infraProviderConfig.Identifier) var infraProviderConfigID int64 - if errors.Is(err, store.ErrResourceNotFound) { - if infraProviderConfigID, err = c.createConfig(ctx, infraProviderConfig); err != nil { + if errors.Is(err, store.ErrResourceNotFound) { // nolint:gocritic + configID, createErr := c.createConfig(ctx, infraProviderConfig) + if createErr != nil { return fmt.Errorf("could not create the config: %q %w", infraProviderConfig.Identifier, err) } + infraProviderConfigID = configID log.Info().Msgf("created new infraconfig %s", infraProviderConfig.Identifier) - } else if err != nil { // todo: should this not be err == nil? - infraProviderConfig.ID = providerConfigInDB.ID - if err = c.updateConfig(ctx, infraProviderConfig); err != nil { - return fmt.Errorf("could not update the config %s: %w", infraProviderConfig.Identifier, err) - } - log.Info().Msgf("updated infraconfig %s", infraProviderConfig.Identifier) - } - if err != nil { + } else if err != nil { return err + } else { + infraProviderConfigID = providerConfigInDB.ID } - if err = c.UpsertResources(ctx, infraProviderConfig.Resources, infraProviderConfigID, space.ID); err != nil { - return err - } - return nil -} -func (c *Service) UpsertResources( - ctx context.Context, - resources []types.InfraProviderResource, - configID int64, - spaceID int64, -) error { - for idx := range resources { - resource := &resources[idx] - resource.InfraProviderConfigID = configID - resource.SpaceID = spaceID - if err := c.validate(ctx, resource); err != nil { - return err - } - _, err := c.infraProviderResourceStore.FindByIdentifier(ctx, resource.SpaceID, resource.UID) - if errors.Is(err, store.ErrResourceNotFound) { - if err = c.infraProviderResourceStore.Create(ctx, resource); err != nil { - return fmt.Errorf("failed to create infraprovider resource for %s: %w", resource.UID, err) - } - log.Info().Msgf("created new resource %s/%s", resource.InfraProviderConfigIdentifier, resource.UID) - } else { - if err = c.UpdateResource(ctx, *resource); err != nil { - log.Info().Msgf("updated resource %s/%s", resource.InfraProviderConfigIdentifier, resource.UID) - return fmt.Errorf("could not update the resources %s: %w", resource.UID, err) - } - } + infraProviderConfig.ID = infraProviderConfigID + if err = c.UpdateConfig(ctx, infraProviderConfig); err != nil { + return fmt.Errorf("could not update the config %s: %w", infraProviderConfig.Identifier, err) + } + + log.Info().Msgf("updated infraconfig %s", infraProviderConfig.Identifier) + if err = c.createMissingResources(ctx, infraProviderResources, infraProviderConfigID, space.ID); err != nil { + return err } return nil } diff --git a/app/services/infraprovider/wire.go b/app/services/infraprovider/wire.go index 76bd9edc6..a54f93820 100644 --- a/app/services/infraprovider/wire.go +++ b/app/services/infraprovider/wire.go @@ -29,11 +29,13 @@ var WireSet = wire.NewSet( func ProvideInfraProvider( tx dbtx.Transactor, + gitspaceConfigStore store.GitspaceConfigStore, resourceStore store.InfraProviderResourceStore, configStore store.InfraProviderConfigStore, templateStore store.InfraProviderTemplateStore, infraProviderFactory infraprovider.Factory, spaceFinder refcache.SpaceFinder, ) *Service { - return NewService(tx, resourceStore, configStore, templateStore, infraProviderFactory, spaceFinder) + return NewService(tx, gitspaceConfigStore, resourceStore, configStore, templateStore, infraProviderFactory, + spaceFinder) } diff --git a/app/store/database.go b/app/store/database.go index 0f0a5284c..f3e49188a 100644 --- a/app/store/database.go +++ b/app/store/database.go @@ -705,6 +705,12 @@ type ( // Count the number of gitspace configs in a space matching the given filter. Count(ctx context.Context, filter *types.GitspaceFilter) (int64, error) + + // ListActiveConfigsForInfraProviderResource returns all active configs for the given infra resource. + ListActiveConfigsForInfraProviderResource( + ctx context.Context, + infraProviderResourceID int64, + ) ([]*types.GitspaceConfig, error) } GitspaceInstanceStore interface { @@ -768,15 +774,18 @@ type ( // Find returns a Infra provider resource given a ID from the datastore. Find(ctx context.Context, id int64) (*types.InfraProviderResource, error) - // FindByIdentifier returns a infra provider resource with a given UID in a space - FindByIdentifier(ctx context.Context, spaceID int64, identifier string) (*types.InfraProviderResource, error) + // FindByConfigAndIdentifier returns the most recent infra provider resource with a given identifier in + // a space for the given infra provider config. + FindByConfigAndIdentifier( + ctx context.Context, + spaceID int64, + infraProviderConfigID int64, + identifier string, + ) (*types.InfraProviderResource, error) // Create creates a new infra provider resource in the datastore. Create(ctx context.Context, infraProviderResource *types.InfraProviderResource) error - // Update tries to update the infra provider resource in the datastore. - Update(ctx context.Context, infraProviderResource *types.InfraProviderResource) error - // List lists the infra provider resource present for the gitspace config in a parent space ID in the datastore. List( ctx context.Context, @@ -784,8 +793,8 @@ type ( filter types.ListQueryFilter, ) ([]*types.InfraProviderResource, error) - // DeleteByIdentifier deletes the Infra provider resource with the given identifier for the given space. - DeleteByIdentifier(ctx context.Context, spaceID int64, identifier string) error + // Delete soft deletes the Infra provider resource with the given id. + Delete(ctx context.Context, id int64) error } PipelineStore interface { diff --git a/app/store/database/gitspace_config.go b/app/store/database/gitspace_config.go index b5b263974..c2f368182 100644 --- a/app/store/database/gitspace_config.go +++ b/app/store/database/gitspace_config.go @@ -398,6 +398,30 @@ func (s gitspaceConfigStore) FindAll(ctx context.Context, ids []int64) ([]*types return s.mapToGitspaceConfigs(ctx, dst) } +func (s gitspaceConfigStore) ListActiveConfigsForInfraProviderResource( + ctx context.Context, + infraProviderResourceID int64, +) ([]*types.GitspaceConfig, error) { + stmt := database.Builder. + Select(gitspaceConfigSelectColumns). + From(gitspaceConfigsTable). + Where("gconf_infra_provider_resource_id = ?", infraProviderResourceID). + Where("gconf_is_deleted = false"). + Where("gconf_is_marked_for_deletion = false") + + sql, args, err := stmt.ToSql() + + if err != nil { + return nil, errors.Wrap(err, "Failed to convert squirrel builder to sql") + } + db := dbtx.GetAccessor(ctx, s.db) + var dst []*gitspaceConfigWithLatestInstance + if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { + return nil, database.ProcessSQLErrorf(ctx, err, "Failed executing list gitspace config query") + } + return s.ToGitspaceConfigs(ctx, dst) +} + func (s gitspaceConfigStore) mapDBToGitspaceConfig( ctx context.Context, in *gitspaceConfig, diff --git a/app/store/database/infra_provider_config.go b/app/store/database/infra_provider_config.go index 5bd7dfde0..10349d64f 100644 --- a/app/store/database/infra_provider_config.go +++ b/app/store/database/infra_provider_config.go @@ -124,7 +124,8 @@ func (i infraProviderConfigStore) FindByType( db := dbtx.GetAccessor(ctx, i.db) dst := new([]*infraProviderConfig) if err := db.SelectContext(ctx, dst, sql, args...); err != nil { - return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list infraprovider resources") + return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list infraprovider configs of type %s for"+ + " space %d", infraProviderType, spaceID) } return i.mapToInfraProviderConfigs(*dst) } diff --git a/app/store/database/infra_provider_resource.go b/app/store/database/infra_provider_resource.go index 5e0a54675..73197f01b 100644 --- a/app/store/database/infra_provider_resource.go +++ b/app/store/database/infra_provider_resource.go @@ -17,7 +17,7 @@ package database import ( "context" "encoding/json" - "fmt" + "time" "github.com/harness/gitness/app/store" "github.com/harness/gitness/store/database" @@ -46,7 +46,9 @@ const ( ipreso_disk, ipreso_network, ipreso_region, - ipreso_opentofu_params + ipreso_metadata, + ipreso_is_deleted, + ipreso_deleted ` infraProviderResourceSelectColumns = "ipreso_id," + infraProviderResourceInsertColumns infraProviderResourceTable = `infra_provider_resources` @@ -64,14 +66,16 @@ type infraProviderResource struct { Disk null.String `db:"ipreso_disk"` Network null.String `db:"ipreso_network"` Region string `db:"ipreso_region"` // need list maybe - OpenTofuParams []byte `db:"ipreso_opentofu_params"` + Metadata []byte `db:"ipreso_metadata"` Created int64 `db:"ipreso_created"` Updated int64 `db:"ipreso_updated"` + IsDeleted bool `db:"ipreso_is_deleted"` + Deleted null.Int `db:"ipreso_deleted"` } var _ store.InfraProviderResourceStore = (*infraProviderResourceStore)(nil) -// NewGitspaceConfigStore returns a new GitspaceConfigStore. +// NewInfraProviderResourceStore returns a new InfraProviderResourceStore. func NewInfraProviderResourceStore(db *sqlx.DB) store.InfraProviderResourceStore { return &infraProviderResourceStore{ db: db, @@ -82,12 +86,23 @@ type infraProviderResourceStore struct { db *sqlx.DB } -func (s infraProviderResourceStore) List(ctx context.Context, infraProviderConfigID int64, - _ types.ListQueryFilter) ([]*types.InfraProviderResource, error) { - stmt := database.Builder. - Select(infraProviderResourceSelectColumns). +func (s infraProviderResourceStore) List( + ctx context.Context, + infraProviderConfigID int64, + _ types.ListQueryFilter, +) ([]*types.InfraProviderResource, error) { + subQuery := squirrel.Select("MAX(ipreso_created)"). From(infraProviderResourceTable). - Where("ipreso_infra_provider_config_id = $1", infraProviderConfigID) + Where("ipreso_infra_provider_config_id = $1", infraProviderConfigID). + Where("ipreso_is_deleted = false"). + GroupBy("ipreso_uid") + + stmt := squirrel.Select(infraProviderResourceSelectColumns). + From(infraProviderResourceTable). + Where("ipreso_infra_provider_config_id = $2", infraProviderConfigID). + Where("ipreso_is_deleted = false"). + Where(squirrel.Expr("ipreso_created IN (?)", subQuery)). + OrderBy("ipreso_uid", "ipreso_created DESC") sql, args, err := stmt.ToSql() if err != nil { @@ -96,7 +111,8 @@ func (s infraProviderResourceStore) List(ctx context.Context, infraProviderConfi db := dbtx.GetAccessor(ctx, s.db) dst := new([]infraProviderResource) if err := db.SelectContext(ctx, dst, sql, args...); err != nil { - return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list infraprovider resources") + return nil, database.ProcessSQLErrorf(ctx, err, "Failed to list infraprovider resources for config %d", + infraProviderConfigID) } return mapToInfraProviderResources(*dst) } @@ -105,7 +121,8 @@ func (s infraProviderResourceStore) Find(ctx context.Context, id int64) (*types. stmt := database.Builder. Select(infraProviderResourceSelectColumns). From(infraProviderResourceTable). - Where(infraProviderResourceIDColumn+" = $1", id) + Where(infraProviderResourceIDColumn+" = $1", id). + Where("ipreso_is_deleted = false") sql, args, err := stmt.ToSql() if err != nil { @@ -119,16 +136,23 @@ func (s infraProviderResourceStore) Find(ctx context.Context, id int64) (*types. return mapToInfraProviderResource(dst) } -func (s infraProviderResourceStore) FindByIdentifier( +func (s infraProviderResourceStore) FindByConfigAndIdentifier( ctx context.Context, spaceID int64, + infraProviderConfigID int64, identifier string, ) (*types.InfraProviderResource, error) { - stmt := database.Builder. - Select(infraProviderResourceSelectColumns). - From(infraProviderResourceTable). - Where("ipreso_uid = $1", identifier). - Where("ipreso_space_id = $2", spaceID) + stmt := + database.Builder. + Select(infraProviderResourceSelectColumns). + From(infraProviderResourceTable). + OrderBy("ipreso_created DESC"). + Limit(1). + Where("ipreso_uid = ?", identifier). + Where("ipreso_space_id = ?", spaceID). + Where("ipreso_infra_provider_config_id = ?", infraProviderConfigID). + Where("ipreso_is_deleted = false") + sql, args, err := stmt.ToSql() if err != nil { return nil, errors.Wrap(err, "Failed to convert squirrel builder to sql") @@ -145,7 +169,7 @@ func (s infraProviderResourceStore) Create( ctx context.Context, infraProviderResource *types.InfraProviderResource, ) error { - jsonBytes, marshalErr := json.Marshal(infraProviderResource.Metadata) + metadata, marshalErr := json.Marshal(infraProviderResource.Metadata) if marshalErr != nil { return marshalErr } @@ -165,7 +189,9 @@ func (s infraProviderResourceStore) Create( infraProviderResource.Disk, infraProviderResource.Network, infraProviderResource.Region, - jsonBytes, + metadata, + infraProviderResource.IsDeleted, + infraProviderResource.Deleted, ). Suffix(ReturningClause + infraProviderResourceIDColumn) sql, args, err := stmt.ToSql() @@ -180,57 +206,9 @@ func (s infraProviderResourceStore) Create( return nil } -func (s infraProviderResourceStore) Update( - ctx context.Context, - infraProviderResource *types.InfraProviderResource, -) error { - dbinfraProviderResource, err := s.mapToInternalInfraProviderResource(infraProviderResource) - if err != nil { - return fmt.Errorf( - "failed to map to DB Obj for infraprovider resource %s", infraProviderResource.UID) - } - stmt := database.Builder. - Update(infraProviderResourceTable). - Set("ipreso_display_name", dbinfraProviderResource.Name). - Set("ipreso_memory", dbinfraProviderResource.Memory). - Set("ipreso_disk", dbinfraProviderResource.Disk). - Set("ipreso_network", dbinfraProviderResource.Network). - Set("ipreso_region", dbinfraProviderResource.Region). - Set("ipreso_opentofu_params", dbinfraProviderResource.OpenTofuParams). - Set("ipreso_updated", dbinfraProviderResource.Updated). - Where("ipreso_id = ?", infraProviderResource.ID) - sql, args, err := stmt.ToSql() - if err != nil { - return errors.Wrap(err, "Failed to convert squirrel builder to sql") - } - db := dbtx.GetAccessor(ctx, s.db) - if _, err := db.ExecContext(ctx, sql, args...); err != nil { - return database.ProcessSQLErrorf( - ctx, err, "Failed to update infraprovider resource %s", infraProviderResource.UID) - } - return nil -} - -func (s infraProviderResourceStore) DeleteByIdentifier(ctx context.Context, spaceID int64, identifier string) error { - stmt := database.Builder. - Delete(infraProviderResourceTable). - Where("ipreso_uid = $1", identifier). - Where("ipreso_space_id = $2", spaceID) - sql, args, err := stmt.ToSql() - if err != nil { - return errors.Wrap(err, "Failed to convert squirrel builder to sql") - } - db := dbtx.GetAccessor(ctx, s.db) - if _, err := db.ExecContext(ctx, sql, args...); err != nil { - return database.ProcessSQLErrorf( - ctx, err, "Failed to delete infra provider resource %s", identifier) - } - return nil -} - func mapToInfraProviderResource(in *infraProviderResource) (*types.InfraProviderResource, error) { metadataParamsMap := make(map[string]string) - marshalErr := json.Unmarshal(in.OpenTofuParams, &metadataParamsMap) + marshalErr := json.Unmarshal(in.Metadata, &metadataParamsMap) if marshalErr != nil { return nil, marshalErr } @@ -249,30 +227,8 @@ func mapToInfraProviderResource(in *infraProviderResource) (*types.InfraProvider Metadata: metadataParamsMap, Created: in.Created, Updated: in.Updated, - }, nil -} - -func (s infraProviderResourceStore) mapToInternalInfraProviderResource( - in *types.InfraProviderResource, -) (*infraProviderResource, error) { - jsonBytes, marshalErr := json.Marshal(in.Metadata) - if marshalErr != nil { - return nil, marshalErr - } - return &infraProviderResource{ - Identifier: in.UID, - InfraProviderConfigID: in.InfraProviderConfigID, - InfraProviderType: in.InfraProviderType, - Name: in.Name, - SpaceID: in.SpaceID, - CPU: null.StringFromPtr(in.CPU), - Memory: null.StringFromPtr(in.Memory), - Disk: null.StringFromPtr(in.Disk), - Network: null.StringFromPtr(in.Network), - Region: in.Region, - OpenTofuParams: jsonBytes, - Created: in.Created, - Updated: in.Updated, + IsDeleted: in.IsDeleted, + Deleted: in.Deleted.Ptr(), }, nil } @@ -371,3 +327,23 @@ func (i InfraProviderResourceView) FindMany(ctx context.Context, ids []int64) ([ } return mapToInfraProviderResources(*dst) } + +func (s infraProviderResourceStore) Delete(ctx context.Context, id int64) error { + now := time.Now().UnixMilli() + stmt := database.Builder. + Update(infraProviderResourceTable). + Set("ipreso_updated", now). + Set("ipreso_deleted", now). + Set("ipreso_is_deleted", true). + Where("ipreso_id = $4", id) + sql, args, err := stmt.ToSql() + if err != nil { + return errors.Wrap(err, "Failed to convert squirrel builder to sql") + } + db := dbtx.GetAccessor(ctx, s.db) + if _, err := db.ExecContext(ctx, sql, args...); err != nil { + return database.ProcessSQLErrorf( + ctx, err, "Failed to update infraprovider resource %d", id) + } + return nil +} diff --git a/app/store/database/migrate/postgres/0101_alter_table_infra_provider_resources_soft_delete.down.sql b/app/store/database/migrate/postgres/0101_alter_table_infra_provider_resources_soft_delete.down.sql new file mode 100644 index 000000000..8ec29bdc9 --- /dev/null +++ b/app/store/database/migrate/postgres/0101_alter_table_infra_provider_resources_soft_delete.down.sql @@ -0,0 +1,11 @@ +DROP INDEX infra_provider_resources_uid_space_id_config_id_created; + +ALTER TABLE infra_provider_resources + RENAME COLUMN ipreso_metadata TO ipreso_opentofu_params; +ALTER TABLE infra_provider_resources + DROP COLUMN ipreso_is_deleted; +ALTER TABLE infra_provider_resources + DROP COLUMN ipreso_deleted; + + +CREATE UNIQUE INDEX infra_provider_resources_uid_space_id ON infra_provider_resources (ipreso_uid, ipreso_space_id); diff --git a/app/store/database/migrate/postgres/0101_alter_table_infra_provider_resources_soft_delete.up.sql b/app/store/database/migrate/postgres/0101_alter_table_infra_provider_resources_soft_delete.up.sql new file mode 100644 index 000000000..e37247f7d --- /dev/null +++ b/app/store/database/migrate/postgres/0101_alter_table_infra_provider_resources_soft_delete.up.sql @@ -0,0 +1,10 @@ +ALTER TABLE infra_provider_resources + RENAME COLUMN ipreso_opentofu_params TO ipreso_metadata; +ALTER TABLE infra_provider_resources + ADD COLUMN ipreso_is_deleted BOOL NOT NULL DEFAULT false; +ALTER TABLE infra_provider_resources + ADD COLUMN ipreso_deleted BIGINT; + +DROP INDEX infra_provider_resources_uid_space_id; +CREATE UNIQUE INDEX infra_provider_resources_uid_space_id_config_id_created ON infra_provider_resources + (ipreso_uid, ipreso_space_id, ipreso_infra_provider_config_id, ipreso_created); diff --git a/app/store/database/migrate/sqlite/0101_alter_table_infra_provider_resources_soft_delete.down.sql b/app/store/database/migrate/sqlite/0101_alter_table_infra_provider_resources_soft_delete.down.sql new file mode 100644 index 000000000..8ec29bdc9 --- /dev/null +++ b/app/store/database/migrate/sqlite/0101_alter_table_infra_provider_resources_soft_delete.down.sql @@ -0,0 +1,11 @@ +DROP INDEX infra_provider_resources_uid_space_id_config_id_created; + +ALTER TABLE infra_provider_resources + RENAME COLUMN ipreso_metadata TO ipreso_opentofu_params; +ALTER TABLE infra_provider_resources + DROP COLUMN ipreso_is_deleted; +ALTER TABLE infra_provider_resources + DROP COLUMN ipreso_deleted; + + +CREATE UNIQUE INDEX infra_provider_resources_uid_space_id ON infra_provider_resources (ipreso_uid, ipreso_space_id); diff --git a/app/store/database/migrate/sqlite/0101_alter_table_infra_provider_resources_soft_delete.up.sql b/app/store/database/migrate/sqlite/0101_alter_table_infra_provider_resources_soft_delete.up.sql new file mode 100644 index 000000000..e37247f7d --- /dev/null +++ b/app/store/database/migrate/sqlite/0101_alter_table_infra_provider_resources_soft_delete.up.sql @@ -0,0 +1,10 @@ +ALTER TABLE infra_provider_resources + RENAME COLUMN ipreso_opentofu_params TO ipreso_metadata; +ALTER TABLE infra_provider_resources + ADD COLUMN ipreso_is_deleted BOOL NOT NULL DEFAULT false; +ALTER TABLE infra_provider_resources + ADD COLUMN ipreso_deleted BIGINT; + +DROP INDEX infra_provider_resources_uid_space_id; +CREATE UNIQUE INDEX infra_provider_resources_uid_space_id_config_id_created ON infra_provider_resources + (ipreso_uid, ipreso_space_id, ipreso_infra_provider_config_id, ipreso_created); diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index b52821012..ff7566d84 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -308,7 +308,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro } dockerProvider := infraprovider.ProvideDockerProvider(dockerConfig, dockerClientFactory, reporter2) factory := infraprovider.ProvideFactory(dockerProvider) - infraproviderService := infraprovider2.ProvideInfraProvider(transactor, infraProviderResourceStore, infraProviderConfigStore, infraProviderTemplateStore, factory, spaceFinder) + infraproviderService := infraprovider2.ProvideInfraProvider(transactor, gitspaceConfigStore, infraProviderResourceStore, infraProviderConfigStore, infraProviderTemplateStore, factory, spaceFinder) gitnessSCM := scm.ProvideGitnessSCM(repoStore, repoFinder, gitInterface, tokenStore, principalStore, provider) genericSCM := scm.ProvideGenericSCM() scmFactory := scm.ProvideFactory(gitnessSCM, genericSCM) diff --git a/infraprovider/docker_provider.go b/infraprovider/docker_provider.go index 1d45c9959..7c00869a8 100644 --- a/infraprovider/docker_provider.go +++ b/infraprovider/docker_provider.go @@ -330,6 +330,6 @@ func volumeName(spacePath string, resourceKey string) string { return name } -func (d DockerProvider) ValidateConfigAndResources(_ *types.InfraProviderConfig) error { +func (d DockerProvider) ValidateConfig(_ *types.InfraProviderConfig) error { return nil } diff --git a/infraprovider/infra_provider.go b/infraprovider/infra_provider.go index 502e130a4..a9a168ca0 100644 --- a/infraprovider/infra_provider.go +++ b/infraprovider/infra_provider.go @@ -69,6 +69,6 @@ type InfraProvider interface { // ProvisioningType specifies whether the provider will provision new infra resources or it will reuse existing. ProvisioningType() enum.InfraProvisioningType - // ValidateConfigAndResources checks if the provided infra config and resources are valid as per the provider. - ValidateConfigAndResources(infraProviderConfig *types.InfraProviderConfig) error + // ValidateConfig checks if the provided infra config is as per the provider. + ValidateConfig(infraProviderConfig *types.InfraProviderConfig) error } diff --git a/types/infra_provider.go b/types/infra_provider.go index 2b8a3c9be..e0a475ba3 100644 --- a/types/infra_provider.go +++ b/types/infra_provider.go @@ -54,6 +54,8 @@ type InfraProviderResource struct { InfraProviderType enum.InfraProviderType `json:"infra_provider_type"` Created int64 `json:"created"` Updated int64 `json:"updated"` + IsDeleted bool `json:"is_deleted,omitempty"` + Deleted *int64 `json:"deleted,omitempty"` } func (i *InfraProviderResource) Identifier() int64 {