diff --git a/app/events/gitspaceoperations/category.go b/app/events/gitspaceoperations/category.go new file mode 100644 index 000000000..b55616554 --- /dev/null +++ b/app/events/gitspaceoperations/category.go @@ -0,0 +1,20 @@ +// 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 events + +const ( + // category defines the event category used for this package. + category = "gitspace_operations" +) diff --git a/app/events/gitspaceoperations/events.go b/app/events/gitspaceoperations/events.go new file mode 100644 index 000000000..9bc9ecaa7 --- /dev/null +++ b/app/events/gitspaceoperations/events.go @@ -0,0 +1,63 @@ +// 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 events + +import ( + "context" + "fmt" + + "github.com/harness/gitness/events" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" + + "github.com/rs/zerolog/log" +) + +const ( + GitspaceOperationsEvent events.EventType = "gitspace_operations_event" +) + +type ( + GitspaceOperationsEventPayload struct { + Type enum.GitspaceOperationsEvent `json:"type"` + Infra types.Infrastructure `json:"infra,omitempty"` + Response any `json:"response,omitempty"` + } +) + +func (r *Reporter) EmitGitspaceOperationsEvent( + ctx context.Context, + event events.EventType, + payload *GitspaceOperationsEventPayload, +) error { + if payload == nil { + return fmt.Errorf("payload can not be nil for event type %s", GitspaceOperationsEvent) + } + eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, event, payload) + if err != nil { + return fmt.Errorf("failed to send %+v event", event) + } + + log.Ctx(ctx).Debug().Msgf("reported %v event with id '%s'", event, eventID) + + return nil +} + +func (r *Reader) RegisterGitspaceOperationsEvent( + fn events.HandlerFunc[*GitspaceOperationsEventPayload], + opts ...events.HandlerOption, +) error { + return events.ReaderRegisterEvent(r.innerReader, GitspaceOperationsEvent, fn, opts...) +} diff --git a/app/events/gitspaceoperations/reader.go b/app/events/gitspaceoperations/reader.go new file mode 100644 index 000000000..27b39a9c5 --- /dev/null +++ b/app/events/gitspaceoperations/reader.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 events + +import ( + "github.com/harness/gitness/events" +) + +func NewReaderFactory(eventsSystem *events.System) (*events.ReaderFactory[*Reader], error) { + readerFactoryFunc := func(innerReader *events.GenericReader) (*Reader, error) { + return &Reader{ + innerReader: innerReader, + }, nil + } + + return events.NewReaderFactory(eventsSystem, category, readerFactoryFunc) +} + +// Reader is the event reader for this package. +type Reader struct { + innerReader *events.GenericReader +} + +func (r *Reader) Configure(opts ...events.ReaderOption) { + r.innerReader.Configure(opts...) +} diff --git a/app/events/gitspaceoperations/reporter.go b/app/events/gitspaceoperations/reporter.go new file mode 100644 index 000000000..68fdecaad --- /dev/null +++ b/app/events/gitspaceoperations/reporter.go @@ -0,0 +1,37 @@ +// 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 events + +import ( + "errors" + + "github.com/harness/gitness/events" +) + +// Reporter is the event reporter for this package. +type Reporter struct { + innerReporter *events.GenericReporter +} + +func NewReporter(eventsSystem *events.System) (*Reporter, error) { + innerReporter, err := events.NewReporter(eventsSystem, category) + if err != nil { + return nil, errors.New("failed to create new GenericReporter from event system") + } + + return &Reporter{ + innerReporter: innerReporter, + }, nil +} diff --git a/app/events/gitspaceoperations/wire.go b/app/events/gitspaceoperations/wire.go new file mode 100644 index 000000000..215800d79 --- /dev/null +++ b/app/events/gitspaceoperations/wire.go @@ -0,0 +1,35 @@ +// 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 events + +import ( + "github.com/harness/gitness/events" + + "github.com/google/wire" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideReaderFactory, + ProvideReporter, +) + +func ProvideReaderFactory(eventsSystem *events.System) (*events.ReaderFactory[*Reader], error) { + return NewReaderFactory(eventsSystem) +} + +func ProvideReporter(eventsSystem *events.System) (*Reporter, error) { + return NewReporter(eventsSystem) +} diff --git a/app/gitspace/infrastructure/find.go b/app/gitspace/infrastructure/find.go index 0b85cffbb..09025398d 100644 --- a/app/gitspace/infrastructure/find.go +++ b/app/gitspace/infrastructure/find.go @@ -28,7 +28,6 @@ import ( 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) @@ -41,35 +40,43 @@ func (i InfraProvisioner) Find( return nil, err } - var inputParams []types.InfraProviderParameter - var configMetadata map[string]any - var agentPort = 0 + var infra *types.Infrastructure + //nolint:nestif if infraProvider.ProvisioningType() == enum.InfraProvisioningTypeNew { - inputParams, configMetadata, err = i.paramsForProvisioningTypeNew(ctx, gitspaceConfig) + 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 + infra, err = i.GetInfraFromStoredInfo(ctx, gitspaceConfig) + if err != nil { + return nil, fmt.Errorf("failed to find infrastructure from stored info: %w", err) + } + status, err := infraProvider.FindInfraStatus( + ctx, + gitspaceConfig.Identifier, + gitspaceConfig.GitspaceInstance.Identifier, + inputParams, + ) + if err != nil { + return nil, fmt.Errorf("failed to find infra status: %w", err) + } + if status != nil { + infra.Status = *status + } } else { - inputParams, configMetadata, err = i.paramsForProvisioningTypeExisting(ctx, infraProviderResource, infraProvider) + 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, configMetadata) - if err != nil { - return nil, fmt.Errorf("failed to find infrastructure: %w", err) - } - - if infra == nil { // fallback - infra, err = i.getInfraFromStoredInfo(ctx, gitspaceConfig) + infra, err = infraProvider.Find( + ctx, + gitspaceConfig.SpaceID, + gitspaceConfig.SpacePath, + gitspaceConfig.Identifier, + inputParams, + ) if err != nil { - return nil, fmt.Errorf("failed to build infrastructure from stored info: %w", err) + return nil, fmt.Errorf("failed to find infrastructure from provider: %w", err) } } @@ -143,7 +150,7 @@ func getGitspaceScheme(ideType enum.IDEType, gitspaceSchemeFromMetadata string) } } -func (i InfraProvisioner) getInfraFromStoredInfo( +func (i InfraProvisioner) GetInfraFromStoredInfo( ctx context.Context, gitspaceConfig types.GitspaceConfig, ) (*types.Infrastructure, error) { diff --git a/app/gitspace/infrastructure/infra_post_exec.go b/app/gitspace/infrastructure/infra_post_exec.go index 1eedeb66c..6dc1dd61e 100644 --- a/app/gitspace/infrastructure/infra_post_exec.go +++ b/app/gitspace/infrastructure/infra_post_exec.go @@ -18,6 +18,7 @@ import ( "context" "encoding/json" "fmt" + "math" "strconv" "time" @@ -47,13 +48,13 @@ func (i InfraProvisioner) PostInfraEventComplete( enum.InfraEventDeprovision, enum.InfraEventStop, enum.InfraEventCleanup: - return i.updateInfraProvisioned(ctx, gitspaceConfig.GitspaceInstance.ID, infra) + return i.UpdateInfraProvisioned(ctx, gitspaceConfig.GitspaceInstance.ID, infra) default: return fmt.Errorf("unsupported event type: %s", eventType) } } -func (i InfraProvisioner) updateInfraProvisioned( +func (i InfraProvisioner) UpdateInfraProvisioned( ctx context.Context, gitspaceInstanceID int64, infrastructure types.Infrastructure, @@ -72,8 +73,22 @@ func (i InfraProvisioner) updateInfraProvisioned( infraProvisionedLatest.InfraStatus = infrastructure.Status infraProvisionedLatest.ServerHostIP = infrastructure.AgentHost infraProvisionedLatest.ServerHostPort = strconv.Itoa(infrastructure.AgentPort) - infraProvisionedLatest.ProxyHost = infrastructure.ProxyAgentHost - infraProvisionedLatest.ProxyPort = int32(infrastructure.ProxyAgentPort) + + proxyHost := infrastructure.AgentHost + if infrastructure.ProxyAgentHost != "" { + proxyHost = infrastructure.ProxyAgentHost + } + infraProvisionedLatest.ProxyHost = proxyHost + + proxyPort := infrastructure.AgentPort + if infrastructure.ProxyAgentPort != 0 { + proxyPort = infrastructure.ProxyAgentPort + } + if proxyPort > math.MaxInt32 || proxyPort < math.MinInt32 { + return fmt.Errorf("proxyPort value %d exceeds int32 range", proxyPort) + } + infraProvisionedLatest.ProxyPort = int32(proxyPort) + infraProvisionedLatest.ResponseMetadata = &responseMetaDataJSON infraProvisionedLatest.Updated = time.Now().UnixMilli() diff --git a/app/gitspace/infrastructure/trigger_infra_event.go b/app/gitspace/infrastructure/trigger_infra_event.go index 67f0d428e..ecd96f9e8 100644 --- a/app/gitspace/infrastructure/trigger_infra_event.go +++ b/app/gitspace/infrastructure/trigger_infra_event.go @@ -163,6 +163,22 @@ func (i InfraProvisioner) provisionNewInfrastructure( 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, @@ -172,6 +188,7 @@ func (i InfraProvisioner) provisionNewInfrastructure( InputParams: paramsBytes, InfraStatus: enum.InfraStatusPending, SpaceID: gitspaceConfig.SpaceID, + ResponseMetadata: &responseMetaDataJSON, } err = i.infraProvisionedStore.Create(ctx, infraProvisioned) @@ -190,7 +207,6 @@ func (i InfraProvisioner) provisionNewInfrastructure( agentPort, requiredGitspacePorts, allParams, - configMetadata, ) if err != nil { infraProvisioned.InfraStatus = enum.InfraStatusUnknown @@ -216,7 +232,7 @@ func (i InfraProvisioner) provisionExistingInfrastructure( gitspaceConfig types.GitspaceConfig, requiredGitspacePorts []types.GitspacePort, ) error { - allParams, configMetadata, err := i.getAllParamsFromDB(ctx, gitspaceConfig.InfraProviderResource, infraProvider) + 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) } @@ -235,7 +251,6 @@ func (i InfraProvisioner) provisionExistingInfrastructure( 0, // NOTE: Agent port is not required for provisioning type Existing. requiredGitspacePorts, allParams, - configMetadata, ) if err != nil { return fmt.Errorf( diff --git a/app/gitspace/orchestrator/container/container_orchestrator.go b/app/gitspace/orchestrator/container/container_orchestrator.go index d0cfecb8b..5d9998d78 100644 --- a/app/gitspace/orchestrator/container/container_orchestrator.go +++ b/app/gitspace/orchestrator/container/container_orchestrator.go @@ -33,13 +33,21 @@ type Orchestrator interface { resolvedDetails scm.ResolvedDetails, defaultBaseImage string, ideService ide.IDE, - ) (*StartResponse, error) + ) error + + // RetryCreateAndStartGitspaceIfRequired will handle the delegate task response and retry if status code is > 202 + RetryCreateAndStartGitspaceIfRequired(ctx context.Context) // StopGitspace stops the gitspace container. StopGitspace(ctx context.Context, config types.GitspaceConfig, infra types.Infrastructure) error // StopAndRemoveGitspace stops and removes the gitspace container. - StopAndRemoveGitspace(ctx context.Context, config types.GitspaceConfig, infra types.Infrastructure) error + StopAndRemoveGitspace( + ctx context.Context, + config types.GitspaceConfig, + infra types.Infrastructure, + canDeleteUserData bool, + ) error // Status checks if the infra is reachable and ready to orchestrate containers. Status(ctx context.Context, infra types.Infrastructure) error diff --git a/app/gitspace/orchestrator/container/devcontainer_container_utils.go b/app/gitspace/orchestrator/container/devcontainer_container_utils.go index 5fa9833f0..f11aadbed 100644 --- a/app/gitspace/orchestrator/container/devcontainer_container_utils.go +++ b/app/gitspace/orchestrator/container/devcontainer_container_utils.go @@ -28,6 +28,7 @@ import ( "strconv" "strings" + "github.com/harness/gitness/app/gitspace/orchestrator/container/response" "github.com/harness/gitness/app/gitspace/orchestrator/runarg" "github.com/harness/gitness/app/gitspace/orchestrator/utils" gitspaceTypes "github.com/harness/gitness/app/gitspace/types" @@ -794,7 +795,7 @@ func GetContainerResponse( containerName string, portMappings map[int]*types.PortMapping, repoName string, -) (*StartResponse, error) { +) (*response.StartResponse, error) { id, ports, remoteUser, err := GetContainerInfo(ctx, containerName, dockerClient, portMappings) if err != nil { return nil, err @@ -803,7 +804,7 @@ func GetContainerResponse( homeDir := GetUserHomeDir(remoteUser) codeRepoDir := filepath.Join(homeDir, repoName) - return &StartResponse{ + return &response.StartResponse{ ContainerID: id, ContainerName: containerName, PublishedPorts: ports, diff --git a/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go b/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go index 81d0dfe1b..baea7179a 100644 --- a/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go +++ b/app/gitspace/orchestrator/container/embedded_docker_container_orchestrator.go @@ -20,7 +20,9 @@ import ( "path/filepath" "strings" + events "github.com/harness/gitness/app/events/gitspaceoperations" "github.com/harness/gitness/app/gitspace/logutil" + "github.com/harness/gitness/app/gitspace/orchestrator/container/response" "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" "github.com/harness/gitness/app/gitspace/orchestrator/ide" "github.com/harness/gitness/app/gitspace/orchestrator/runarg" @@ -29,6 +31,7 @@ import ( gitspaceTypes "github.com/harness/gitness/app/gitspace/types" "github.com/harness/gitness/infraprovider" "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" "github.com/docker/docker/api/types/mount" "github.com/docker/docker/client" @@ -46,6 +49,7 @@ type EmbeddedDockerOrchestrator struct { dockerClientFactory *infraprovider.DockerClientFactory statefulLogger *logutil.StatefulLogger runArgProvider runarg.Provider + eventReporter *events.Reporter } // Step represents a single setup action. @@ -87,11 +91,13 @@ func NewEmbeddedDockerOrchestrator( dockerClientFactory *infraprovider.DockerClientFactory, statefulLogger *logutil.StatefulLogger, runArgProvider runarg.Provider, -) Orchestrator { - return &EmbeddedDockerOrchestrator{ + eventReporter *events.Reporter, +) EmbeddedDockerOrchestrator { + return EmbeddedDockerOrchestrator{ dockerClientFactory: dockerClientFactory, statefulLogger: statefulLogger, runArgProvider: runArgProvider, + eventReporter: eventReporter, } } @@ -106,20 +112,20 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace( resolvedRepoDetails scm.ResolvedDetails, defaultBaseImage string, ideService ide.IDE, -) (*StartResponse, error) { +) error { containerName := GetGitspaceContainerName(gitspaceConfig) logger := log.Ctx(ctx).With().Str(loggingKey, containerName).Logger() // Step 1: Validate access key accessKey, err := e.getAccessKey(gitspaceConfig) if err != nil { - return nil, err + return err } // Step 2: Get Docker client dockerClient, err := e.getDockerClient(ctx, infra) if err != nil { - return nil, err + return err } defer e.closeDockerClient(dockerClient) @@ -129,7 +135,7 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace( // Step 3: Check the current state of the container state, err := e.checkContainerState(ctx, dockerClient, containerName) if err != nil { - return nil, err + return err } // Step 4: Handle different container states @@ -146,7 +152,7 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace( accessKey, ideService, ); err != nil { - return nil, err + return err } case ContainerStateRemoved: if err = e.createAndStartNewGitspace( @@ -158,18 +164,37 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace( defaultBaseImage, ideService, imagAuthMap); err != nil { - return nil, err + return err } case ContainerStatePaused, ContainerStateCreated, ContainerStateUnknown, ContainerStateDead: // TODO handle the following states - return nil, fmt.Errorf("gitspace %s is in a unhandled state: %s", containerName, state) + return fmt.Errorf("gitspace %s is in a unhandled state: %s", containerName, state) default: - return nil, fmt.Errorf("gitspace %s is in a bad state: %s", containerName, state) + return fmt.Errorf("gitspace %s is in a bad state: %s", containerName, state) } // Step 5: Retrieve container information and return response - return GetContainerResponse(ctx, dockerClient, containerName, infra.GitspacePortMappings, resolvedRepoDetails.RepoName) + startResponse, err := GetContainerResponse( + ctx, + dockerClient, + containerName, + infra.GitspacePortMappings, + resolvedRepoDetails.RepoName, + ) + if err != nil { + return err + } + + return e.eventReporter.EmitGitspaceOperationsEvent( + ctx, + events.GitspaceOperationsEvent, + &events.GitspaceOperationsEventPayload{ + Type: enum.GitspaceOperationsEventStart, + Infra: infra, + Response: *startResponse, + }, + ) } // startStoppedGitspace starts the Gitspace container if it was stopped. @@ -293,8 +318,16 @@ func (e *EmbeddedDockerOrchestrator) StopGitspace( return fmt.Errorf("gitspace %s is in a bad state: %s", containerName, state) } + err = e.eventReporter.EmitGitspaceOperationsEvent( + ctx, + events.GitspaceOperationsEvent, + &events.GitspaceOperationsEventPayload{ + Type: enum.GitspaceOperationsEventStop, + Infra: infra, + }, + ) logger.Debug().Msg("stopped gitspace") - return nil + return err } // stopRunningGitspace handles stopping the container when it is in a running state. @@ -326,6 +359,7 @@ func (e *EmbeddedDockerOrchestrator) StopAndRemoveGitspace( ctx context.Context, gitspaceConfig types.GitspaceConfig, infra types.Infrastructure, + canDeleteUserData bool, ) error { containerName := GetGitspaceContainerName(gitspaceConfig) logger := log.Ctx(ctx).With().Str(loggingKey, containerName).Logger() @@ -373,8 +407,17 @@ func (e *EmbeddedDockerOrchestrator) StopAndRemoveGitspace( return fmt.Errorf("failed to remove gitspace %s: %w", containerName, err) } + err = e.eventReporter.EmitGitspaceOperationsEvent( + ctx, + events.GitspaceOperationsEvent, + &events.GitspaceOperationsEventPayload{ + Type: enum.GitspaceOperationsEventDelete, + Infra: infra, + Response: &response.DeleteResponse{CanDeleteUserData: canDeleteUserData}, + }, + ) logger.Debug().Msg("removed gitspace") - return nil + return err } func (e *EmbeddedDockerOrchestrator) StreamLogs( @@ -833,3 +876,7 @@ func getImage(devcontainerConfig types.DevcontainerConfig, defaultBaseImage stri } return imageName } + +func (e *EmbeddedDockerOrchestrator) RetryCreateAndStartGitspaceIfRequired(_ context.Context) { + // Nothing to do here as the event will be published from CreateAndStartGitspace itself. +} diff --git a/app/gitspace/orchestrator/container/orchestrator_factory.go b/app/gitspace/orchestrator/container/orchestrator_factory.go new file mode 100644 index 000000000..f89f99762 --- /dev/null +++ b/app/gitspace/orchestrator/container/orchestrator_factory.go @@ -0,0 +1,43 @@ +// 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 container + +import ( + "fmt" + + "github.com/harness/gitness/types/enum" +) + +type Factory interface { + GetContainerOrchestrator(providerType enum.InfraProviderType) (Orchestrator, error) +} +type factory struct { + containerOrchestrators map[enum.InfraProviderType]Orchestrator +} + +func NewFactory(embeddedDockerOrchestrator EmbeddedDockerOrchestrator) Factory { + containerOrchestrators := make(map[enum.InfraProviderType]Orchestrator) + containerOrchestrators[enum.InfraProviderTypeDocker] = &embeddedDockerOrchestrator + return &factory{containerOrchestrators: containerOrchestrators} +} + +func (f *factory) GetContainerOrchestrator(infraProviderType enum.InfraProviderType) (Orchestrator, error) { + val, exist := f.containerOrchestrators[infraProviderType] + if !exist { + return nil, fmt.Errorf("unsupported infra provider type: %s", infraProviderType) + } + + return val, nil +} diff --git a/app/gitspace/orchestrator/container/response/delete.go b/app/gitspace/orchestrator/container/response/delete.go new file mode 100644 index 000000000..e35abac87 --- /dev/null +++ b/app/gitspace/orchestrator/container/response/delete.go @@ -0,0 +1,19 @@ +// 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 response + +type DeleteResponse struct { + CanDeleteUserData bool `json:"can_delete_user_data"` +} diff --git a/app/gitspace/orchestrator/container/response/start.go b/app/gitspace/orchestrator/container/response/start.go new file mode 100644 index 000000000..95eb37839 --- /dev/null +++ b/app/gitspace/orchestrator/container/response/start.go @@ -0,0 +1,23 @@ +// 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 response + +type StartResponse struct { + ContainerID string `json:"container_id"` + ContainerName string `json:"container_name"` + PublishedPorts map[int]string `json:"published_ports"` + AbsoluteRepoPath string `json:"absolute_repo_path"` + RemoteUser string `json:"remote_user"` +} diff --git a/app/gitspace/orchestrator/container/types.go b/app/gitspace/orchestrator/container/types.go index 60ca6a86e..98813071d 100644 --- a/app/gitspace/orchestrator/container/types.go +++ b/app/gitspace/orchestrator/container/types.go @@ -14,14 +14,6 @@ package container -type StartResponse struct { - ContainerID string - ContainerName string - PublishedPorts map[int]string - AbsoluteRepoPath string - RemoteUser string -} - type PostAction string const ( diff --git a/app/gitspace/orchestrator/container/util.go b/app/gitspace/orchestrator/container/util.go index 763bff558..c8ccfa288 100644 --- a/app/gitspace/orchestrator/container/util.go +++ b/app/gitspace/orchestrator/container/util.go @@ -18,14 +18,17 @@ import ( "context" "encoding/json" "fmt" + "io" "path/filepath" "sync" + "github.com/harness/gitness/app/gitspace/orchestrator/container/response" "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" gitspaceTypes "github.com/harness/gitness/app/gitspace/types" "github.com/harness/gitness/types" dockerTypes "github.com/docker/docker/api/types" + "github.com/rs/zerolog/log" ) const ( @@ -155,3 +158,32 @@ func ExecuteLifecycleCommands( return nil } + +func ProcessStartResponse( + ctx context.Context, + config types.GitspaceConfig, + resp io.ReadCloser, +) (*response.StartResponse, error) { + var err error + defer func() { + err = resp.Close() + if err != nil { + log.Ctx(ctx).Warn().Err(err).Msgf( + "failed to close response after starting gitspace %s", config.Identifier) + } + }() + + bodyBytes, _ := io.ReadAll(resp) + responseBody := string(bodyBytes) + + log.Debug().Msgf("response from %s %s", config.GitspaceInstance.Identifier, responseBody) + + var startResponse response.StartResponse + err = json.Unmarshal(bodyBytes, &startResponse) + if err != nil { + return nil, fmt.Errorf("error unmarshalling start response for gitspace instance %s: %w", + config.GitspaceInstance.Identifier, err) + } + + return &startResponse, nil +} diff --git a/app/gitspace/orchestrator/container/wire.go b/app/gitspace/orchestrator/container/wire.go index b69e9034f..4db878669 100644 --- a/app/gitspace/orchestrator/container/wire.go +++ b/app/gitspace/orchestrator/container/wire.go @@ -15,6 +15,7 @@ package container import ( + events "github.com/harness/gitness/app/events/gitspaceoperations" "github.com/harness/gitness/app/gitspace/logutil" "github.com/harness/gitness/app/gitspace/orchestrator/runarg" "github.com/harness/gitness/infraprovider" @@ -24,16 +25,25 @@ import ( var WireSet = wire.NewSet( ProvideEmbeddedDockerOrchestrator, + ProvideContainerOrchestratorFactory, ) func ProvideEmbeddedDockerOrchestrator( dockerClientFactory *infraprovider.DockerClientFactory, statefulLogger *logutil.StatefulLogger, - runArgProvdier runarg.Provider, -) Orchestrator { + runArgProvider runarg.Provider, + eventReporter *events.Reporter, +) EmbeddedDockerOrchestrator { return NewEmbeddedDockerOrchestrator( dockerClientFactory, statefulLogger, - runArgProvdier, + runArgProvider, + eventReporter, ) } + +func ProvideContainerOrchestratorFactory( + embeddedDockerOrchestrator EmbeddedDockerOrchestrator, +) Factory { + return NewFactory(embeddedDockerOrchestrator) +} diff --git a/app/gitspace/orchestrator/orchestrator_resume.go b/app/gitspace/orchestrator/orchestrator_resume.go index 5a3474a0c..5326a9e97 100644 --- a/app/gitspace/orchestrator/orchestrator_resume.go +++ b/app/gitspace/orchestrator/orchestrator_resume.go @@ -20,11 +20,9 @@ import ( "strconv" "time" - "github.com/harness/gitness/app/gitspace/orchestrator/container" + "github.com/harness/gitness/app/gitspace/orchestrator/container/response" "github.com/harness/gitness/app/gitspace/orchestrator/ide" - "github.com/harness/gitness/app/gitspace/secret" - secretenum "github.com/harness/gitness/app/gitspace/secret/enum" - "github.com/harness/gitness/app/paths" + "github.com/harness/gitness/app/gitspace/orchestrator/utils" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" @@ -39,39 +37,16 @@ func (o Orchestrator) ResumeStartGitspace( provisionedInfra types.Infrastructure, ) (types.GitspaceInstance, *types.GitspaceError) { gitspaceInstance := gitspaceConfig.GitspaceInstance - gitspaceInstance.State = enum.GitspaceInstanceStateError - - secretResolver, err := o.getSecretResolver(gitspaceInstance.AccessType) + gitspaceInstance.State = enum.GitspaceInstanceStateStarting + secretValue, err := utils.ResolveSecret(ctx, o.secretResolverFactory, gitspaceConfig) if err != nil { - log.Err(err).Msgf("could not find secret resolver for type: %s", gitspaceInstance.AccessType) return *gitspaceInstance, &types.GitspaceError{ - Error: err, + Error: fmt.Errorf("cannot resolve secret for ID %s: %w", + gitspaceConfig.InfraProviderResource.UID, err), ErrorMessage: ptr.String(err.Error()), } } - rootSpaceID, _, err := paths.DisectRoot(gitspaceConfig.SpacePath) - if err != nil { - log.Err(err).Msgf("unable to find root space id from space path: %s", gitspaceConfig.SpacePath) - return *gitspaceInstance, &types.GitspaceError{ - Error: err, - ErrorMessage: ptr.String(err.Error()), - } - } - resolvedSecret, err := secretResolver.Resolve(ctx, secret.ResolutionContext{ - UserIdentifier: gitspaceConfig.GitspaceUser.Identifier, - GitspaceIdentifier: gitspaceConfig.Identifier, - SecretRef: *gitspaceInstance.AccessKeyRef, - SpaceIdentifier: rootSpaceID, - }) - if err != nil { - log.Err(err).Msgf("could not resolve secret type: %s, ref: %s", - gitspaceInstance.AccessType, *gitspaceInstance.AccessKeyRef) - return *gitspaceInstance, &types.GitspaceError{ - Error: err, - ErrorMessage: ptr.String(err.Error()), - } - } - gitspaceInstance.AccessKey = &resolvedSecret.SecretValue + gitspaceInstance.AccessKey = secretValue ideSvc, err := o.ideFactory.GetIDE(gitspaceConfig.IDE) if err != nil { @@ -118,13 +93,13 @@ func (o Orchestrator) ResumeStartGitspace( } o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectStart) - err = o.containerOrchestrator.Status(ctx, provisionedInfra) + containerOrchestrator, err := o.containerOrchestratorFactory.GetContainerOrchestrator(provisionedInfra.ProviderType) if err != nil { o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectFailed) - agentUnreachableErr := fmt.Errorf("couldn't call the agent health API: %w", err) return *gitspaceInstance, &types.GitspaceError{ - Error: agentUnreachableErr, - ErrorMessage: ptr.String(agentUnreachableErr.Error()), + Error: fmt.Errorf("failed to get the container orchestrator for infra provider type %s: %w", + provisionedInfra.ProviderType, err), + ErrorMessage: ptr.String(err.Error()), } } @@ -153,7 +128,7 @@ func (o Orchestrator) ResumeStartGitspace( // NOTE: Currently we use a static identifier as the Gitspace user. gitspaceConfig.GitspaceUser.Identifier = harnessUser - startResponse, err := o.containerOrchestrator.CreateAndStartGitspace( + err = containerOrchestrator.CreateAndStartGitspace( ctx, gitspaceConfig, provisionedInfra, *scmResolvedDetails, o.config.DefaultBaseImage, ideSvc) if err != nil { o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceCreationFailed) @@ -164,7 +139,26 @@ func (o Orchestrator) ResumeStartGitspace( } } + return *gitspaceConfig.GitspaceInstance, nil +} + +// FinishResumeStartGitspace needs to be called from the API Handler. +func (o Orchestrator) FinishResumeStartGitspace( + ctx context.Context, + gitspaceConfig types.GitspaceConfig, + provisionedInfra types.Infrastructure, + startResponse *response.StartResponse, +) (types.GitspaceInstance, *types.GitspaceError) { o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceCreationCompleted) + gitspaceInstance := gitspaceConfig.GitspaceInstance + + ideSvc, err := o.ideFactory.GetIDE(gitspaceConfig.IDE) + if err != nil { + return *gitspaceInstance, &types.GitspaceError{ + Error: err, + ErrorMessage: ptr.String(err.Error()), + } + } ideURLString := generateIDEURL(provisionedInfra, ideSvc, startResponse) gitspaceInstance.URL = &ideURLString @@ -183,7 +177,7 @@ func (o Orchestrator) ResumeStartGitspace( func generateIDEURL( provisionedInfra types.Infrastructure, ideSvc ide.IDE, - startResponse *container.StartResponse, + startResponse *response.StartResponse, ) string { idePort := ideSvc.Port() var forwardedPort string @@ -202,19 +196,6 @@ func generateIDEURL( return ideSvc.GenerateURL(startResponse.AbsoluteRepoPath, host, forwardedPort, startResponse.RemoteUser) } -func (o Orchestrator) getSecretResolver(accessType enum.GitspaceAccessType) (secret.Resolver, error) { - secretType := secretenum.PasswordSecretType - switch accessType { - case enum.GitspaceAccessTypeUserCredentials: - secretType = secretenum.PasswordSecretType - case enum.GitspaceAccessTypeJWTToken: - secretType = secretenum.JWTSecretType - case enum.GitspaceAccessTypeSSHKey: - secretType = secretenum.SSHSecretType - } - return o.secretResolverFactory.GetSecretResolver(secretType) -} - // ResumeStopGitspace saves the deprovisioned infra details. func (o Orchestrator) ResumeStopGitspace( ctx context.Context, diff --git a/app/gitspace/orchestrator/orchestrator_trigger.go b/app/gitspace/orchestrator/orchestrator_trigger.go index a32b5aa4b..498ad4c4d 100644 --- a/app/gitspace/orchestrator/orchestrator_trigger.go +++ b/app/gitspace/orchestrator/orchestrator_trigger.go @@ -26,6 +26,7 @@ import ( "github.com/harness/gitness/app/gitspace/platformconnector" "github.com/harness/gitness/app/gitspace/scm" "github.com/harness/gitness/app/gitspace/secret" + "github.com/harness/gitness/app/store" "github.com/harness/gitness/types" "github.com/harness/gitness/types/enum" @@ -41,35 +42,38 @@ type Config struct { } type Orchestrator struct { - scm *scm.SCM - platformConnector platformconnector.PlatformConnector - infraProvisioner infrastructure.InfraProvisioner - containerOrchestrator container.Orchestrator - eventReporter *events.Reporter - config *Config - ideFactory ide.Factory - secretResolverFactory *secret.ResolverFactory + scm *scm.SCM + platformConnector platformconnector.PlatformConnector + infraProvisioner infrastructure.InfraProvisioner + containerOrchestratorFactory container.Factory + eventReporter *events.Reporter + config *Config + ideFactory ide.Factory + secretResolverFactory *secret.ResolverFactory + gitspaceInstanceStore store.GitspaceInstanceStore } func NewOrchestrator( scm *scm.SCM, platformConnector platformconnector.PlatformConnector, infraProvisioner infrastructure.InfraProvisioner, - containerOrchestrator container.Orchestrator, + containerOrchestratorFactory container.Factory, eventReporter *events.Reporter, config *Config, ideFactory ide.Factory, secretResolverFactory *secret.ResolverFactory, + gitspaceInstanceStore store.GitspaceInstanceStore, ) Orchestrator { return Orchestrator{ - scm: scm, - platformConnector: platformConnector, - infraProvisioner: infraProvisioner, - containerOrchestrator: containerOrchestrator, - eventReporter: eventReporter, - config: config, - ideFactory: ideFactory, - secretResolverFactory: secretResolverFactory, + scm: scm, + platformConnector: platformConnector, + infraProvisioner: infraProvisioner, + containerOrchestratorFactory: containerOrchestratorFactory, + eventReporter: eventReporter, + config: config, + ideFactory: ideFactory, + secretResolverFactory: secretResolverFactory, + gitspaceInstanceStore: gitspaceInstanceStore, } } @@ -165,19 +169,6 @@ func (o Orchestrator) TriggerStopGitspace( } } - o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraStopStart) - - err = o.infraProvisioner.TriggerInfraEvent(ctx, enum.InfraEventStop, gitspaceConfig, infra) - if err != nil { - o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraStopFailed) - infraStopErr := fmt.Errorf("cannot trigger stop infrastructure with ID %s: %w", - gitspaceConfig.InfraProviderResource.UID, err) - return &types.GitspaceError{ - Error: infraStopErr, - ErrorMessage: ptr.String(infraStopErr.Error()), // TODO: Fetch explicit error msg - } - } - return nil } @@ -188,11 +179,10 @@ func (o Orchestrator) stopGitspaceContainer( ) error { o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectStart) - err := o.containerOrchestrator.Status(ctx, infra) + containerOrchestrator, err := o.containerOrchestratorFactory.GetContainerOrchestrator(infra.ProviderType) if err != nil { o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectFailed) - - return fmt.Errorf("couldn't call the agent health API: %w", err) + return fmt.Errorf("couldn't get the container orchestrator: %w", err) } o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectCompleted) @@ -202,14 +192,35 @@ func (o Orchestrator) stopGitspaceContainer( // NOTE: Currently we use a static identifier as the Gitspace user. gitspaceConfig.GitspaceUser.Identifier = harnessUser - err = o.containerOrchestrator.StopGitspace(ctx, gitspaceConfig, infra) + err = containerOrchestrator.StopGitspace(ctx, gitspaceConfig, infra) if err != nil { o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceStopFailed) return fmt.Errorf("error stopping the Gitspace container: %w", err) } + return nil +} + +func (o Orchestrator) FinishStopGitspaceContainer( + ctx context.Context, + gitspaceConfig types.GitspaceConfig, + infra types.Infrastructure, +) *types.GitspaceError { o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceStopCompleted) + + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraStopStart) + + err := o.infraProvisioner.TriggerInfraEvent(ctx, enum.InfraEventStop, gitspaceConfig, &infra) + if err != nil { + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraStopFailed) + infraStopErr := fmt.Errorf("cannot trigger stop infrastructure with ID %s: %w", + gitspaceConfig.InfraProviderResource.UID, err) + return &types.GitspaceError{ + Error: infraStopErr, + ErrorMessage: ptr.String(infraStopErr.Error()), // TODO: Fetch explicit error msg + } + } return nil } @@ -217,14 +228,14 @@ func (o Orchestrator) stopAndRemoveGitspaceContainer( ctx context.Context, gitspaceConfig types.GitspaceConfig, infra types.Infrastructure, + canDeleteUserData bool, ) error { o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectStart) - err := o.containerOrchestrator.Status(ctx, infra) + containerOrchestrator, err := o.containerOrchestratorFactory.GetContainerOrchestrator(infra.ProviderType) if err != nil { o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectFailed) - - return fmt.Errorf("couldn't call the agent health API: %w", err) + return fmt.Errorf("couldn't get the container orchestrator: %w", err) } o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectCompleted) @@ -234,12 +245,43 @@ func (o Orchestrator) stopAndRemoveGitspaceContainer( // NOTE: Currently we use a static identifier as the Gitspace user. gitspaceConfig.GitspaceUser.Identifier = harnessUser - err = o.containerOrchestrator.StopAndRemoveGitspace(ctx, gitspaceConfig, infra) + err = containerOrchestrator.StopAndRemoveGitspace(ctx, gitspaceConfig, infra, canDeleteUserData) if err != nil { o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceDeletionFailed) log.Err(err).Msgf("error stopping the Gitspace container") + } + return nil +} + +func (o Orchestrator) FinishStopAndRemoveGitspaceContainer( + ctx context.Context, + gitspaceConfig types.GitspaceConfig, + infra types.Infrastructure, + canDeleteUserData bool, +) *types.GitspaceError { + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceDeletionCompleted) + if canDeleteUserData { + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraDeprovisioningStart) } else { - o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceDeletionCompleted) + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraResetStart) + } + + opts := infrastructure.InfraEventOpts{CanDeleteUserData: canDeleteUserData} + err := o.infraProvisioner.TriggerInfraEventWithOpts(ctx, enum.InfraEventDeprovision, gitspaceConfig, &infra, opts) + if err != nil { + if canDeleteUserData { + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraDeprovisioningFailed) + } else { + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraResetFailed) + } + return &types.GitspaceError{ + Error: fmt.Errorf( + "cannot trigger deprovision infrastructure with ID %s: %w", + gitspaceConfig.InfraProviderResource.UID, + err, + ), + ErrorMessage: ptr.String(err.Error()), + } } return nil } @@ -302,30 +344,66 @@ func (o Orchestrator) TriggerDeleteGitspace( "unable to find provisioned infra while triggering delete for gitspace instance %s: %w", gitspaceConfig.GitspaceInstance.Identifier, err) } - if infra.ProviderType == enum.InfraProviderTypeDocker { - if err = o.stopAndRemoveGitspaceContainer(ctx, gitspaceConfig, *infra); err != nil { - return err - } - } - if canDeleteUserData { - o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraDeprovisioningStart) - } else { - o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraResetStart) + if err = o.stopAndRemoveGitspaceContainer(ctx, gitspaceConfig, *infra, canDeleteUserData); err != nil { + log.Warn().Msgf("error stopping and removing gitspace container: %v", err) } - opts := infrastructure.InfraEventOpts{CanDeleteUserData: canDeleteUserData} - err = o.infraProvisioner.TriggerInfraEventWithOpts(ctx, enum.InfraEventDeprovision, gitspaceConfig, infra, opts) - if err != nil { - if canDeleteUserData { - o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraDeprovisioningFailed) - } else { - o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraResetFailed) - } - return fmt.Errorf( - "cannot trigger deprovision infrastructure with ID %s: %w", gitspaceConfig.InfraProviderResource.UID, err) - } + // TODO: Add a job for cleanup of infra if stop fails + log.Warn().Msgf( + "Checking and force deleting the infra if required for gitspace instance %s", + gitspaceConfig.GitspaceInstance.Identifier, + ) + ticker := time.NewTicker(60 * time.Second) + timeout := time.After(15 * time.Minute) + defer ticker.Stop() + ch := make(chan error) - return nil + for { + select { + case msg := <-ch: + if msg == nil { + return msg + } + case <-ticker.C: + instance, err := o.gitspaceInstanceStore.Find( + ctx, + gitspaceConfig.GitspaceInstance.ID, + ) + if err != nil { + return fmt.Errorf( + "failed to find gitspace instance %s: %w", + gitspaceConfig.GitspaceInstance.Identifier, + err, + ) + } + if instance.State == enum.GitspaceInstanceStateDeleted || + instance.State == enum.GitspaceInstanceStateCleaned { + return nil + } + case <-timeout: + opts := infrastructure.InfraEventOpts{CanDeleteUserData: true} + err := o.infraProvisioner.TriggerInfraEventWithOpts( + ctx, + enum.InfraEventDeprovision, + gitspaceConfig, + infra, + opts, + ) + if err != nil { + if canDeleteUserData { + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraDeprovisioningFailed) + } else { + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraResetFailed) + } + return fmt.Errorf( + "cannot trigger deprovision infrastructure with gitspace identifier %s: %w", + gitspaceConfig.GitspaceInstance.Identifier, + err, + ) + } + return nil + } + } } func (o Orchestrator) emitGitspaceEvent( @@ -379,9 +457,14 @@ func (o Orchestrator) GetGitspaceLogs(ctx context.Context, gitspaceConfig types. gitspaceConfig.GitspaceInstance.Identifier, err) } + containerOrchestrator, err := o.containerOrchestratorFactory.GetContainerOrchestrator(infra.ProviderType) + if err != nil { + return "", fmt.Errorf("couldn't get the container orchestrator: %w", err) + } + // NOTE: Currently we use a static identifier as the Gitspace user. gitspaceConfig.GitspaceUser.Identifier = harnessUser - logs, err := o.containerOrchestrator.StreamLogs(ctx, gitspaceConfig, *infra) + logs, err := containerOrchestrator.StreamLogs(ctx, gitspaceConfig, *infra) if err != nil { return "", fmt.Errorf("error while fetching logs from container orchestrator: %w", err) } @@ -394,12 +477,7 @@ func (o Orchestrator) getProvisionedInfra( gitspaceConfig types.GitspaceConfig, expectedStatus []enum.InfraStatus, ) (*types.Infrastructure, error) { - requiredGitspacePorts, err := o.getPortsRequiredForGitspace(gitspaceConfig, types.DevcontainerConfig{}) - if err != nil { - return nil, fmt.Errorf("cannot get the ports required for gitspace: %w", err) - } - - infra, err := o.infraProvisioner.Find(ctx, gitspaceConfig, requiredGitspacePorts) + infra, err := o.infraProvisioner.Find(ctx, gitspaceConfig) if err != nil { return nil, fmt.Errorf("cannot find the provisioned infra: %w", err) } diff --git a/app/gitspace/orchestrator/utils/secret.go b/app/gitspace/orchestrator/utils/secret.go new file mode 100644 index 000000000..a2133c337 --- /dev/null +++ b/app/gitspace/orchestrator/utils/secret.go @@ -0,0 +1,67 @@ +// 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 utils + +import ( + "context" + "fmt" + + "github.com/harness/gitness/app/gitspace/secret" + "github.com/harness/gitness/app/gitspace/secret/enum" + "github.com/harness/gitness/app/paths" + "github.com/harness/gitness/types" + gitnessenum "github.com/harness/gitness/types/enum" +) + +func ResolveSecret(ctx context.Context, secretResolverFactory *secret.ResolverFactory, config types.GitspaceConfig) (*string, error) { + rootSpaceID, _, err := paths.DisectRoot(config.SpacePath) + if err != nil { + return nil, fmt.Errorf("unable to find root space id from space path: %s", config.SpacePath) + } + + secretType := GetSecretType(config.GitspaceInstance.AccessType) + secretResolver, err := secretResolverFactory.GetSecretResolver(secretType) + if err != nil { + return nil, fmt.Errorf("could not find secret resolver for type: %s", config.GitspaceInstance.AccessType) + } + + resolvedSecret, err := secretResolver.Resolve(ctx, secret.ResolutionContext{ + UserIdentifier: config.GitspaceUser.Identifier, + GitspaceIdentifier: config.Identifier, + SecretRef: *config.GitspaceInstance.AccessKeyRef, + SpaceIdentifier: rootSpaceID, + }) + if err != nil { + return nil, fmt.Errorf( + "could not resolve secret type: %s, ref: %s", + config.GitspaceInstance.AccessType, + *config.GitspaceInstance.AccessKeyRef, + ) + } + return &resolvedSecret.SecretValue, nil +} + +func GetSecretType(accessType gitnessenum.GitspaceAccessType) enum.SecretType { + secretType := enum.PasswordSecretType + switch accessType { + case gitnessenum.GitspaceAccessTypeUserCredentials: + secretType = enum.PasswordSecretType + case gitnessenum.GitspaceAccessTypeJWTToken: + secretType = enum.JWTSecretType + case gitnessenum.GitspaceAccessTypeSSHKey: + secretType = enum.SSHSecretType + } + return secretType +} diff --git a/app/gitspace/orchestrator/wire.go b/app/gitspace/orchestrator/wire.go index 521aaaced..53c776b33 100644 --- a/app/gitspace/orchestrator/wire.go +++ b/app/gitspace/orchestrator/wire.go @@ -22,6 +22,7 @@ import ( "github.com/harness/gitness/app/gitspace/platformconnector" "github.com/harness/gitness/app/gitspace/scm" "github.com/harness/gitness/app/gitspace/secret" + "github.com/harness/gitness/app/store" "github.com/google/wire" ) @@ -35,20 +36,22 @@ func ProvideOrchestrator( scm *scm.SCM, platformConnector platformconnector.PlatformConnector, infraProvisioner infrastructure.InfraProvisioner, - containerOrchestrator container.Orchestrator, + containerOrchestratorFactor container.Factory, reporter *events.Reporter, config *Config, ideFactory ide.Factory, secretResolverFactory *secret.ResolverFactory, + gitspaceInstanceStore store.GitspaceInstanceStore, ) Orchestrator { return NewOrchestrator( scm, platformConnector, infraProvisioner, - containerOrchestrator, + containerOrchestratorFactor, reporter, config, ideFactory, secretResolverFactory, + gitspaceInstanceStore, ) } diff --git a/app/services/gitspaceinfraevent/handler.go b/app/services/gitspaceinfraevent/handler.go index a2c6ee49f..c3fc55c62 100644 --- a/app/services/gitspaceinfraevent/handler.go +++ b/app/services/gitspaceinfraevent/handler.go @@ -57,6 +57,7 @@ func (s *Service) handleGitspaceInfraResumeEvent( } defer func() { + // TODO: Update would not be needed for provision, stop and deprovision. Needs to be removed later. updateErr := s.gitspaceSvc.UpdateInstance(ctxWithTimedOut, instance) if updateErr != nil { log.Err(updateErr).Msgf("failed to update gitspace instance") diff --git a/app/services/gitspaceoperationsevent/handler.go b/app/services/gitspaceoperationsevent/handler.go new file mode 100644 index 000000000..41a819c24 --- /dev/null +++ b/app/services/gitspaceoperationsevent/handler.go @@ -0,0 +1,153 @@ +// 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 gitspaceoperationsevent + +import ( + "context" + "fmt" + "time" + + gitspaceEvents "github.com/harness/gitness/app/events/gitspace" + gitspaceOperationsEvents "github.com/harness/gitness/app/events/gitspaceoperations" + "github.com/harness/gitness/app/gitspace/orchestrator/container/response" + "github.com/harness/gitness/events" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" + + "github.com/rs/zerolog/log" +) + +func (s *Service) handleGitspaceOperationsEvent( + ctx context.Context, + event *events.Event[*gitspaceOperationsEvents.GitspaceOperationsEventPayload], +) error { + logr := log.With().Str("event", string(event.Payload.Type)).Logger() + logr.Debug().Msg("Received gitspace operations event") + + payload := event.Payload + ctxWithTimedOut, cancel := context.WithTimeout(ctx, time.Duration(s.config.TimeoutInMins)*time.Minute) + defer cancel() + config, fetchErr := s.getConfig( + ctxWithTimedOut, payload.Infra.SpacePath, payload.Infra.GitspaceConfigIdentifier) + if fetchErr != nil { + return fetchErr + } + + instance := config.GitspaceInstance + if payload.Infra.GitspaceInstanceIdentifier != "" { + gitspaceInstance, err := s.gitspaceSvc.FindInstanceByIdentifier( + ctxWithTimedOut, + payload.Infra.GitspaceInstanceIdentifier, + payload.Infra.SpacePath, + ) + if err != nil { + return fmt.Errorf("failed to fetch gitspace instance: %w", err) + } + + instance = gitspaceInstance + config.GitspaceInstance = instance + } + + defer func() { + updateErr := s.gitspaceSvc.UpdateInstance(ctxWithTimedOut, instance) + if updateErr != nil { + log.Err(updateErr).Msgf("failed to update gitspace instance") + } + }() + + var err error + + switch payload.Type { + case enum.GitspaceOperationsEventStart: + if config.GitspaceInstance.Identifier != payload.Infra.GitspaceInstanceIdentifier { + return fmt.Errorf("gitspace instance is not latest, stopping provisioning") + } + + startResponse, ok := payload.Response.(*response.StartResponse) + if !ok { + return fmt.Errorf("failed to cast start response") + } + updatedInstance, handleResumeStartErr := s.orchestrator.FinishResumeStartGitspace( + ctxWithTimedOut, + *config, + payload.Infra, + startResponse, + ) + if handleResumeStartErr != nil { + s.emitGitspaceConfigEvent(ctxWithTimedOut, config, enum.GitspaceEventTypeGitspaceActionStartFailed) + updatedInstance.ErrorMessage = handleResumeStartErr.ErrorMessage + err = fmt.Errorf("failed to finish resume start gitspace: %w", handleResumeStartErr.Error) + } + instance = &updatedInstance + case enum.GitspaceOperationsEventStop: + finishStopErr := s.orchestrator.FinishStopGitspaceContainer(ctxWithTimedOut, *config, payload.Infra) + if finishStopErr != nil { + s.emitGitspaceConfigEvent(ctxWithTimedOut, config, enum.GitspaceEventTypeGitspaceActionStopFailed) + instance.ErrorMessage = finishStopErr.ErrorMessage + err = fmt.Errorf("failed to finish trigger start gitspace: %w", finishStopErr.Error) + } + case enum.GitspaceOperationsEventDelete: + deleteResponse, ok := payload.Response.(*response.DeleteResponse) + if !ok { + return fmt.Errorf("failed to cast delete response") + } + finishStopAndRemoveErr := s.orchestrator.FinishStopAndRemoveGitspaceContainer( + ctxWithTimedOut, + *config, + payload.Infra, + deleteResponse.CanDeleteUserData, + ) + if finishStopAndRemoveErr != nil { + s.emitGitspaceConfigEvent(ctxWithTimedOut, config, enum.GitspaceEventTypeGitspaceActionStopFailed) + instance.ErrorMessage = finishStopAndRemoveErr.ErrorMessage + err = fmt.Errorf("failed to finish trigger start gitspace: %w", finishStopAndRemoveErr.Error) + } + + default: + return fmt.Errorf("unknown event type: %s", event.Payload.Type) + } + + if err != nil { + log.Err(err).Msgf("error while handling gitspace operations event") + } + + return nil +} + +func (s *Service) getConfig( + ctx context.Context, + spaceRef string, + identifier string, +) (*types.GitspaceConfig, error) { + config, err := s.gitspaceSvc.FindWithLatestInstance(ctx, spaceRef, identifier) + if err != nil { + return nil, fmt.Errorf( + "failed to find gitspace config during infra event handling, identifier %s: %w", identifier, err) + } + return config, nil +} + +func (s *Service) emitGitspaceConfigEvent(ctx context.Context, + config *types.GitspaceConfig, + eventType enum.GitspaceEventType, +) { + s.eventReporter.EmitGitspaceEvent(ctx, gitspaceEvents.GitspaceEvent, &gitspaceEvents.GitspaceEventPayload{ + QueryKey: config.Identifier, + EntityID: config.ID, + EntityType: enum.GitspaceEntityTypeGitspaceConfig, + EventType: eventType, + Timestamp: time.Now().UnixNano(), + }) +} diff --git a/app/services/gitspaceoperationsevent/service.go b/app/services/gitspaceoperationsevent/service.go new file mode 100644 index 000000000..1a2559f75 --- /dev/null +++ b/app/services/gitspaceoperationsevent/service.go @@ -0,0 +1,77 @@ +// 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 gitspaceoperationsevent + +import ( + "context" + "fmt" + "time" + + gitspaceevents "github.com/harness/gitness/app/events/gitspace" + gitspaceoperationsevents "github.com/harness/gitness/app/events/gitspaceoperations" + "github.com/harness/gitness/app/gitspace/orchestrator" + "github.com/harness/gitness/app/services/gitspace" + "github.com/harness/gitness/app/services/gitspaceevent" + "github.com/harness/gitness/events" + "github.com/harness/gitness/stream" +) + +const groupGitspaceOperationsEvents = "gitness:gitspaceoperations" + +type Service struct { + config *gitspaceevent.Config + orchestrator orchestrator.Orchestrator + gitspaceSvc *gitspace.Service + eventReporter *gitspaceevents.Reporter +} + +func NewService( + ctx context.Context, + config *gitspaceevent.Config, + gitspaceOperationsEventReaderFactory *events.ReaderFactory[*gitspaceoperationsevents.Reader], + orchestrator orchestrator.Orchestrator, + gitspaceSvc *gitspace.Service, + eventReporter *gitspaceevents.Reporter, +) (*Service, error) { + if err := config.Sanitize(); err != nil { + return nil, fmt.Errorf("provided gitspace operations event service config is invalid: %w", err) + } + service := &Service{ + config: config, + orchestrator: orchestrator, + gitspaceSvc: gitspaceSvc, + eventReporter: eventReporter, + } + + _, err := gitspaceOperationsEventReaderFactory.Launch(ctx, groupGitspaceOperationsEvents, config.EventReaderName, + func(r *gitspaceoperationsevents.Reader) error { + var idleTimeout = time.Duration(config.TimeoutInMins) * time.Minute + r.Configure( + stream.WithConcurrency(config.Concurrency), + stream.WithHandlerOptions( + stream.WithIdleTimeout(idleTimeout), + stream.WithMaxRetries(config.MaxRetries), + )) + + _ = r.RegisterGitspaceOperationsEvent(service.handleGitspaceOperationsEvent) + + return nil + }) + if err != nil { + return nil, fmt.Errorf("failed to launch gitspace operations event reader: %w", err) + } + + return service, nil +} diff --git a/app/services/gitspaceoperationsevent/wire.go b/app/services/gitspaceoperationsevent/wire.go new file mode 100644 index 000000000..bed5c1897 --- /dev/null +++ b/app/services/gitspaceoperationsevent/wire.go @@ -0,0 +1,51 @@ +// 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 gitspaceoperationsevent + +import ( + "context" + + gitspaceevents "github.com/harness/gitness/app/events/gitspace" + gitspaceoperationsevents "github.com/harness/gitness/app/events/gitspaceoperations" + "github.com/harness/gitness/app/gitspace/orchestrator" + "github.com/harness/gitness/app/services/gitspace" + "github.com/harness/gitness/app/services/gitspaceevent" + "github.com/harness/gitness/events" + + "github.com/google/wire" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideService, +) + +func ProvideService( + ctx context.Context, + config *gitspaceevent.Config, + gitspaceInfraEventReaderFactory *events.ReaderFactory[*gitspaceoperationsevents.Reader], + orchestrator orchestrator.Orchestrator, + gitspaceSvc *gitspace.Service, + eventReporter *gitspaceevents.Reporter, +) (*Service, error) { + return NewService( + ctx, + config, + gitspaceInfraEventReaderFactory, + orchestrator, + gitspaceSvc, + eventReporter, + ) +} diff --git a/app/services/gitspaceservice/wire.go b/app/services/gitspaceservice/wire.go index 6499992aa..48742185c 100644 --- a/app/services/gitspaceservice/wire.go +++ b/app/services/gitspaceservice/wire.go @@ -17,6 +17,7 @@ package gitspaceservice import ( "github.com/harness/gitness/app/services/gitspace" "github.com/harness/gitness/app/services/gitspaceinfraevent" + "github.com/harness/gitness/app/services/gitspaceoperationsevent" "github.com/harness/gitness/app/services/infraprovider" "github.com/google/wire" @@ -26,4 +27,5 @@ var WireSet = wire.NewSet( gitspace.WireSet, gitspaceinfraevent.WireSet, infraprovider.WireSet, + gitspaceoperationsevent.WireSet, ) diff --git a/app/services/wire.go b/app/services/wire.go index 112277056..a246ca92d 100644 --- a/app/services/wire.go +++ b/app/services/wire.go @@ -19,6 +19,7 @@ import ( "github.com/harness/gitness/app/services/gitspace" "github.com/harness/gitness/app/services/gitspaceevent" "github.com/harness/gitness/app/services/gitspaceinfraevent" + "github.com/harness/gitness/app/services/gitspaceoperationsevent" "github.com/harness/gitness/app/services/infraprovider" "github.com/harness/gitness/app/services/instrument" "github.com/harness/gitness/app/services/keywordsearch" @@ -57,10 +58,11 @@ type Services struct { } type GitspaceServices struct { - GitspaceEvent *gitspaceevent.Service - infraProvider *infraprovider.Service - gitspace *gitspace.Service - gitspaceInfraEventSvc *gitspaceinfraevent.Service + GitspaceEvent *gitspaceevent.Service + infraProvider *infraprovider.Service + gitspace *gitspace.Service + gitspaceInfraEventSvc *gitspaceinfraevent.Service + gitspaceOperationsEventSvc *gitspaceoperationsevent.Service } func ProvideGitspaceServices( @@ -68,12 +70,14 @@ func ProvideGitspaceServices( infraProviderSvc *infraprovider.Service, gitspaceSvc *gitspace.Service, gitspaceInfraEventSvc *gitspaceinfraevent.Service, + gitspaceOperationsEventSvc *gitspaceoperationsevent.Service, ) *GitspaceServices { return &GitspaceServices{ - GitspaceEvent: gitspaceEventSvc, - infraProvider: infraProviderSvc, - gitspace: gitspaceSvc, - gitspaceInfraEventSvc: gitspaceInfraEventSvc, + GitspaceEvent: gitspaceEventSvc, + infraProvider: infraProviderSvc, + gitspace: gitspaceSvc, + gitspaceInfraEventSvc: gitspaceInfraEventSvc, + gitspaceOperationsEventSvc: gitspaceOperationsEventSvc, } } diff --git a/app/store/database.go b/app/store/database.go index 1c68b9e9e..78a572ced 100644 --- a/app/store/database.go +++ b/app/store/database.go @@ -733,6 +733,7 @@ type ( Find(ctx context.Context, id int64) (*types.GitspaceInstance, error) // FindByIdentifier returns a gitspace instance given a gitspace instance identifier from the datastore. + // TODO: Fix this. It needs to use space ID as well. FindByIdentifier(ctx context.Context, identifier string) (*types.GitspaceInstance, error) // FindLatestByGitspaceConfigID returns the latest gitspace instance given a gitspace config ID from the datastore. diff --git a/app/store/database/migrate/postgres/0102_alter_table_delegate_provision_details_add_column_task_details.down.sql b/app/store/database/migrate/postgres/0102_alter_table_delegate_provision_details_add_column_task_details.down.sql new file mode 100644 index 000000000..d37628db4 --- /dev/null +++ b/app/store/database/migrate/postgres/0102_alter_table_delegate_provision_details_add_column_task_details.down.sql @@ -0,0 +1 @@ +ALTER TABLE delegate_provision_details DROP COLUMN dpdeta_task_details; \ No newline at end of file diff --git a/app/store/database/migrate/postgres/0102_alter_table_delegate_provision_details_add_column_task_details.up.sql b/app/store/database/migrate/postgres/0102_alter_table_delegate_provision_details_add_column_task_details.up.sql new file mode 100644 index 000000000..dad3ba0a8 --- /dev/null +++ b/app/store/database/migrate/postgres/0102_alter_table_delegate_provision_details_add_column_task_details.up.sql @@ -0,0 +1 @@ +ALTER TABLE delegate_provision_details ADD COLUMN dpdeta_task_details jsonb; \ No newline at end of file diff --git a/app/store/database/migrate/sqlite/0102_alter_table_delegate_provision_details_add_column_task_details.down.sql b/app/store/database/migrate/sqlite/0102_alter_table_delegate_provision_details_add_column_task_details.down.sql new file mode 100644 index 000000000..4f0e990e6 --- /dev/null +++ b/app/store/database/migrate/sqlite/0102_alter_table_delegate_provision_details_add_column_task_details.down.sql @@ -0,0 +1,21 @@ +DROP TABLE delegate_provision_details; + +CREATE TABLE delegate_provision_details +( + dpdeta_id INTEGER PRIMARY KEY AUTOINCREMENT, + dpdeta_task_id TEXT NOT NULL, + dpdeta_action_type TEXT NOT NULL, + dpdeta_gitspace_instance_id INTEGER NOT NULL, + dpdeta_space_id INTEGER NOT NULL, + dpdeta_agent_port INTEGER NOT NULL, + dpdeta_created BIGINT NOT NULL, + dpdeta_updated BIGINT NOT NULL, + CONSTRAINT fk_dpdeta_gitspace_instance_id FOREIGN KEY (dpdeta_gitspace_instance_id) + REFERENCES gitspaces (gits_id) MATCH SIMPLE + ON UPDATE NO ACTION + CONSTRAINT fk_dpdeta_space_id FOREIGN KEY (dpdeta_space_id) + REFERENCES spaces (space_id) MATCH SIMPLE + ON UPDATE NO ACTION +); + +CREATE UNIQUE INDEX delegate_provision_details_task_id_space_id ON delegate_provision_details (dpdeta_task_id, dpdeta_space_id); \ No newline at end of file diff --git a/app/store/database/migrate/sqlite/0102_alter_table_delegate_provision_details_add_column_task_details.up.sql b/app/store/database/migrate/sqlite/0102_alter_table_delegate_provision_details_add_column_task_details.up.sql new file mode 100644 index 000000000..f16e25368 --- /dev/null +++ b/app/store/database/migrate/sqlite/0102_alter_table_delegate_provision_details_add_column_task_details.up.sql @@ -0,0 +1 @@ +ALTER TABLE delegate_provision_details ADD COLUMN dpdeta_task_details TEXT; \ No newline at end of file diff --git a/cmd/gitness/wire.go b/cmd/gitness/wire.go index d687ba5ca..7ac6dab74 100644 --- a/cmd/gitness/wire.go +++ b/cmd/gitness/wire.go @@ -56,6 +56,7 @@ import ( gitevents "github.com/harness/gitness/app/events/git" gitspaceevents "github.com/harness/gitness/app/events/gitspace" gitspaceinfraevents "github.com/harness/gitness/app/events/gitspaceinfra" + gitspaceoperationsevents "github.com/harness/gitness/app/events/gitspaceoperations" pipelineevents "github.com/harness/gitness/app/events/pipeline" pullreqevents "github.com/harness/gitness/app/events/pullreq" repoevents "github.com/harness/gitness/app/events/repo" @@ -270,6 +271,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e ide.WireSet, gitspaceinfraevents.WireSet, gitspaceservice.WireSet, + gitspaceoperationsevents.WireSet, cliserver.ProvideGitspaceInfraProvisionerConfig, cliserver.ProvideIDEVSCodeConfig, cliserver.ProvideIDEJetBrainsConfig, diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index b0ba757f2..edf015c79 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -42,11 +42,12 @@ import ( "github.com/harness/gitness/app/auth/authz" "github.com/harness/gitness/app/bootstrap" "github.com/harness/gitness/app/connector" - events7 "github.com/harness/gitness/app/events/git" + events8 "github.com/harness/gitness/app/events/git" events3 "github.com/harness/gitness/app/events/gitspace" events4 "github.com/harness/gitness/app/events/gitspaceinfra" - events5 "github.com/harness/gitness/app/events/pipeline" - events6 "github.com/harness/gitness/app/events/pullreq" + events5 "github.com/harness/gitness/app/events/gitspaceoperations" + events6 "github.com/harness/gitness/app/events/pipeline" + events7 "github.com/harness/gitness/app/events/pullreq" events2 "github.com/harness/gitness/app/events/repo" "github.com/harness/gitness/app/gitspace/infrastructure" "github.com/harness/gitness/app/gitspace/logutil" @@ -76,6 +77,7 @@ import ( "github.com/harness/gitness/app/services/gitspace" "github.com/harness/gitness/app/services/gitspaceevent" "github.com/harness/gitness/app/services/gitspaceinfraevent" + "github.com/harness/gitness/app/services/gitspaceoperationsevent" "github.com/harness/gitness/app/services/importer" infraprovider2 "github.com/harness/gitness/app/services/infraprovider" "github.com/harness/gitness/app/services/instrument" @@ -121,7 +123,7 @@ import ( "github.com/harness/gitness/pubsub" api2 "github.com/harness/gitness/registry/app/api" "github.com/harness/gitness/registry/app/api/router" - events8 "github.com/harness/gitness/registry/app/events" + events9 "github.com/harness/gitness/registry/app/events" "github.com/harness/gitness/registry/app/pkg" "github.com/harness/gitness/registry/app/pkg/docker" "github.com/harness/gitness/registry/app/pkg/filemanager" @@ -332,7 +334,12 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro if err != nil { return nil, err } - containerOrchestrator := container.ProvideEmbeddedDockerOrchestrator(dockerClientFactory, statefulLogger, runargProvider) + reporter3, err := events5.ProvideReporter(eventsSystem) + if err != nil { + return nil, err + } + embeddedDockerOrchestrator := container.ProvideEmbeddedDockerOrchestrator(dockerClientFactory, statefulLogger, runargProvider, reporter3) + containerFactory := container.ProvideContainerOrchestratorFactory(embeddedDockerOrchestrator) orchestratorConfig := server.ProvideGitspaceOrchestratorConfig(config) vsCodeConfig := server.ProvideIDEVSCodeConfig(config) vsCode := ide.ProvideVSCodeService(vsCodeConfig) @@ -343,15 +350,15 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro ideFactory := ide.ProvideIDEFactory(vsCode, vsCodeWeb, v) passwordResolver := secret.ProvidePasswordResolver() resolverFactory := secret.ProvideResolverFactory(passwordResolver) - orchestratorOrchestrator := orchestrator.ProvideOrchestrator(scmSCM, platformConnector, infraProvisioner, containerOrchestrator, eventsReporter, orchestratorConfig, ideFactory, resolverFactory) + orchestratorOrchestrator := orchestrator.ProvideOrchestrator(scmSCM, platformConnector, infraProvisioner, containerFactory, eventsReporter, orchestratorConfig, ideFactory, resolverFactory, gitspaceInstanceStore) gitspaceService := gitspace.ProvideGitspace(transactor, gitspaceConfigStore, gitspaceInstanceStore, eventsReporter, gitspaceEventStore, spaceFinder, infraproviderService, orchestratorOrchestrator, scmSCM, config) usageMetricStore := database.ProvideUsageMetricStore(db) spaceController := space.ProvideController(config, transactor, provider, streamer, spaceIdentifier, authorizer, spacePathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore, listService, spaceFinder, repository, exporterRepository, resourceLimiter, publicaccessService, auditService, gitspaceService, labelService, instrumentService, executionStore, rulesService, usageMetricStore, repoIdentifier) - reporter3, err := events5.ProvideReporter(eventsSystem) + reporter4, err := events6.ProvideReporter(eventsSystem) if err != nil { return nil, err } - pipelineController := pipeline.ProvideController(triggerStore, authorizer, pipelineStore, reporter3, repoFinder) + pipelineController := pipeline.ProvideController(triggerStore, authorizer, pipelineStore, reporter4, repoFinder) secretController := secret2.ProvideController(encrypter, secretStore, authorizer, spaceFinder) triggerController := trigger.ProvideController(authorizer, triggerStore, pipelineStore, repoFinder) scmService := connector.ProvideSCMConnectorHandler(secretStore) @@ -365,25 +372,25 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro pullReqReviewerStore := database.ProvidePullReqReviewerStore(db, principalInfoCache) userGroupReviewersStore := database.ProvideUserGroupReviewerStore(db, principalInfoCache, userGroupStore) pullReqFileViewStore := database.ProvidePullReqFileViewStore(db) - reporter4, err := events6.ProvideReporter(eventsSystem) + reporter5, err := events7.ProvideReporter(eventsSystem) if err != nil { return nil, err } migrator := codecomments.ProvideMigrator(gitInterface) - readerFactory, err := events7.ProvideReaderFactory(eventsSystem) + readerFactory, err := events8.ProvideReaderFactory(eventsSystem) if err != nil { return nil, err } - eventsReaderFactory, err := events6.ProvideReaderFactory(eventsSystem) + eventsReaderFactory, err := events7.ProvideReaderFactory(eventsSystem) if err != nil { return nil, err } - pullreqService, err := pullreq.ProvideService(ctx, config, readerFactory, eventsReaderFactory, reporter4, gitInterface, repoFinder, repoStore, pullReqStore, pullReqActivityStore, principalInfoCache, codeCommentView, migrator, pullReqFileViewStore, pubSub, provider, streamer) + pullreqService, err := pullreq.ProvideService(ctx, config, readerFactory, eventsReaderFactory, reporter5, gitInterface, repoFinder, repoStore, pullReqStore, pullReqActivityStore, principalInfoCache, codeCommentView, migrator, pullReqFileViewStore, pubSub, provider, streamer) if err != nil { return nil, err } pullReq := migrate.ProvidePullReqImporter(provider, gitInterface, principalStore, spaceStore, repoStore, pullReqStore, pullReqActivityStore, labelStore, labelValueStore, pullReqLabelAssignmentStore, repoFinder, transactor, mutexManager) - pullreqController := pullreq2.ProvideController(transactor, provider, authorizer, auditService, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, userGroupStore, userGroupReviewersStore, principalInfoCache, pullReqFileViewStore, membershipStore, checkStore, gitInterface, repoFinder, reporter4, migrator, pullreqService, listService, protectionManager, streamer, codeownersService, lockerLocker, pullReq, labelService, instrumentService, searchService) + pullreqController := pullreq2.ProvideController(transactor, provider, authorizer, auditService, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, userGroupStore, userGroupReviewersStore, principalInfoCache, pullReqFileViewStore, membershipStore, checkStore, gitInterface, repoFinder, reporter5, migrator, pullreqService, listService, protectionManager, streamer, codeownersService, lockerLocker, pullReq, labelService, instrumentService, searchService) webhookConfig := server.ProvideWebhookConfig(config) webhookStore := database.ProvideWebhookStore(db) webhookExecutionStore := database.ProvideWebhookExecutionStore(db) @@ -395,7 +402,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro } preprocessor := webhook2.ProvidePreprocessor() webhookController := webhook2.ProvideController(authorizer, spaceFinder, repoFinder, webhookService, encrypter, preprocessor) - reporter5, err := events7.ProvideReporter(eventsSystem) + reporter6, err := events8.ProvideReporter(eventsSystem) if err != nil { return nil, err } @@ -412,7 +419,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro return nil, err } lfsObjectStore := database.ProvideLFSObjectStore(db) - githookController := githook.ProvideController(authorizer, principalStore, repoStore, repoFinder, reporter5, reporter, gitInterface, pullReqStore, provider, protectionManager, clientFactory, resourceLimiter, settingsService, preReceiveExtender, updateExtender, postReceiveExtender, streamer, lfsObjectStore) + githookController := githook.ProvideController(authorizer, principalStore, repoStore, repoFinder, reporter6, reporter, gitInterface, pullReqStore, provider, protectionManager, clientFactory, resourceLimiter, settingsService, preReceiveExtender, updateExtender, postReceiveExtender, streamer, lfsObjectStore) serviceaccountController := serviceaccount.NewController(principalUID, authorizer, principalStore, spaceStore, repoStore, tokenStore) principalController := principal.ProvideController(principalStore, authorizer) usergroupController := usergroup2.ProvideController(userGroupStore, spaceStore, authorizer, searchService) @@ -457,11 +464,11 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro layerRepository := database2.ProvideLayerDao(db, mediaTypesRepository) eventReporter := docker.ProvideReporter() ociImageIndexMappingRepository := database2.ProvideOCIImageIndexMappingDao(db) - reporter6, err := events8.ProvideArtifactReporter(eventsSystem) + reporter7, err := events9.ProvideArtifactReporter(eventsSystem) if err != nil { return nil, err } - manifestService := docker.ManifestServiceProvider(registryRepository, manifestRepository, blobRepository, mediaTypesRepository, manifestReferenceRepository, tagRepository, imageRepository, artifactRepository, layerRepository, gcService, transactor, eventReporter, spaceFinder, ociImageIndexMappingRepository, reporter6, provider) + manifestService := docker.ManifestServiceProvider(registryRepository, manifestRepository, blobRepository, mediaTypesRepository, manifestReferenceRepository, tagRepository, imageRepository, artifactRepository, layerRepository, gcService, transactor, eventReporter, spaceFinder, ociImageIndexMappingRepository, reporter7, provider) registryBlobRepository := database2.ProvideRegistryBlobDao(db) bandwidthStatRepository := database2.ProvideBandwidthStatDao(db) downloadStatRepository := database2.ProvideDownloadStatDao(db) @@ -481,7 +488,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro cleanupPolicyRepository := database2.ProvideCleanupPolicyDao(db, transactor) webhooksRepository := database2.ProvideWebhookDao(db) webhooksExecutionRepository := database2.ProvideWebhookExecutionDao(db) - readerFactory2, err := events8.ProvideReaderFactory(eventsSystem) + readerFactory2, err := events9.ProvideReaderFactory(eventsSystem) if err != nil { return nil, err } @@ -513,7 +520,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro serverServer := server2.ProvideServer(config, routerRouter) publickeyService := publickey.ProvidePublicKey(publicKeyStore, principalInfoCache) sshServer := ssh.ProvideServer(config, publickeyService, repoController, lfsController) - executionManager := manager.ProvideExecutionManager(config, executionStore, pipelineStore, provider, streamer, fileService, converterService, logStore, logStream, checkStore, repoStore, schedulerScheduler, secretStore, stageStore, stepStore, principalStore, publicaccessService, reporter3) + executionManager := manager.ProvideExecutionManager(config, executionStore, pipelineStore, provider, streamer, fileService, converterService, logStore, logStream, checkStore, repoStore, schedulerScheduler, secretStore, stageStore, stepStore, principalStore, publicaccessService, reporter4) client := manager.ProvideExecutionClient(executionManager, provider, config) resolverManager := resolver.ProvideResolver(config, pluginStore, templateStore, executionStore, repoStore) runtimeRunner, err := runner.ProvideExecutionRunner(config, client, resolverManager) @@ -576,7 +583,15 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro if err != nil { return nil, err } - gitspaceServices := services.ProvideGitspaceServices(gitspaceeventService, infraproviderService, gitspaceService, gitspaceinfraeventService) + readerFactory6, err := events5.ProvideReaderFactory(eventsSystem) + if err != nil { + return nil, err + } + gitspaceoperationseventService, err := gitspaceoperationsevent.ProvideService(ctx, gitspaceeventConfig, readerFactory6, orchestratorOrchestrator, gitspaceService, eventsReporter) + if err != nil { + return nil, err + } + gitspaceServices := services.ProvideGitspaceServices(gitspaceeventService, infraproviderService, gitspaceService, gitspaceinfraeventService, gitspaceoperationseventService) consumer, err := instrument.ProvideGitConsumer(ctx, config, readerFactory, repoStore, principalInfoCache, instrumentService) if err != nil { return nil, err diff --git a/events/reporter.go b/events/reporter.go index 749e94c73..470f40bf0 100644 --- a/events/reporter.go +++ b/events/reporter.go @@ -20,6 +20,8 @@ import ( "encoding/gob" "fmt" "time" + + "github.com/harness/gitness/app/gitspace/orchestrator/container/response" ) // GenericReporter represents an event reporter that supports sending typesafe messages @@ -46,6 +48,8 @@ func ReporterSendEvent[T interface{}](reporter *GenericReporter, ctx context.Con buff := &bytes.Buffer{} encoder := gob.NewEncoder(buff) + gob.Register((*response.StartResponse)(nil)) + gob.Register((*response.DeleteResponse)(nil)) if err := encoder.Encode(&event); err != nil { return "", fmt.Errorf("failed to encode payload: %w", err) diff --git a/infraprovider/docker_provider.go b/infraprovider/docker_provider.go index faa1d5fc1..1228062b0 100644 --- a/infraprovider/docker_provider.go +++ b/infraprovider/docker_provider.go @@ -59,7 +59,6 @@ func (d DockerProvider) Provision( _ int, requiredGitspacePorts []types.GitspacePort, inputParameters []types.InfraProviderParameter, - _ map[string]any, ) error { dockerClient, err := d.dockerClientFactory.NewDockerClient(ctx, types.Infrastructure{ ProviderType: enum.InfraProviderTypeDocker, @@ -125,11 +124,7 @@ func (d DockerProvider) Find( spaceID int64, spacePath string, gitspaceConfigIdentifier string, - _ string, - _ int, - _ []types.GitspacePort, inputParameters []types.InfraProviderParameter, - _ map[string]any, ) (*types.Infrastructure, error) { dockerClient, err := d.dockerClientFactory.NewDockerClient(ctx, types.Infrastructure{ ProviderType: enum.InfraProviderTypeDocker, @@ -160,6 +155,13 @@ func (d DockerProvider) Find( return infrastructure, nil } +func (d DockerProvider) FindInfraStatus(_ context.Context, + _ string, + _ string, + _ []types.InfraProviderParameter) (*enum.InfraStatus, error) { + return nil, nil //nolint:nilnil +} + // Stop is NOOP as this provider uses already running docker engine. It does not stop the docker engine. func (d DockerProvider) Stop(ctx context.Context, infra types.Infrastructure) error { infra.Status = enum.InfraStatusDestroyed diff --git a/infraprovider/infra_provider.go b/infraprovider/infra_provider.go index f20d67ac4..8a4efbdb4 100644 --- a/infraprovider/infra_provider.go +++ b/infraprovider/infra_provider.go @@ -32,7 +32,6 @@ type InfraProvider interface { agentPort int, requiredGitspacePorts []types.GitspacePort, inputParameters []types.InfraProviderParameter, - configMetadata map[string]any, ) error // Find finds infrastructure provisioned against a gitspace. @@ -41,13 +40,16 @@ type InfraProvider interface { spaceID int64, spacePath string, gitspaceConfigIdentifier string, - gitspaceInstanceIdentifier string, - agentPort int, - requiredGitspacePorts []types.GitspacePort, inputParameters []types.InfraProviderParameter, - configMetadata map[string]any, ) (*types.Infrastructure, error) + FindInfraStatus( + ctx context.Context, + gitspaceConfigIdentifier string, + gitspaceInstanceIdentifier string, + inputParameters []types.InfraProviderParameter, + ) (*enum.InfraStatus, error) + // Stop frees up the resources allocated against a gitspace, which can be freed. Stop(ctx context.Context, infra types.Infrastructure) error diff --git a/types/enum/gitspace_operations_event.go b/types/enum/gitspace_operations_event.go new file mode 100644 index 000000000..a37f1a50e --- /dev/null +++ b/types/enum/gitspace_operations_event.go @@ -0,0 +1,31 @@ +// 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 enum + +type GitspaceOperationsEvent string + +func (GitspaceOperationsEvent) Enum() []interface{} { + return toInterfaceSlice(gitspaceOperationsEvent) +} + +var gitspaceOperationsEvent = []GitspaceOperationsEvent{ + GitspaceOperationsEventStart, GitspaceOperationsEventStop, GitspaceOperationsEventDelete, +} + +const ( + GitspaceOperationsEventStart GitspaceOperationsEvent = "start" + GitspaceOperationsEventStop GitspaceOperationsEvent = "stop" + GitspaceOperationsEventDelete GitspaceOperationsEvent = "delete" +) diff --git a/types/infrastructure.go b/types/infrastructure.go index cea65abc0..d9c209999 100644 --- a/types/infrastructure.go +++ b/types/infrastructure.go @@ -48,6 +48,10 @@ type InstanceInfo struct { PoolName string `json:"pool_name"` Zone string `json:"zone"` StorageIdentifier string `json:"storage_identifier"` + CAKey []byte `json:"ca_key"` + CACert []byte `json:"ca_cert"` + TLSKey []byte `json:"tls_key"` + TLSCert []byte `json:"tls_cert"` } type Infrastructure struct { @@ -65,6 +69,8 @@ type Infrastructure struct { ProviderType enum.InfraProviderType // InputParameters which are required by the provider to provision the infra. InputParameters []InfraProviderParameter + // ConfigMetadata contains the infra config metadata required by the infra provider to provision the infra. + ConfigMetadata map[string]any // Status of the infra. Status enum.InfraStatus