// 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 infrastructure import ( "context" "encoding/json" "fmt" "github.com/harness/gitness/infraprovider" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" ) // Find finds the provisioned infra resources for the gitspace instance. func (i InfraProvisioner) Find( ctx context.Context, gitspaceConfig types.GitspaceConfig, requiredGitspacePorts []types.GitspacePort, ) (*types.Infrastructure, error) { infraProviderResource := gitspaceConfig.InfraProviderResource infraProviderEntity, err := i.getConfigFromResource(ctx, infraProviderResource) if err != nil { return nil, err } infraProvider, err := i.getInfraProvider(infraProviderEntity.Type) if err != nil { return nil, err } var inputParams []types.InfraProviderParameter var agentPort = 0 if infraProvider.ProvisioningType() == enum.InfraProvisioningTypeNew { inputParams, err = i.paramsForProvisioningTypeNew(ctx, gitspaceConfig) if err != nil { return nil, err } // TODO: What if the agent port has deviated from when the last instance was created? agentPort = i.config.AgentPort } else { inputParams, err = i.paramsForProvisioningTypeExisting(ctx, infraProviderResource, infraProvider) if err != nil { return nil, err } } infra, err := infraProvider.Find(ctx, gitspaceConfig.SpaceID, gitspaceConfig.SpacePath, gitspaceConfig.Identifier, gitspaceConfig.GitspaceInstance.Identifier, agentPort, requiredGitspacePorts, inputParams) if err != nil { return nil, fmt.Errorf("failed to find infrastructure: %w", err) } if infra == nil { // fallback infra, err = i.getInfraFromStoredInfo(ctx, gitspaceConfig) if err != nil { return nil, fmt.Errorf("failed to build infrastructure from stored info: %w", err) } } gitspaceScheme, err := getGitspaceScheme(gitspaceConfig.IDE, infraProviderResource.Metadata["gitspace_scheme"]) if err != nil { return nil, fmt.Errorf("failed to get gitspace_scheme: %w", err) } infra.GitspaceScheme = gitspaceScheme return infra, nil } func (i InfraProvisioner) paramsForProvisioningTypeNew( ctx context.Context, gitspaceConfig types.GitspaceConfig, ) ([]types.InfraProviderParameter, error) { infraProvisionedLatest, err := i.infraProvisionedStore.FindLatestByGitspaceInstanceID( ctx, gitspaceConfig.GitspaceInstance.ID) if err != nil { return nil, fmt.Errorf( "could not find latest infra provisioned entity for instance %d: %w", gitspaceConfig.GitspaceInstance.ID, err) } if infraProvisionedLatest.InputParams == "" { return []types.InfraProviderParameter{}, err } allParams, err := deserializeInfraProviderParams(infraProvisionedLatest.InputParams) if err != nil { return nil, err } return allParams, nil } func deserializeInfraProviderParams(in string) ([]types.InfraProviderParameter, error) { var parameters []types.InfraProviderParameter err := json.Unmarshal([]byte(in), ¶meters) if err != nil { return nil, fmt.Errorf("unable to unmarshal infra provider params %+v: %w", in, err) } return parameters, nil } func (i InfraProvisioner) paramsForProvisioningTypeExisting( ctx context.Context, infraProviderResource types.InfraProviderResource, infraProvider infraprovider.InfraProvider, ) ([]types.InfraProviderParameter, error) { allParams, err := i.getAllParamsFromDB(ctx, infraProviderResource, infraProvider) if err != nil { return nil, fmt.Errorf("could not get all params from DB while finding: %w", err) } return allParams, nil } func getGitspaceScheme(ideType enum.IDEType, gitspaceSchemeFromMetadata string) (string, error) { switch ideType { case enum.IDETypeVSCodeWeb: return gitspaceSchemeFromMetadata, nil case enum.IDETypeVSCode: return "ssh", nil case enum.IDETypeIntelliJ, enum.IDETypePyCharm, enum.IDETypeGoland, enum.IDETypeWebStorm, enum.IDETypeCLion, enum.IDETypePHPStorm, enum.IDETypeRubyMine, enum.IDETypeRider: return "ssh", nil default: return "", fmt.Errorf("unknown ideType %s", ideType) } } func (i InfraProvisioner) getInfraFromStoredInfo( ctx context.Context, gitspaceConfig types.GitspaceConfig, ) (*types.Infrastructure, error) { infraProvisioned, err := i.infraProvisionedStore.FindLatestByGitspaceInstanceID( ctx, gitspaceConfig.GitspaceInstance.ID, ) if err != nil { return nil, fmt.Errorf("failed to find infraProvisioned: %w", err) } var infra types.Infrastructure err = json.Unmarshal([]byte(*infraProvisioned.ResponseMetadata), &infra) if err != nil { return nil, fmt.Errorf("failed to unmarshal response metadata: %w", err) } return &infra, nil } // Methods to find infra provider resources. func (i InfraProvisioner) getConfigFromResource( ctx context.Context, infraProviderResource types.InfraProviderResource, ) (*types.InfraProviderConfig, error) { config, err := i.infraProviderConfigStore.Find(ctx, infraProviderResource.InfraProviderConfigID) if err != nil { return nil, fmt.Errorf( "unable to get infra provider details for ID %d: %w", infraProviderResource.InfraProviderConfigID, err) } return config, nil } func (i InfraProvisioner) getInfraProvider( infraProviderType enum.InfraProviderType, ) (infraprovider.InfraProvider, error) { infraProvider, err := i.providerFactory.GetInfraProvider(infraProviderType) if err != nil { return nil, fmt.Errorf("unable to get infra provider of type %v: %w", infraProviderType, err) } return infraProvider, nil } func (i InfraProvisioner) getAllParamsFromDB( ctx context.Context, infraProviderResource types.InfraProviderResource, infraProvider infraprovider.InfraProvider, ) ([]types.InfraProviderParameter, error) { var allParams []types.InfraProviderParameter templateParams, err := i.getTemplateParams(ctx, infraProvider, infraProviderResource) if err != nil { return nil, err } allParams = append(allParams, templateParams...) params := i.paramsFromResource(infraProviderResource, infraProvider) allParams = append(allParams, params...) return allParams, nil } func (i InfraProvisioner) getTemplateParams( ctx context.Context, infraProvider infraprovider.InfraProvider, infraProviderResource types.InfraProviderResource, ) ([]types.InfraProviderParameter, error) { var params []types.InfraProviderParameter templateParams := infraProvider.TemplateParams() for _, param := range templateParams { key := param.Name if infraProviderResource.Metadata[key] != "" { templateIdentifier := infraProviderResource.Metadata[key] template, err := i.infraProviderTemplateStore.FindByIdentifier( ctx, infraProviderResource.SpaceID, templateIdentifier) if err != nil { return nil, fmt.Errorf("unable to get template params for ID %s: %w", infraProviderResource.Metadata[key], err) } params = append(params, types.InfraProviderParameter{ Name: key, Value: template.Data, }) } } return params, nil } func (i InfraProvisioner) paramsFromResource( infraProviderResource types.InfraProviderResource, infraProvider infraprovider.InfraProvider, ) []types.InfraProviderParameter { // NOTE: templateParamsMap is required to filter out template params since their values have already been fetched // and we dont need the template identifiers, which are the values for template params in the resource Metadata. templateParamsMap := make(map[string]bool) for _, templateParam := range infraProvider.TemplateParams() { templateParamsMap[templateParam.Name] = true } params := make([]types.InfraProviderParameter, 0) for key, value := range infraProviderResource.Metadata { if key == "" || value == "" || templateParamsMap[key] { continue } params = append(params, types.InfraProviderParameter{ Name: key, Value: value, }) } return params }