drone/app/gitspace/orchestrator/orchestrator_impl.go
Ansuman Satapathy 18226c8914 feat: [CDE-137]: check valid code repository for CDE use. (#2197)
* feat: [CDE-137]: check valid code repository for CDE use.
* feat: [CDE-137]: check valid code repository for CDE use.
* feat: [CDE-137]: check valid code repository for CDE use.
2024-07-11 05:47:29 +00:00

309 lines
11 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 orchestrator
import (
"context"
"fmt"
"net/url"
"time"
events "github.com/harness/gitness/app/events/gitspace"
"github.com/harness/gitness/app/gitspace/infrastructure"
"github.com/harness/gitness/app/gitspace/orchestrator/container"
"github.com/harness/gitness/app/gitspace/scm"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type orchestrator struct {
scm scm.SCM
infraProviderResourceStore store.InfraProviderResourceStore
infraProvisioner infrastructure.InfraProvisioner
containerOrchestrator container.Orchestrator
eventReporter *events.Reporter
}
var _ Orchestrator = (*orchestrator)(nil)
func NewOrchestrator(
scm scm.SCM,
infraProviderResourceStore store.InfraProviderResourceStore,
infraProvisioner infrastructure.InfraProvisioner,
containerOrchestrator container.Orchestrator,
eventReporter *events.Reporter,
) Orchestrator {
return orchestrator{
scm: scm,
infraProviderResourceStore: infraProviderResourceStore,
infraProvisioner: infraProvisioner,
containerOrchestrator: containerOrchestrator,
eventReporter: eventReporter,
}
}
func (o orchestrator) StartGitspace(
ctx context.Context,
gitspaceConfig *types.GitspaceConfig,
) (*types.GitspaceInstance, error) {
gitspaceInstance := gitspaceConfig.GitspaceInstance
gitspaceInstance.State = enum.GitspaceInstanceStateError
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchDevcontainerStart)
devcontainerConfig, err := o.scm.DevcontainerConfig(ctx, gitspaceConfig)
if err != nil {
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchDevcontainerFailed)
log.Warn().Err(err).Msg("devcontainer config fetch failed.")
}
if devcontainerConfig == nil {
log.Warn().Err(err).Msg("devcontainer config is nil, using empty config")
devcontainerConfig = &types.DevcontainerConfig{}
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchDevcontainerCompleted)
infraProviderResource, err := o.infraProviderResourceStore.Find(ctx, gitspaceConfig.InfraProviderResourceID)
if err != nil {
return gitspaceInstance, fmt.Errorf("cannot get the infraprovider resource for ID %d: %w",
gitspaceConfig.InfraProviderResourceID, err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraProvisioningStart)
infra, err := o.infraProvisioner.Provision(ctx, infraProviderResource, gitspaceConfig)
if err != nil {
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraProvisioningFailed)
return gitspaceInstance, fmt.Errorf(
"cannot provision infrastructure for ID %d: %w", gitspaceConfig.InfraProviderResourceID, err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraProvisioningCompleted)
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectStart)
err = o.containerOrchestrator.Status(ctx, infra)
gitspaceInstance.State = enum.GitspaceInstanceStateError
if err != nil {
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectFailed)
return gitspaceInstance, fmt.Errorf("couldn't call the agent health API: %w", err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectCompleted)
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceCreationStart)
startResponse, err := o.containerOrchestrator.StartGitspace(ctx, gitspaceConfig, devcontainerConfig, infra)
if err != nil {
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceCreationFailed)
return gitspaceInstance, fmt.Errorf("couldn't call the agent start API: %w", err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceCreationCompleted)
repoName, err := o.scm.RepositoryName(ctx, gitspaceConfig)
if err != nil {
log.Warn().Err(err).Msg("failed to fetch repository name.")
}
port := startResponse.PortsUsed[gitspaceConfig.IDE]
var ideURL url.URL
if infra.Host == "" {
// TODO: This fix does not cover all use-cases. Ideally, we need to read the host name
// on which this docker is running and set it as the infra.Host. Remove once that change is done.
infra.Host = "localhost"
}
if gitspaceConfig.IDE == enum.IDETypeVSCodeWeb {
ideURL = url.URL{
Scheme: "http",
Host: infra.Host + ":" + port,
RawQuery: "folder=" + startResponse.WorkingDirectory + "/" + repoName,
}
} else if gitspaceConfig.IDE == enum.IDETypeVSCode {
// TODO: the following user ID is hard coded and should be changed.
ideURL = url.URL{
Scheme: "vscode-remote",
Host: "", // Empty since we include the host and port in the path
Path: fmt.Sprintf(
"ssh-remote+%s@%s:%s/%s/%s",
"harness",
infra.Host,
port,
startResponse.WorkingDirectory,
repoName,
),
}
}
ideURLString := ideURL.String()
gitspaceInstance.URL = &ideURLString
gitspaceInstance.LastUsed = time.Now().UnixMilli()
gitspaceInstance.State = enum.GitspaceInstanceStateRunning
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeGitspaceActionStartCompleted)
return gitspaceInstance, nil
}
func (o orchestrator) StopGitspace(
ctx context.Context,
gitspaceConfig *types.GitspaceConfig,
) (*types.GitspaceInstance, error) {
gitspaceInstance := gitspaceConfig.GitspaceInstance
gitspaceInstance.State = enum.GitspaceInstanceStateError
infraProviderResource, err := o.infraProviderResourceStore.Find(ctx, gitspaceConfig.InfraProviderResourceID)
if err != nil {
return nil, fmt.Errorf(
"cannot get the infraProviderResource with ID %d: %w", gitspaceConfig.InfraProviderResourceID, err)
}
infra, err := o.infraProvisioner.Find(ctx, infraProviderResource, gitspaceConfig)
if err != nil {
return gitspaceInstance, fmt.Errorf("cannot find the provisioned infra: %w", err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectStart)
err = o.containerOrchestrator.Status(ctx, infra)
if err != nil {
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectFailed)
return gitspaceConfig.GitspaceInstance, fmt.Errorf("couldn't call the agent health API: %w", err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectCompleted)
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceDeletionStart)
err = o.containerOrchestrator.StopGitspace(ctx, gitspaceConfig, infra)
if err != nil {
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceDeletionFailed)
return gitspaceInstance, fmt.Errorf("error stopping the Gitspace container: %w", err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceDeletionCompleted)
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraUnprovisioningStart)
_, err = o.infraProvisioner.Stop(ctx, infraProviderResource, gitspaceConfig)
if err != nil {
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraUnprovisioningFailed)
return gitspaceInstance, fmt.Errorf(
"cannot stop provisioned infrastructure with ID %d: %w", gitspaceConfig.InfraProviderResourceID, err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraUnprovisioningCompleted)
gitspaceInstance.State = enum.GitspaceInstanceStateDeleted
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeGitspaceActionStopCompleted)
return gitspaceInstance, err
}
func (o orchestrator) DeleteGitspace(
ctx context.Context,
gitspaceConfig *types.GitspaceConfig,
) (*types.GitspaceInstance, error) {
gitspaceInstance := gitspaceConfig.GitspaceInstance
currentState := gitspaceInstance.State
gitspaceInstance.State = enum.GitspaceInstanceStateError
infraProviderResource, err := o.infraProviderResourceStore.Find(ctx, gitspaceConfig.InfraProviderResourceID)
if err != nil {
return nil, fmt.Errorf(
"cannot get the infraProviderResource with ID %d: %w", gitspaceConfig.InfraProviderResourceID, err)
}
if currentState == enum.GitspaceInstanceStateRunning ||
currentState == enum.GitspaceInstanceStateUnknown {
infra, err := o.infraProvisioner.Find(ctx, infraProviderResource, gitspaceConfig)
if err != nil {
return nil, fmt.Errorf("cannot find the provisioned infra: %w", err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectStart)
err = o.containerOrchestrator.Status(ctx, infra)
if err != nil {
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectFailed)
return gitspaceConfig.GitspaceInstance, fmt.Errorf("couldn't call the agent health API: %w", err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentConnectCompleted)
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceDeletionStart)
err = o.containerOrchestrator.StopGitspace(ctx, gitspaceConfig, infra)
if err != nil {
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceDeletionFailed)
return nil, fmt.Errorf("error stopping the Gitspace container: %w", err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceDeletionCompleted)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraUnprovisioningStart)
_, err = o.infraProvisioner.Unprovision(ctx, infraProviderResource, gitspaceConfig)
if err != nil {
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraUnprovisioningFailed)
return nil, fmt.Errorf(
"cannot stop provisioned infrastructure with ID %d: %w", gitspaceConfig.InfraProviderResourceID, err)
}
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeInfraUnprovisioningCompleted)
gitspaceInstance.State = enum.GitspaceInstanceStateDeleted
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeGitspaceActionStopCompleted)
return gitspaceInstance, nil
}
func (o orchestrator) emitGitspaceEvent(
ctx context.Context,
config *types.GitspaceConfig,
eventType enum.GitspaceEventType,
) {
o.eventReporter.EmitGitspaceEvent(
ctx,
events.GitspaceEvent,
&events.GitspaceEventPayload{
QueryKey: config.Identifier,
EntityID: config.GitspaceInstance.ID,
EntityType: enum.GitspaceEntityTypeGitspaceInstance,
EventType: eventType,
Created: time.Now().UnixMilli(),
})
}