drone/app/gitspace/infrastructure/trigger_infra_event.go

300 lines
9.9 KiB
Go

// 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"
"time"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/infraprovider"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type Config struct {
AgentPort int
}
type InfraEventOpts struct {
RequiredGitspacePorts []types.GitspacePort
CanDeleteUserData bool
}
type InfraProvisioner struct {
infraProviderConfigStore store.InfraProviderConfigStore
infraProviderResourceStore store.InfraProviderResourceStore
providerFactory infraprovider.Factory
infraProviderTemplateStore store.InfraProviderTemplateStore
infraProvisionedStore store.InfraProvisionedStore
config *Config
}
func NewInfraProvisionerService(
infraProviderConfigStore store.InfraProviderConfigStore,
infraProviderResourceStore store.InfraProviderResourceStore,
providerFactory infraprovider.Factory,
infraProviderTemplateStore store.InfraProviderTemplateStore,
infraProvisionedStore store.InfraProvisionedStore,
config *Config,
) InfraProvisioner {
return InfraProvisioner{
infraProviderConfigStore: infraProviderConfigStore,
infraProviderResourceStore: infraProviderResourceStore,
providerFactory: providerFactory,
infraProviderTemplateStore: infraProviderTemplateStore,
infraProvisionedStore: infraProvisionedStore,
config: config,
}
}
// TriggerInfraEvent an interaction with the infrastructure, and once completed emits event in an asynchronous manner.
func (i InfraProvisioner) TriggerInfraEvent(
ctx context.Context,
eventType enum.InfraEvent,
gitspaceConfig types.GitspaceConfig,
infra *types.Infrastructure,
) error {
opts := InfraEventOpts{CanDeleteUserData: false}
return i.TriggerInfraEventWithOpts(ctx, eventType, gitspaceConfig, infra, opts)
}
// TriggerInfraEventWithOpts triggers the provisionining of infra resources using the
// infraProviderResource with different infra providers.
// Stop event deprovisions those resources which can be stopped without losing the Gitspace data.
// CleanupInstance event cleans up resources exclusive for a gitspace instance
// Deprovision triggers deprovisioning of resources created for a Gitspace.
// canDeleteUserData = true -> deprovision of all resources
// canDeleteUserData = false -> deprovision of all resources except storage associated to user data.
func (i InfraProvisioner) TriggerInfraEventWithOpts(
ctx context.Context,
eventType enum.InfraEvent,
gitspaceConfig types.GitspaceConfig,
infra *types.Infrastructure,
opts InfraEventOpts,
) error {
infraProviderEntity, err := i.getConfigFromResource(ctx, gitspaceConfig.InfraProviderResource)
if err != nil {
return err
}
infraProvider, err := i.getInfraProvider(infraProviderEntity.Type)
if err != nil {
return err
}
switch eventType {
case enum.InfraEventProvision:
if infraProvider.ProvisioningType() == enum.InfraProvisioningTypeNew {
return i.provisionNewInfrastructure(ctx, infraProvider, infraProviderEntity.Type,
gitspaceConfig, opts.RequiredGitspacePorts)
}
return i.provisionExistingInfrastructure(ctx, infraProvider, gitspaceConfig, opts.RequiredGitspacePorts)
case enum.InfraEventDeprovision:
if infraProvider.ProvisioningType() == enum.InfraProvisioningTypeNew {
return i.deprovisionNewInfrastructure(ctx, infraProvider, gitspaceConfig, *infra, opts.CanDeleteUserData)
}
return infraProvider.Deprovision(ctx, *infra, opts.CanDeleteUserData)
case enum.InfraEventCleanup:
return infraProvider.CleanupInstanceResources(ctx, *infra)
case enum.InfraEventStop:
return infraProvider.Stop(ctx, *infra)
default:
return fmt.Errorf("unsupported event type: %s", eventType)
}
}
func (i InfraProvisioner) provisionNewInfrastructure(
ctx context.Context,
infraProvider infraprovider.InfraProvider,
infraProviderType enum.InfraProviderType,
gitspaceConfig types.GitspaceConfig,
requiredGitspacePorts []types.GitspacePort,
) error {
// Logic for new provisioning...
infraProvisionedLatest, _ := i.infraProvisionedStore.FindLatestByGitspaceInstanceID(
ctx, gitspaceConfig.GitspaceInstance.ID)
if infraProvisionedLatest != nil &&
infraProvisionedLatest.InfraStatus == enum.InfraStatusPending &&
time.Since(time.UnixMilli(infraProvisionedLatest.Updated)).Milliseconds() < (10*60*1000) {
return fmt.Errorf("there is already infra provisioning in pending state %d", infraProvisionedLatest.ID)
} else if infraProvisionedLatest != nil {
infraProvisionedLatest.InfraStatus = enum.InfraStatusUnknown
err := i.infraProvisionedStore.Update(ctx, infraProvisionedLatest)
if err != nil {
return fmt.Errorf("could not update Infra Provisioned entity: %w", err)
}
}
infraProviderResource := gitspaceConfig.InfraProviderResource
allParams, configMetadata, err := i.getAllParamsFromDB(ctx, infraProviderResource, infraProvider)
if err != nil {
return fmt.Errorf("could not get all params from DB while provisioning: %w", err)
}
err = infraProvider.ValidateParams(allParams)
if err != nil {
return fmt.Errorf("invalid provisioning params %v: %w", infraProviderResource.Metadata, err)
}
now := time.Now()
paramsBytes, err := serializeInfraProviderParams(allParams)
if err != nil {
return err
}
infrastructure := types.Infrastructure{
Identifier: gitspaceConfig.GitspaceInstance.Identifier,
SpaceID: gitspaceConfig.SpaceID,
SpacePath: gitspaceConfig.SpacePath,
GitspaceConfigIdentifier: gitspaceConfig.Identifier,
GitspaceInstanceIdentifier: gitspaceConfig.GitspaceInstance.Identifier,
ProviderType: infraProviderType,
InputParameters: allParams,
ConfigMetadata: configMetadata,
}
responseMetadata, err := json.Marshal(infrastructure)
if err != nil {
return fmt.Errorf("unable to marshal infra object %+v: %w", responseMetadata, err)
}
responseMetaDataJSON := string(responseMetadata)
infraProvisioned := &types.InfraProvisioned{
GitspaceInstanceID: gitspaceConfig.GitspaceInstance.ID,
InfraProviderType: infraProviderType,
InfraProviderResourceID: infraProviderResource.ID,
Created: now.UnixMilli(),
Updated: now.UnixMilli(),
InputParams: paramsBytes,
InfraStatus: enum.InfraStatusPending,
SpaceID: gitspaceConfig.SpaceID,
ResponseMetadata: &responseMetaDataJSON,
}
err = i.infraProvisionedStore.Create(ctx, infraProvisioned)
if err != nil {
return fmt.Errorf("unable to create infraProvisioned entry for %d", gitspaceConfig.GitspaceInstance.ID)
}
agentPort := i.config.AgentPort
err = infraProvider.Provision(
ctx,
gitspaceConfig.SpaceID,
gitspaceConfig.SpacePath,
gitspaceConfig.Identifier,
gitspaceConfig.GitspaceInstance.Identifier,
agentPort,
requiredGitspacePorts,
allParams,
)
if err != nil {
infraProvisioned.InfraStatus = enum.InfraStatusUnknown
infraProvisioned.Updated = time.Now().UnixMilli()
updateErr := i.infraProvisionedStore.Update(ctx, infraProvisioned)
if updateErr != nil {
log.Err(updateErr).Msgf("unable to update infraProvisioned Entry for %d", infraProvisioned.ID)
}
return fmt.Errorf(
"unable to trigger provision infrastructure for gitspaceConfigIdentifier %v: %w",
gitspaceConfig.Identifier,
err,
)
}
return nil
}
func (i InfraProvisioner) provisionExistingInfrastructure(
ctx context.Context,
infraProvider infraprovider.InfraProvider,
gitspaceConfig types.GitspaceConfig,
requiredGitspacePorts []types.GitspacePort,
) error {
allParams, _, err := i.getAllParamsFromDB(ctx, gitspaceConfig.InfraProviderResource, infraProvider)
if err != nil {
return fmt.Errorf("could not get all params from DB while provisioning: %w", err)
}
err = infraProvider.ValidateParams(allParams)
if err != nil {
return fmt.Errorf("invalid provisioning params %v: %w", gitspaceConfig.InfraProviderResource.Metadata, err)
}
err = infraProvider.Provision(
ctx,
gitspaceConfig.SpaceID,
gitspaceConfig.SpacePath,
gitspaceConfig.Identifier,
gitspaceConfig.GitspaceInstance.Identifier,
0, // NOTE: Agent port is not required for provisioning type Existing.
requiredGitspacePorts,
allParams,
)
if err != nil {
return fmt.Errorf(
"unable to trigger provision infrastructure for gitspaceConfigIdentifier %v: %w",
gitspaceConfig.Identifier,
err,
)
}
return nil
}
func (i InfraProvisioner) deprovisionNewInfrastructure(
ctx context.Context,
infraProvider infraprovider.InfraProvider,
gitspaceConfig types.GitspaceConfig,
infra types.Infrastructure,
canDeleteUserData bool,
) error {
infraProvisionedLatest, err := i.infraProvisionedStore.FindLatestByGitspaceInstanceID(
ctx, gitspaceConfig.GitspaceInstance.ID)
if err != nil {
return fmt.Errorf(
"could not find latest infra provisioned entity for instance %d: %w",
gitspaceConfig.GitspaceInstance.ID, err)
}
if infraProvisionedLatest.InfraStatus == enum.InfraStatusDestroyed {
return nil
}
err = infraProvider.Deprovision(ctx, infra, canDeleteUserData)
if err != nil {
return fmt.Errorf("unable to trigger deprovision infra %+v: %w", infra, err)
}
return err
}
func serializeInfraProviderParams(in []types.InfraProviderParameter) (string, error) {
output, err := json.Marshal(in)
if err != nil {
return "", fmt.Errorf("unable to marshal infra provider params: %w", err)
}
return string(output), nil
}