feat: [CDE-637]: Changes to support hybrid's infra resources. (#3453)

* Linting
* Refactoring db query in infra resource store for pq support.
* Using a default value of infra config identifier if not present in the create gitspace request.
* feat: [CDE-637]: Decoupling infra resource mgmt from config. Adding soft delete support to infra resource table.
* Making infra provider resource unique to config and space.
jobatzil/login/xforwardedfor
Dhruv Dhruv 2025-02-19 06:45:24 +00:00 committed by Harness
parent 8732b64aa8
commit 31d15dc8eb
32 changed files with 847 additions and 605 deletions

View File

@ -13,4 +13,7 @@ GITNESS_SSH_HOST=localhost
GITNESS_SSH_PORT=2222
GITNESS_REGISTRY_STORAGE_TYPE=filesystem
GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY=/tmp
GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY=/tmp
#GITNESS_DATABASE_DRIVER=postgres
#GITNESS_DATABASE_DATASOURCE=postgres://postgres:postgres@localhost:5432/gitness?sslmode=disable

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -25,7 +25,7 @@ import (
)
type createInfraProviderConfigRequest struct {
infraprovider.CreateInput
infraprovider.ConfigInput
}
type getInfraProviderRequest struct {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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,

View File

@ -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)
}

View File

@ -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
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {