diff --git a/app/gitspace/orchestrator/container/embedded_docker_core.go b/app/gitspace/orchestrator/container/devcontainer_setup.go similarity index 71% rename from app/gitspace/orchestrator/container/embedded_docker_core.go rename to app/gitspace/orchestrator/container/devcontainer_setup.go index a873bfa8c..524b58afe 100644 --- a/app/gitspace/orchestrator/container/embedded_docker_core.go +++ b/app/gitspace/orchestrator/container/devcontainer_setup.go @@ -22,10 +22,7 @@ import ( "strconv" "strings" - "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" - "github.com/harness/gitness/app/gitspace/orchestrator/ide" orchestratorTypes "github.com/harness/gitness/app/gitspace/orchestrator/types" - "github.com/harness/gitness/app/gitspace/scm" "github.com/harness/gitness/types" "github.com/docker/docker/api/types/container" @@ -42,6 +39,14 @@ const ( catchAllIP = "0.0.0.0" ) +var containerStateMapping = map[string]State{ + "running": ContainerStateRunning, + "exited": ContainerStateStopped, + "dead": ContainerStateDead, + "created": ContainerStateCreated, + "paused": ContainerStatePaused, +} + // Helper function to log messages and handle error wrapping. func logStreamWrapError(gitspaceLogger orchestratorTypes.GitspaceLogger, msg string, err error) error { gitspaceLogger.Error(msg, err) @@ -49,7 +54,7 @@ func logStreamWrapError(gitspaceLogger orchestratorTypes.GitspaceLogger, msg str } // Generalized Docker Container Management. -func (e *EmbeddedDockerOrchestrator) manageContainer( +func ManageContainer( ctx context.Context, action Action, containerName string, @@ -83,7 +88,7 @@ func (e *EmbeddedDockerOrchestrator) manageContainer( return nil } -func (e *EmbeddedDockerOrchestrator) containerState( +func FetchContainerState( ctx context.Context, containerName string, dockerClient *client.Client, @@ -99,12 +104,22 @@ func (e *EmbeddedDockerOrchestrator) containerState( if len(containers) == 0 { return ContainerStateRemoved, nil } + containerState := ContainerStateUnknown + for _, value := range containers { + name, _ := strings.CutPrefix(value.Names[0], "/") + if name == containerName { + if state, ok := containerStateMapping[value.State]; ok { + containerState = state + } + break + } + } - return State(containers[0].State), nil + return containerState, nil } // Create a new Docker container. -func (e *EmbeddedDockerOrchestrator) createContainer( +func CreateContainer( ctx context.Context, dockerClient *client.Client, imageName string, @@ -174,7 +189,7 @@ func prepareHostConfig(volumeName, homeDir string, portBindings nat.PortMap) *co return hostConfig } -func (e *EmbeddedDockerOrchestrator) getContainerInfo( +func GetContainerInfo( ctx context.Context, containerName string, dockerClient *client.Client, @@ -201,7 +216,7 @@ func (e *EmbeddedDockerOrchestrator) getContainerInfo( return inspectResp.ID, usedPorts, nil } -func (e *EmbeddedDockerOrchestrator) pullImage( +func PullImage( ctx context.Context, imageName string, dockerClient *client.Client, @@ -230,101 +245,15 @@ func (e *EmbeddedDockerOrchestrator) pullImage( return nil } -func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps( - ctx context.Context, - gitspaceConfig types.GitspaceConfig, - dockerClient *client.Client, - ideService ide.IDE, - infrastructure types.Infrastructure, - resolvedRepoDetails scm.ResolvedDetails, - defaultBaseImage string, - gitspaceLogger orchestratorTypes.GitspaceLogger, -) error { - homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier) - containerName := GetGitspaceContainerName(gitspaceConfig) - - devcontainerConfig := resolvedRepoDetails.DevcontainerConfig - imageName := devcontainerConfig.Image - if imageName == "" { - imageName = defaultBaseImage - } - - // Pull the required image - if err := e.pullImage(ctx, imageName, dockerClient, gitspaceLogger); err != nil { - return err - } - portMappings := infrastructure.GitspacePortMappings - forwardPorts := ExtractForwardPorts(devcontainerConfig) - if len(forwardPorts) > 0 { - for _, port := range forwardPorts { - portMappings[port] = &types.PortMapping{ - PublishedPort: port, - ForwardedPort: port, - } - } - gitspaceLogger.Info(fmt.Sprintf("Forwarding ports : %v", forwardPorts)) - } - - storage := infrastructure.Storage - environment := ExtractEnv(devcontainerConfig) - if len(environment) > 0 { - gitspaceLogger.Info(fmt.Sprintf("Setting Environment : %v", environment)) - } - // Create the container - err := e.createContainer( - ctx, - dockerClient, - imageName, - containerName, - gitspaceLogger, - storage, - homeDir, - portMappings, - environment, - ) - if err != nil { - return err - } - - // Start the container - if err := e.manageContainer(ctx, ContainerActionStart, containerName, dockerClient, gitspaceLogger); err != nil { - return err - } - - // Setup and run commands - exec := &devcontainer.Exec{ - ContainerName: containerName, - DockerClient: dockerClient, - HomeDir: homeDir, - UserIdentifier: gitspaceConfig.GitspaceUser.Identifier, - AccessKey: *gitspaceConfig.GitspaceInstance.AccessKey, - AccessType: gitspaceConfig.GitspaceInstance.AccessType, - } - - if err := e.setupGitspaceAndIDE( - ctx, - exec, - gitspaceLogger, - ideService, - gitspaceConfig, - resolvedRepoDetails, - defaultBaseImage, - ); err != nil { - return err - } - - return nil -} - // getContainerResponse retrieves container information and prepares the start response. -func (e *EmbeddedDockerOrchestrator) getContainerResponse( +func GetContainerResponse( ctx context.Context, dockerClient *client.Client, containerName string, portMappings map[int]*types.PortMapping, codeRepoDir string, ) (*StartResponse, error) { - id, ports, err := e.getContainerInfo(ctx, containerName, dockerClient, portMappings) + id, ports, err := GetContainerInfo(ctx, containerName, dockerClient, portMappings) if err != nil { return nil, err } diff --git a/app/gitspace/orchestrator/container/embedded_docker_provider.go b/app/gitspace/orchestrator/container/embedded_docker_provider.go index 8522e3d8d..9ef46384b 100644 --- a/app/gitspace/orchestrator/container/embedded_docker_provider.go +++ b/app/gitspace/orchestrator/container/embedded_docker_provider.go @@ -156,6 +156,9 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace( ideService); err != nil { return nil, 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) default: return nil, fmt.Errorf("gitspace %s is in a bad state: %s", containerName, state) @@ -165,7 +168,7 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace( codeRepoDir := filepath.Join(homeDir, resolvedRepoDetails.RepoName) // Step 5: Retrieve container information and return response - return e.getContainerResponse(ctx, dockerClient, containerName, infra.GitspacePortMappings, codeRepoDir) + return GetContainerResponse(ctx, dockerClient, containerName, infra.GitspacePortMappings, codeRepoDir) } // startStoppedGitspace starts the Gitspace container if it was stopped. @@ -185,7 +188,7 @@ func (e *EmbeddedDockerOrchestrator) startStoppedGitspace( } defer e.flushLogStream(logStreamInstance, gitspaceConfig.ID) - startErr := e.manageContainer(ctx, ContainerActionStart, containerName, dockerClient, logStreamInstance) + startErr := ManageContainer(ctx, ContainerActionStart, containerName, dockerClient, logStreamInstance) if startErr != nil { return startErr } @@ -260,7 +263,9 @@ func (e *EmbeddedDockerOrchestrator) StopGitspace( if err := e.stopRunningGitspace(ctx, gitspaceConfig, containerName, dockerClient); err != nil { return err } - + case ContainerStatePaused, ContainerStateCreated, ContainerStateUnknown, ContainerStateDead: + // TODO handle the following states + return fmt.Errorf("gitspace %s is in a unhandled state: %s", containerName, state) default: return fmt.Errorf("gitspace %s is in a bad state: %s", containerName, state) } @@ -284,7 +289,7 @@ func (e *EmbeddedDockerOrchestrator) stopRunningGitspace( defer e.flushLogStream(logStreamInstance, gitspaceConfig.ID) // Step 5: Stop the container - return e.manageContainer(ctx, ContainerActionStop, containerName, dockerClient, logStreamInstance) + return ManageContainer(ctx, ContainerActionStop, containerName, dockerClient, logStreamInstance) } // Status is NOOP for EmbeddedDockerOrchestrator as the docker host is verified by the infra provisioner. @@ -331,7 +336,7 @@ func (e *EmbeddedDockerOrchestrator) StopAndRemoveGitspace( // Step 5: Stop the container if it's not already stopped if state != ContainerStateStopped { logger.Debug().Msg("stopping gitspace") - if err := e.manageContainer( + if err := ManageContainer( ctx, ContainerActionStop, containerName, dockerClient, logStreamInstance); err != nil { return fmt.Errorf("failed to stop gitspace %s: %w", containerName, err) } @@ -340,7 +345,7 @@ func (e *EmbeddedDockerOrchestrator) StopAndRemoveGitspace( // Step 6: Remove the container logger.Debug().Msg("removing gitspace") - if err := e.manageContainer( + if err := ManageContainer( ctx, ContainerActionRemove, containerName, dockerClient, logStreamInstance); err != nil { return fmt.Errorf("failed to remove gitspace %s: %w", containerName, err) } @@ -364,6 +369,92 @@ func (e *EmbeddedDockerOrchestrator) getAccessKey(gitspaceConfig types.GitspaceC return "", fmt.Errorf("no access key is configured: %s", gitspaceConfig.Identifier) } +func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps( + ctx context.Context, + gitspaceConfig types.GitspaceConfig, + dockerClient *client.Client, + ideService ide.IDE, + infrastructure types.Infrastructure, + resolvedRepoDetails scm.ResolvedDetails, + defaultBaseImage string, + gitspaceLogger orchestratorTypes.GitspaceLogger, +) error { + homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier) + containerName := GetGitspaceContainerName(gitspaceConfig) + + devcontainerConfig := resolvedRepoDetails.DevcontainerConfig + imageName := devcontainerConfig.Image + if imageName == "" { + imageName = defaultBaseImage + } + + // Pull the required image + if err := PullImage(ctx, imageName, dockerClient, gitspaceLogger); err != nil { + return err + } + portMappings := infrastructure.GitspacePortMappings + forwardPorts := ExtractForwardPorts(devcontainerConfig) + if len(forwardPorts) > 0 { + for _, port := range forwardPorts { + portMappings[port] = &types.PortMapping{ + PublishedPort: port, + ForwardedPort: port, + } + } + gitspaceLogger.Info(fmt.Sprintf("Forwarding ports : %v", forwardPorts)) + } + + storage := infrastructure.Storage + environment := ExtractEnv(devcontainerConfig) + if len(environment) > 0 { + gitspaceLogger.Info(fmt.Sprintf("Setting Environment : %v", environment)) + } + // Create the container + err := CreateContainer( + ctx, + dockerClient, + imageName, + containerName, + gitspaceLogger, + storage, + homeDir, + portMappings, + environment, + ) + if err != nil { + return err + } + + // Start the container + if err := ManageContainer(ctx, ContainerActionStart, containerName, dockerClient, gitspaceLogger); err != nil { + return err + } + + // Setup and run commands + exec := &devcontainer.Exec{ + ContainerName: containerName, + DockerClient: dockerClient, + HomeDir: homeDir, + UserIdentifier: gitspaceConfig.GitspaceUser.Identifier, + AccessKey: *gitspaceConfig.GitspaceInstance.AccessKey, + AccessType: gitspaceConfig.GitspaceInstance.AccessType, + } + + if err := e.setupGitspaceAndIDE( + ctx, + exec, + gitspaceLogger, + ideService, + gitspaceConfig, + resolvedRepoDetails, + defaultBaseImage, + ); err != nil { + return err + } + + return nil +} + // getDockerClient creates and returns a new Docker client using the factory. func (e *EmbeddedDockerOrchestrator) getDockerClient( ctx context.Context, @@ -390,7 +481,7 @@ func (e *EmbeddedDockerOrchestrator) checkContainerState( containerName string, ) (State, error) { log.Debug().Msg("checking current state of gitspace") - state, err := e.containerState(ctx, containerName, dockerClient) + state, err := FetchContainerState(ctx, containerName, dockerClient) if err != nil { return "", err } diff --git a/app/gitspace/orchestrator/container/types.go b/app/gitspace/orchestrator/container/types.go index 9dc4f6fe5..bfb0e91ac 100644 --- a/app/gitspace/orchestrator/container/types.go +++ b/app/gitspace/orchestrator/container/types.go @@ -33,7 +33,11 @@ type State string const ( ContainerStateRunning = State("running") ContainerStateRemoved = State("removed") + ContainerStateDead = State("dead") ContainerStateStopped = State("exited") + ContainerStatePaused = State("paused") + ContainerStateUnknown = State("unknown") + ContainerStateCreated = State("created") ) type Action string