mirror of https://github.com/harness/drone.git
feat: [CDE-92]: Container orchestrator and IDE service
* feat: [CDE-92]: Removing Client method from infraprovider interface and using DockerClientFactory to generate docker clients. Using go templates to substitute values in gitspace scripts. Setting a default base path for gitspaces if not provided. * Rebasing * feat: [CDE-92]: Addressing review comments. * Rebasing * Rebasing * feat: [CDE-92]: Addressing review comments * Rebasing * feat: [CDE-92]: Using port from config for code-server installation * feat: [CDE-92]: Initial commit * Rebasingunified-ui
parent
4833ed67a5
commit
3acded8ed8
|
@ -0,0 +1,40 @@
|
|||
// 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 (
|
||||
"context"
|
||||
|
||||
"github.com/harness/gitness/infraprovider"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
type Orchestrator interface {
|
||||
// StartGitspace starts the gitspace container using the specified image or default image, clones the code,
|
||||
// runs SSH server and installs the IDE inside the container. It returns a map of the ports used by the Gitspace.
|
||||
StartGitspace(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
devcontainerConfig *types.DevcontainerConfig,
|
||||
infra *infraprovider.Infrastructure,
|
||||
) (map[enum.IDEType]string, error)
|
||||
|
||||
// StopGitspace stops and removes the gitspace container.
|
||||
StopGitspace(ctx context.Context, gitspaceConfig *types.GitspaceConfig, infra *infraprovider.Infrastructure) error
|
||||
|
||||
// Status checks if the infra is reachable and ready to begin container creation.
|
||||
Status(ctx context.Context, infra *infraprovider.Infrastructure) error
|
||||
}
|
|
@ -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 container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type Devcontainer struct {
|
||||
ContainerName string
|
||||
WorkingDir string
|
||||
DockerClient *client.Client
|
||||
}
|
||||
|
||||
func (d *Devcontainer) ExecuteCommand(ctx context.Context, command string, detach bool) (*[]byte, error) {
|
||||
cmd := []string{"/bin/bash", "-c", command}
|
||||
|
||||
execConfig := dockerTypes.ExecConfig{
|
||||
User: "root",
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Cmd: cmd,
|
||||
Detach: detach,
|
||||
WorkingDir: d.WorkingDir,
|
||||
}
|
||||
|
||||
execID, err := d.DockerClient.ContainerExecCreate(ctx, d.ContainerName, execConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create docker exec for container %s: %w", d.ContainerName, err)
|
||||
}
|
||||
|
||||
execResponse, err := d.DockerClient.ContainerExecAttach(ctx, execID.ID, dockerTypes.ExecStartCheck{Detach: detach})
|
||||
if err != nil && err.Error() != "unable to upgrade to tcp, received 200" {
|
||||
return nil, fmt.Errorf("failed to start docker exec for container %s: %w", d.ContainerName, err)
|
||||
}
|
||||
|
||||
if execResponse.Conn != nil {
|
||||
defer execResponse.Close()
|
||||
}
|
||||
|
||||
var output []byte
|
||||
if execResponse.Reader != nil {
|
||||
output, err = io.ReadAll(execResponse.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &output, nil
|
||||
}
|
|
@ -0,0 +1,508 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/harness/gitness/infraprovider"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/strslice"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var _ Orchestrator = (*EmbeddedDockerOrchestrator)(nil)
|
||||
|
||||
const (
|
||||
loggingKey = "gitspace.container"
|
||||
sshPort = "22/tcp"
|
||||
catchAllIP = "0.0.0.0"
|
||||
catchAllPort = "0"
|
||||
containerStateRunning = "running"
|
||||
containerStateRemoved = "removed"
|
||||
templateCloneGit = "clone_git.sh"
|
||||
templateSetupSSHServer = "setup_ssh_server.sh"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
DefaultBaseImage string
|
||||
DefaultBindMountTargetPath string
|
||||
DefaultBindMountSourceBasePath string
|
||||
}
|
||||
|
||||
type EmbeddedDockerOrchestrator struct {
|
||||
dockerClientFactory *infraprovider.DockerClientFactory
|
||||
vsCodeService *VSCode
|
||||
vsCodeWebService *VSCodeWeb
|
||||
config *Config
|
||||
}
|
||||
|
||||
func NewEmbeddedDockerOrchestrator(
|
||||
dockerClientFactory *infraprovider.DockerClientFactory,
|
||||
vsCodeService *VSCode,
|
||||
vsCodeWebService *VSCodeWeb,
|
||||
config *Config,
|
||||
) Orchestrator {
|
||||
return &EmbeddedDockerOrchestrator{
|
||||
dockerClientFactory: dockerClientFactory,
|
||||
vsCodeService: vsCodeService,
|
||||
vsCodeWebService: vsCodeWebService,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// StartGitspace checks if the Gitspace is already running by checking its entry in a map. If is it running,
|
||||
// it returns, else, it creates a new Gitspace container by using the provided image. If the provided image is
|
||||
// nil, it uses a default image read from Gitness config. Post creation it runs the postCreate command and clones
|
||||
// the code inside the container. It uses the IDE service to setup the relevant IDE and also installs SSH server
|
||||
// inside the container.
|
||||
func (e *EmbeddedDockerOrchestrator) StartGitspace(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
devcontainerConfig *types.DevcontainerConfig,
|
||||
infra *infraprovider.Infrastructure,
|
||||
) (map[enum.IDEType]string, error) {
|
||||
containerName := getGitspaceContainerName(gitspaceConfig)
|
||||
|
||||
log := log.Ctx(ctx).With().Str(loggingKey, containerName).Logger()
|
||||
|
||||
dockerClient, err := e.dockerClientFactory.NewDockerClient(ctx, infra)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting docker client from docker client factory: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
closingErr := dockerClient.Close()
|
||||
if closingErr != nil {
|
||||
log.Warn().Err(closingErr).Msg("failed to close docker client")
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debug().Msg("checking current state of gitspace")
|
||||
state, err := e.containerState(ctx, containerName, dockerClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var usedPorts map[enum.IDEType]string
|
||||
|
||||
switch state {
|
||||
case containerStateRunning:
|
||||
log.Debug().Msg("gitspace is already running")
|
||||
|
||||
ideService, startErr := e.getIDEService(gitspaceConfig)
|
||||
if startErr != nil {
|
||||
return nil, startErr
|
||||
}
|
||||
|
||||
ports, startErr := e.getUsedPorts(ctx, containerName, dockerClient, ideService)
|
||||
if startErr != nil {
|
||||
return nil, startErr
|
||||
}
|
||||
usedPorts = ports
|
||||
|
||||
case containerStateRemoved:
|
||||
log.Debug().Msg("gitspace is not running, starting it...")
|
||||
|
||||
ideService, startErr := e.getIDEService(gitspaceConfig)
|
||||
if startErr != nil {
|
||||
return nil, startErr
|
||||
}
|
||||
|
||||
startErr = e.startGitspace(
|
||||
ctx,
|
||||
gitspaceConfig,
|
||||
devcontainerConfig,
|
||||
containerName,
|
||||
dockerClient,
|
||||
ideService,
|
||||
)
|
||||
if startErr != nil {
|
||||
return nil, fmt.Errorf("failed to start gitspace %s: %w", containerName, startErr)
|
||||
}
|
||||
ports, startErr := e.getUsedPorts(ctx, containerName, dockerClient, ideService)
|
||||
if startErr != nil {
|
||||
return nil, startErr
|
||||
}
|
||||
usedPorts = ports
|
||||
|
||||
// TODO: Add gitspace status reporting.
|
||||
log.Debug().Msg("started gitspace")
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("gitspace %s is in a bad state: %s", containerName, state)
|
||||
}
|
||||
|
||||
return usedPorts, nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) startGitspace(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
devcontainerConfig *types.DevcontainerConfig,
|
||||
containerName string,
|
||||
dockerClient *client.Client,
|
||||
ideService IDE,
|
||||
) error {
|
||||
var imageName = devcontainerConfig.Image
|
||||
if imageName == "" {
|
||||
imageName = e.config.DefaultBaseImage
|
||||
}
|
||||
|
||||
err := e.pullImage(ctx, imageName, dockerClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.createContainer(ctx, gitspaceConfig, dockerClient, imageName, containerName, ideService)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var devcontainer = &Devcontainer{
|
||||
ContainerName: containerName,
|
||||
DockerClient: dockerClient,
|
||||
WorkingDir: e.config.DefaultBindMountTargetPath,
|
||||
}
|
||||
|
||||
err = e.executePostCreateCommand(ctx, devcontainerConfig, devcontainer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.cloneCode(ctx, gitspaceConfig, devcontainerConfig, devcontainer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.setupSSHServer(ctx, gitspaceConfig.GitspaceInstance, devcontainer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ideService.Setup(ctx, devcontainer, gitspaceConfig.GitspaceInstance)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup IDE for gitspace %s: %w", containerName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) getUsedPorts(
|
||||
ctx context.Context,
|
||||
containerName string,
|
||||
dockerClient *client.Client,
|
||||
ideService IDE,
|
||||
) (map[enum.IDEType]string, error) {
|
||||
inspectResp, err := dockerClient.ContainerInspect(ctx, containerName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not inspect container %s: %w", containerName, err)
|
||||
}
|
||||
|
||||
usedPorts := map[enum.IDEType]string{}
|
||||
for port, bindings := range inspectResp.NetworkSettings.Ports {
|
||||
if port == sshPort {
|
||||
usedPorts[enum.IDETypeVSCode] = bindings[0].HostPort
|
||||
}
|
||||
if port == nat.Port(ideService.PortAndProtocol()) {
|
||||
usedPorts[ideService.Type()] = bindings[0].HostPort
|
||||
}
|
||||
}
|
||||
|
||||
return usedPorts, nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) getIDEService(gitspaceConfig *types.GitspaceConfig) (IDE, error) {
|
||||
var ideService IDE
|
||||
|
||||
switch gitspaceConfig.IDE {
|
||||
case enum.IDETypeVSCode:
|
||||
ideService = e.vsCodeService
|
||||
case enum.IDETypeVSCodeWeb:
|
||||
ideService = e.vsCodeWebService
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported IDE: %s", gitspaceConfig.IDE)
|
||||
}
|
||||
|
||||
return ideService, nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) setupSSHServer(
|
||||
ctx context.Context,
|
||||
gitspaceInstance *types.GitspaceInstance,
|
||||
devcontainer *Devcontainer,
|
||||
) error {
|
||||
sshServerScript, err := GenerateScriptFromTemplate(
|
||||
templateSetupSSHServer, &SetupSSHServerPayload{
|
||||
Username: "harness",
|
||||
Password: gitspaceInstance.AccessKey.String,
|
||||
WorkingDirectory: devcontainer.WorkingDir,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to generate scipt to setup ssh server from template %s: %w", templateSetupSSHServer, err)
|
||||
}
|
||||
|
||||
_, err = devcontainer.ExecuteCommand(ctx, sshServerScript, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup SSH server: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) cloneCode(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
devcontainerConfig *types.DevcontainerConfig,
|
||||
devcontainer *Devcontainer,
|
||||
) error {
|
||||
var devcontainerPresent = "true"
|
||||
if devcontainerConfig.Image == "" {
|
||||
devcontainerPresent = "false"
|
||||
}
|
||||
|
||||
gitCloneScript, err := GenerateScriptFromTemplate(
|
||||
templateCloneGit, &CloneGitPayload{
|
||||
RepoURL: gitspaceConfig.CodeRepoURL,
|
||||
DevcontainerPresent: devcontainerPresent,
|
||||
Image: e.config.DefaultBaseImage,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate scipt to clone git from template %s: %w", templateCloneGit, err)
|
||||
}
|
||||
|
||||
_, err = devcontainer.ExecuteCommand(ctx, gitCloneScript, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to clone code: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) executePostCreateCommand(
|
||||
ctx context.Context,
|
||||
devcontainerConfig *types.DevcontainerConfig,
|
||||
devcontainer *Devcontainer,
|
||||
) error {
|
||||
if devcontainerConfig.PostCreateCommand == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := devcontainer.ExecuteCommand(ctx, devcontainerConfig.PostCreateCommand, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("post create command failed %q: %w", devcontainerConfig.PostCreateCommand, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) createContainer(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
dockerClient *client.Client,
|
||||
imageName string,
|
||||
containerName string,
|
||||
ideService IDE,
|
||||
) error {
|
||||
portUsedByIDE := ideService.PortAndProtocol()
|
||||
|
||||
exposedPorts := nat.PortSet{
|
||||
sshPort: struct{}{},
|
||||
}
|
||||
|
||||
hostPortBindings := []nat.PortBinding{
|
||||
{
|
||||
HostIP: catchAllIP,
|
||||
HostPort: catchAllPort,
|
||||
},
|
||||
}
|
||||
|
||||
portBindings := nat.PortMap{
|
||||
sshPort: hostPortBindings,
|
||||
}
|
||||
|
||||
if portUsedByIDE != "" {
|
||||
natPort := nat.Port(portUsedByIDE)
|
||||
exposedPorts[natPort] = struct{}{}
|
||||
portBindings[natPort] = hostPortBindings
|
||||
}
|
||||
|
||||
entryPoint := make(strslice.StrSlice, 0)
|
||||
entryPoint = append(entryPoint, "sleep")
|
||||
|
||||
commands := make(strslice.StrSlice, 0)
|
||||
commands = append(commands, "infinity")
|
||||
|
||||
bindMountSourcePath :=
|
||||
filepath.Join(
|
||||
e.config.DefaultBindMountSourceBasePath,
|
||||
gitspaceConfig.SpacePath,
|
||||
gitspaceConfig.Identifier,
|
||||
)
|
||||
err := os.MkdirAll(bindMountSourcePath, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"could not create bind mount source path %s: %w", bindMountSourcePath, err)
|
||||
}
|
||||
|
||||
resp2, err := dockerClient.ContainerCreate(ctx, &container.Config{
|
||||
Image: imageName,
|
||||
Entrypoint: entryPoint,
|
||||
Cmd: commands,
|
||||
ExposedPorts: exposedPorts,
|
||||
}, &container.HostConfig{
|
||||
PortBindings: portBindings,
|
||||
Mounts: []mount.Mount{
|
||||
{
|
||||
Type: mount.TypeBind,
|
||||
Source: bindMountSourcePath,
|
||||
Target: e.config.DefaultBindMountTargetPath,
|
||||
},
|
||||
},
|
||||
}, nil, containerName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create container %s: %w", containerName, err)
|
||||
}
|
||||
|
||||
err = dockerClient.ContainerStart(ctx, resp2.ID, dockerTypes.ContainerStartOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start container %s: %w", containerName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) pullImage(
|
||||
ctx context.Context,
|
||||
imageName string,
|
||||
dockerClient *client.Client,
|
||||
) error {
|
||||
resp, err := dockerClient.ImagePull(ctx, imageName, dockerTypes.ImagePullOptions{})
|
||||
defer func() {
|
||||
closingErr := resp.Close()
|
||||
if closingErr != nil {
|
||||
log.Warn().Err(closingErr).Msg("failed to close image pull response")
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not pull image %s: %w", imageName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopGitspace checks if the Gitspace container is running. If yes, it stops and removes the container.
|
||||
// Else it returns.
|
||||
func (e EmbeddedDockerOrchestrator) StopGitspace(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
infra *infraprovider.Infrastructure,
|
||||
) error {
|
||||
containerName := getGitspaceContainerName(gitspaceConfig)
|
||||
|
||||
log := log.Ctx(ctx).With().Str(loggingKey, containerName).Logger()
|
||||
|
||||
dockerClient, err := e.dockerClientFactory.NewDockerClient(ctx, infra)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting docker client from docker client factory: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
closingErr := dockerClient.Close()
|
||||
if closingErr != nil {
|
||||
log.Warn().Err(closingErr).Msg("failed to close docker client")
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debug().Msg("checking current state of gitspace")
|
||||
state, err := e.containerState(ctx, containerName, dockerClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if state == containerStateRemoved {
|
||||
log.Debug().Msg("gitspace is already stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debug().Msg("stopping gitspace")
|
||||
err = e.stopGitspace(ctx, containerName, dockerClient)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop gitspace %s: %w", containerName, err)
|
||||
}
|
||||
|
||||
log.Debug().Msg("stopped gitspace")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e EmbeddedDockerOrchestrator) stopGitspace(
|
||||
ctx context.Context,
|
||||
containerName string,
|
||||
dockerClient *client.Client,
|
||||
) error {
|
||||
err := dockerClient.ContainerStop(ctx, containerName, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not stop container %s: %w", containerName, err)
|
||||
}
|
||||
|
||||
err = dockerClient.ContainerRemove(ctx, containerName, dockerTypes.ContainerRemoveOptions{Force: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not remove container %s: %w", containerName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getGitspaceContainerName(config *types.GitspaceConfig) string {
|
||||
return "gitspace-" + config.UserID + "-" + config.Identifier
|
||||
}
|
||||
|
||||
// Status is NOOP for EmbeddedDockerOrchestrator as the docker host is verified by the infra provisioner.
|
||||
func (e *EmbeddedDockerOrchestrator) Status(_ context.Context, _ *infraprovider.Infrastructure) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) containerState(
|
||||
ctx context.Context,
|
||||
containerName string,
|
||||
dockerClient *client.Client,
|
||||
) (string, error) {
|
||||
var args = filters.NewArgs()
|
||||
args.Add("name", containerName)
|
||||
|
||||
containers, err := dockerClient.ContainerList(ctx, dockerTypes.ContainerListOptions{All: true, Filters: args})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not list container %s: %w", containerName, err)
|
||||
}
|
||||
|
||||
if len(containers) == 0 {
|
||||
return containerStateRemoved, nil
|
||||
}
|
||||
|
||||
return containers[0].State, nil
|
||||
}
|
|
@ -12,21 +12,23 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package infraprovider
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
var _ Client = (*DockerClient)(nil)
|
||||
type IDE interface {
|
||||
// Setup is responsible for doing all the operations for setting up the IDE in the container e.g. installation,
|
||||
// copying settings and configurations, ensuring SSH server is running etc.
|
||||
Setup(ctx context.Context, containerParams *Devcontainer, gitspaceInstance *types.GitspaceInstance) error
|
||||
|
||||
type DockerClient struct {
|
||||
dockerClient *client.Client
|
||||
closeFunc func(ctx context.Context)
|
||||
}
|
||||
// PortAndProtocol provides the port with protocol which will be used by this IDE.
|
||||
PortAndProtocol() string
|
||||
|
||||
func (d DockerClient) Close(ctx context.Context) {
|
||||
d.closeFunc(ctx)
|
||||
// Type provides the IDE type to which the service belongs.
|
||||
Type() enum.IDEType
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const (
|
||||
templatesDir = "template"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed template/*
|
||||
files embed.FS
|
||||
scriptTemplates map[string]*template.Template
|
||||
)
|
||||
|
||||
type CloneGitPayload struct {
|
||||
RepoURL string
|
||||
DevcontainerPresent string
|
||||
Image string
|
||||
}
|
||||
|
||||
type InstallVSCodeWebPayload struct {
|
||||
Password string
|
||||
Port string
|
||||
}
|
||||
|
||||
type SetupSSHServerPayload struct {
|
||||
Username string
|
||||
Password string
|
||||
WorkingDirectory string
|
||||
}
|
||||
|
||||
func init() {
|
||||
err := LoadTemplates()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error loading script templates: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func LoadTemplates() error {
|
||||
scriptTemplates = make(map[string]*template.Template)
|
||||
|
||||
tmplFiles, err := fs.ReadDir(files, templatesDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading script templates: %w", err)
|
||||
}
|
||||
|
||||
for _, tmpl := range tmplFiles {
|
||||
if tmpl.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
textTemplate, parsingErr := template.ParseFS(files, path.Join(templatesDir, tmpl.Name()))
|
||||
if parsingErr != nil {
|
||||
return fmt.Errorf("error parsing template %s: %w", tmpl.Name(), parsingErr)
|
||||
}
|
||||
|
||||
scriptTemplates[tmpl.Name()] = textTemplate
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateScriptFromTemplate(name string, data interface{}) (string, error) {
|
||||
if scriptTemplates[name] == nil {
|
||||
return "", fmt.Errorf("no script template found for %s", name)
|
||||
}
|
||||
|
||||
tmplOutput := bytes.Buffer{}
|
||||
err := scriptTemplates[name].Execute(&tmplOutput, data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error executing template %s with data %+v: %w", name, data, err)
|
||||
}
|
||||
|
||||
return tmplOutput.String(), nil
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/bash
|
||||
|
||||
repo_url={{ .RepoURL }}
|
||||
devcontainer_present={{ .DevcontainerPresent }}
|
||||
image={{ .Image }}
|
||||
|
||||
# Extract the repository name from the URL
|
||||
repo_name=$(basename -s .git "$repo_url")
|
||||
|
||||
# Check if Git is installed
|
||||
if ! command -v git &>/dev/null; then
|
||||
echo "Git is not installed. Installing Git..."
|
||||
apt-get update
|
||||
apt-get install -y git
|
||||
fi
|
||||
|
||||
if ! command -v git &>/dev/null; then
|
||||
echo "Git is not installed. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clone the repository only if it doesn't exist
|
||||
if [ ! -d "$repo_name" ]; then
|
||||
echo "Cloning the repository..."
|
||||
git clone "$repo_url"
|
||||
else
|
||||
echo "Repository already exists. Skipping clone."
|
||||
fi
|
||||
|
||||
# Check if devcontainer_present is set to false
|
||||
if [ "$devcontainer_present" = "false" ]; then
|
||||
# Ensure the repository is cloned
|
||||
if [ -d "$repo_name" ]; then
|
||||
echo "Creating .devcontainer directory and devcontainer.json..."
|
||||
mkdir -p "$repo_name/.devcontainer"
|
||||
cat <<EOL > "$repo_name/.devcontainer/devcontainer.json"
|
||||
{
|
||||
"image": "$image"
|
||||
}
|
||||
EOL
|
||||
echo "devcontainer.json created."
|
||||
else
|
||||
echo "Repository directory not found. Cannot create .devcontainer."
|
||||
fi
|
||||
else
|
||||
echo "devcontainer_present is set to true. Skipping .devcontainer creation."
|
||||
fi
|
||||
|
||||
echo "Script completed."
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "Installing code-server"
|
||||
|
||||
password={{ .Password }}
|
||||
port={{ .Port }}
|
||||
|
||||
curl -fsSL https://code-server.dev/install.sh | sh
|
||||
|
||||
# Ensure the configuration directory exists
|
||||
mkdir -p /root/.config/code-server
|
||||
|
||||
# Create or overwrite the config file with new settings
|
||||
cat > /root/.config/code-server/config.yaml <<EOF
|
||||
bind-addr: 0.0.0.0:$port
|
||||
auth: password
|
||||
password: $password
|
||||
cert: false
|
||||
EOF
|
||||
|
||||
code-server
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Install SSH if it's not already installed
|
||||
if ! command -v sshd &> /dev/null; then
|
||||
echo "OpenSSH server is not installed. Installing..."
|
||||
apt-get update
|
||||
apt-get install -y openssh-server
|
||||
else
|
||||
echo "OpenSSH server is already installed."
|
||||
fi
|
||||
|
||||
username={{ .Username }}
|
||||
password={{ .Password }}
|
||||
workingDir={{ .WorkingDirectory }}
|
||||
|
||||
# Check if the user already exists
|
||||
if id "$username" &> /dev/null; then
|
||||
echo "User $username already exists."
|
||||
else
|
||||
# Create a new user
|
||||
adduser --disabled-password --home "$workingDir" --gecos "" "$username"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create user $username."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set or update the user's password using chpasswd
|
||||
echo "$username:$password" | chpasswd
|
||||
|
||||
# Configure SSH to allow this user
|
||||
config_file='/etc/ssh/sshd_config'
|
||||
grep -q "^AllowUsers" $config_file
|
||||
if [ $? -eq 0 ]; then
|
||||
# If AllowUsers exists, add the user to it
|
||||
sed -i "/^AllowUsers/ s/$/ $username/" $config_file
|
||||
else
|
||||
# Otherwise, add a new AllowUsers line
|
||||
echo "AllowUsers $username" >> $config_file
|
||||
fi
|
||||
|
||||
# Ensure password authentication is enabled
|
||||
sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' $config_file
|
||||
if ! grep -q "^PasswordAuthentication yes" $config_file; then
|
||||
echo "PasswordAuthentication yes" >> $config_file
|
||||
fi
|
||||
|
||||
# Changing ownership of everything inside user home to the newly created user
|
||||
chown -R $username .
|
||||
|
||||
mkdir /var/run/sshd
|
||||
/usr/sbin/sshd
|
|
@ -0,0 +1,45 @@
|
|||
// 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 (
|
||||
"context"
|
||||
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
var _ IDE = (*VSCode)(nil)
|
||||
|
||||
type VSCode struct{}
|
||||
|
||||
func NewVsCodeService() *VSCode {
|
||||
return &VSCode{}
|
||||
}
|
||||
|
||||
// Setup is a NOOP since VS Code doesn't require any installation.
|
||||
// TODO Check if the SSH server is accessible on the required port.
|
||||
func (v *VSCode) Setup(_ context.Context, _ *Devcontainer, _ *types.GitspaceInstance) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PortAndProtocol return nil since VS Code doesn't require any additional port to be exposed.
|
||||
func (v *VSCode) PortAndProtocol() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (v *VSCode) Type() enum.IDEType {
|
||||
return enum.IDETypeVSCode
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
var _ IDE = (*VSCodeWeb)(nil)
|
||||
|
||||
const templateInstallVSCodeWeb = "install_vscode_web.sh"
|
||||
|
||||
type VSCodeWebConfig struct {
|
||||
Port string
|
||||
}
|
||||
|
||||
type VSCodeWeb struct {
|
||||
config *VSCodeWebConfig
|
||||
}
|
||||
|
||||
func NewVsCodeWebService(config *VSCodeWebConfig) *VSCodeWeb {
|
||||
return &VSCodeWeb{config: config}
|
||||
}
|
||||
|
||||
// Setup runs the installScript which downloads the required version of the code-server binary and runs it
|
||||
// with the given password.
|
||||
func (v *VSCodeWeb) Setup(
|
||||
ctx context.Context,
|
||||
devcontainer *Devcontainer,
|
||||
gitspaceInstance *types.GitspaceInstance,
|
||||
) error {
|
||||
installScript, err := GenerateScriptFromTemplate(
|
||||
templateInstallVSCodeWeb, &InstallVSCodeWebPayload{
|
||||
Password: gitspaceInstance.AccessKey.String,
|
||||
Port: v.config.Port,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to generate scipt to install code server from template %s: %w",
|
||||
templateInstallVSCodeWeb,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
_, err = devcontainer.ExecuteCommand(ctx, installScript, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to install code-server: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PortAndProtocol returns the port on which the code-server is listening.
|
||||
func (v *VSCodeWeb) PortAndProtocol() string {
|
||||
return v.config.Port + "/tcp"
|
||||
}
|
||||
|
||||
func (v *VSCodeWeb) Type() enum.IDEType {
|
||||
return enum.IDETypeVSCodeWeb
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// 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 (
|
||||
"github.com/harness/gitness/infraprovider"
|
||||
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
var WireSet = wire.NewSet(
|
||||
ProvideEmbeddedDockerOrchestrator,
|
||||
ProvideVSCodeWebService,
|
||||
ProvideVSCodeService,
|
||||
)
|
||||
|
||||
func ProvideEmbeddedDockerOrchestrator(
|
||||
dockerClientFactory *infraprovider.DockerClientFactory,
|
||||
vsCodeService *VSCode,
|
||||
vsCodeWebService *VSCodeWeb,
|
||||
config *Config,
|
||||
) Orchestrator {
|
||||
return NewEmbeddedDockerOrchestrator(
|
||||
dockerClientFactory,
|
||||
vsCodeService,
|
||||
vsCodeWebService,
|
||||
config,
|
||||
)
|
||||
}
|
||||
|
||||
func ProvideVSCodeWebService(config *VSCodeWebConfig) *VSCodeWeb {
|
||||
return NewVsCodeWebService(config)
|
||||
}
|
||||
|
||||
func ProvideVSCodeService() *VSCode {
|
||||
return NewVsCodeService()
|
||||
}
|
|
@ -22,6 +22,7 @@ import (
|
|||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/harness/gitness/app/gitspace/orchestrator/container"
|
||||
"github.com/harness/gitness/app/services/cleanup"
|
||||
"github.com/harness/gitness/app/services/codeowners"
|
||||
"github.com/harness/gitness/app/services/keywordsearch"
|
||||
|
@ -31,6 +32,7 @@ import (
|
|||
"github.com/harness/gitness/blob"
|
||||
"github.com/harness/gitness/events"
|
||||
gittypes "github.com/harness/gitness/git/types"
|
||||
"github.com/harness/gitness/infraprovider"
|
||||
"github.com/harness/gitness/job"
|
||||
"github.com/harness/gitness/lock"
|
||||
"github.com/harness/gitness/pubsub"
|
||||
|
@ -48,6 +50,7 @@ const (
|
|||
schemeHTTPS = "https"
|
||||
gitnessHomeDir = ".gitness"
|
||||
blobDir = "blob"
|
||||
gitspacesDir = "gitspaces"
|
||||
)
|
||||
|
||||
// LoadConfig returns the system configuration from the
|
||||
|
@ -371,3 +374,44 @@ func ProvideJobsConfig(config *types.Config) job.Config {
|
|||
BackgroundJobsRetentionTime: config.BackgroundJobs.RetentionTime,
|
||||
}
|
||||
}
|
||||
|
||||
// ProvideDockerConfig loads config for Docker.
|
||||
func ProvideDockerConfig(config *types.Config) *infraprovider.DockerConfig {
|
||||
return &infraprovider.DockerConfig{
|
||||
DockerHost: config.Docker.Host,
|
||||
DockerAPIVersion: config.Docker.APIVersion,
|
||||
DockerCertPath: config.Docker.CertPath,
|
||||
DockerTLSVerify: config.Docker.TLSVerify,
|
||||
}
|
||||
}
|
||||
|
||||
// ProvideIDEVSCodeWebConfig loads the VSCode Web IDE config from the main config.
|
||||
func ProvideIDEVSCodeWebConfig(config *types.Config) *container.VSCodeWebConfig {
|
||||
return &container.VSCodeWebConfig{
|
||||
Port: config.IDE.VSCodeWeb.Port,
|
||||
}
|
||||
}
|
||||
|
||||
// ProvideGitspaceContainerOrchestratorConfig loads the Gitspace container orchestrator config from the main config.
|
||||
func ProvideGitspaceContainerOrchestratorConfig(config *types.Config) (*container.Config, error) {
|
||||
var bindMountSourceBasePath string
|
||||
|
||||
if config.Gitspace.DefaultBindMountSourceBasePath == "" {
|
||||
var homedir string
|
||||
|
||||
homedir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to determine home directory: %w", err)
|
||||
}
|
||||
|
||||
bindMountSourceBasePath = filepath.Join(homedir, gitnessHomeDir, gitspacesDir)
|
||||
} else {
|
||||
bindMountSourceBasePath = filepath.Join(config.Gitspace.DefaultBindMountSourceBasePath, gitspacesDir)
|
||||
}
|
||||
|
||||
return &container.Config{
|
||||
DefaultBaseImage: config.Gitspace.DefaultBaseImage,
|
||||
DefaultBindMountTargetPath: config.Gitspace.DefaultBindMountTargetPath,
|
||||
DefaultBindMountSourceBasePath: bindMountSourceBasePath,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
// 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 infraprovider
|
||||
|
||||
import "context"
|
||||
|
||||
type Client interface {
|
||||
// Close closes the underlying client.
|
||||
Close(ctx context.Context)
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
// 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 infraprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/harness/gitness/infraprovider/enum"
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
)
|
||||
|
||||
type DockerConfig struct {
|
||||
DockerHost string
|
||||
DockerAPIVersion string
|
||||
DockerCertPath string
|
||||
DockerTLSVerify string
|
||||
}
|
||||
|
||||
type DockerClientFactory struct {
|
||||
config *DockerConfig
|
||||
}
|
||||
|
||||
func NewDockerClientFactory(config *DockerConfig) *DockerClientFactory {
|
||||
return &DockerClientFactory{config: config}
|
||||
}
|
||||
|
||||
// NewDockerClient returns a new docker client created using the docker config and infra.
|
||||
func (d *DockerClientFactory) NewDockerClient(
|
||||
_ context.Context,
|
||||
infra *Infrastructure,
|
||||
) (*client.Client, error) {
|
||||
if infra.ProviderType != enum.InfraProviderTypeDocker {
|
||||
return nil, fmt.Errorf("infra provider type %s not supported", infra.ProviderType)
|
||||
}
|
||||
dockerClient, err := d.getClient(infra.Parameters)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating docker client using infra %+v: %w", infra, err)
|
||||
}
|
||||
return dockerClient, nil
|
||||
}
|
||||
|
||||
func (d *DockerClientFactory) getClient(_ []Parameter) (*client.Client, error) {
|
||||
var opts []client.Opt
|
||||
|
||||
opts = append(opts, client.WithHost(d.config.DockerHost))
|
||||
|
||||
opts = append(opts, client.WithVersion(d.config.DockerAPIVersion))
|
||||
|
||||
if d.config.DockerCertPath != "" {
|
||||
httpsClient, err := d.getHTTPSClient()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create https client for docker client: %w", err)
|
||||
}
|
||||
opts = append(opts, client.WithHTTPClient(httpsClient))
|
||||
}
|
||||
|
||||
dockerClient, err := client.NewClientWithOpts(opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create docker client: %w", err)
|
||||
}
|
||||
|
||||
return dockerClient, nil
|
||||
}
|
||||
|
||||
func (d *DockerClientFactory) getHTTPSClient() (*http.Client, error) {
|
||||
options := tlsconfig.Options{
|
||||
CAFile: filepath.Join(d.config.DockerCertPath, "ca.pem"),
|
||||
CertFile: filepath.Join(d.config.DockerCertPath, "cert.pem"),
|
||||
KeyFile: filepath.Join(d.config.DockerCertPath, "key.pem"),
|
||||
InsecureSkipVerify: d.config.DockerTLSVerify == "",
|
||||
}
|
||||
tlsc, err := tlsconfig.Client(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{TLSClientConfig: tlsc},
|
||||
CheckRedirect: client.CheckRedirect,
|
||||
}, nil
|
||||
}
|
|
@ -18,43 +18,42 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/harness/gitness/infraprovider/enum"
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var _ InfraProvider = (*DockerProvider)(nil)
|
||||
|
||||
type Config struct {
|
||||
DockerHost string
|
||||
DockerAPIVersion string
|
||||
DockerCertPath string
|
||||
DockerTLSVerify string
|
||||
}
|
||||
|
||||
type DockerProvider struct {
|
||||
config *Config
|
||||
dockerClientFactory *DockerClientFactory
|
||||
}
|
||||
|
||||
func NewDockerProvider(config *Config) *DockerProvider {
|
||||
func NewDockerProvider(dockerClientFactory *DockerClientFactory) *DockerProvider {
|
||||
return &DockerProvider{
|
||||
config: config,
|
||||
dockerClientFactory: dockerClientFactory,
|
||||
}
|
||||
}
|
||||
|
||||
// Provision assumes a docker engine is already running on the Gitness host machine and re-uses that as infra.
|
||||
// It does not start docker engine.
|
||||
func (d DockerProvider) Provision(ctx context.Context, _ string, params []Parameter) (Infrastructure, error) {
|
||||
dockerClient, closeFunc, err := d.getClient(params)
|
||||
dockerClient, err := d.dockerClientFactory.NewDockerClient(ctx, &Infrastructure{
|
||||
ProviderType: enum.InfraProviderTypeDocker,
|
||||
Parameters: params,
|
||||
})
|
||||
if err != nil {
|
||||
return Infrastructure{}, err
|
||||
return Infrastructure{}, fmt.Errorf("error getting docker client from docker client factory: %w", err)
|
||||
}
|
||||
defer closeFunc(ctx)
|
||||
|
||||
defer func() {
|
||||
closingErr := dockerClient.Close()
|
||||
if closingErr != nil {
|
||||
log.Ctx(ctx).Warn().Err(closingErr).Msg("failed to close docker client")
|
||||
}
|
||||
}()
|
||||
|
||||
info, err := dockerClient.Info(ctx)
|
||||
if err != nil {
|
||||
return Infrastructure{}, fmt.Errorf("unable to connect to docker engine: %w", err)
|
||||
|
@ -110,63 +109,3 @@ func (d DockerProvider) Exec(_ context.Context, _ Infrastructure, _ []string) (i
|
|||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
// Client returns a new docker client created using params.
|
||||
func (d DockerProvider) Client(_ context.Context, infra Infrastructure) (Client, error) {
|
||||
dockerClient, closeFunc, err := d.getClient(infra.Parameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DockerClient{
|
||||
dockerClient: dockerClient,
|
||||
closeFunc: closeFunc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getClient returns a new docker client created using values from gitness docker config.
|
||||
func (d DockerProvider) getClient(_ []Parameter) (*client.Client, func(context.Context), error) {
|
||||
var opts []client.Opt
|
||||
|
||||
opts = append(opts, client.WithHost(d.config.DockerHost))
|
||||
|
||||
opts = append(opts, client.WithVersion(d.config.DockerAPIVersion))
|
||||
|
||||
if d.config.DockerCertPath != "" {
|
||||
httpsClient, err := d.getHTTPSClient()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create https client for docker client: %w", err)
|
||||
}
|
||||
opts = append(opts, client.WithHTTPClient(httpsClient))
|
||||
}
|
||||
|
||||
dockerClient, err := client.NewClientWithOpts(opts...)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create docker client: %w", err)
|
||||
}
|
||||
|
||||
closeFunc := func(ctx context.Context) {
|
||||
closingErr := dockerClient.Close()
|
||||
if closingErr != nil {
|
||||
log.Ctx(ctx).Warn().Err(closingErr).Msg("failed to close docker client")
|
||||
}
|
||||
}
|
||||
|
||||
return dockerClient, closeFunc, nil
|
||||
}
|
||||
|
||||
func (d DockerProvider) getHTTPSClient() (*http.Client, error) {
|
||||
options := tlsconfig.Options{
|
||||
CAFile: filepath.Join(d.config.DockerCertPath, "ca.pem"),
|
||||
CertFile: filepath.Join(d.config.DockerCertPath, "cert.pem"),
|
||||
KeyFile: filepath.Join(d.config.DockerCertPath, "key.pem"),
|
||||
InsecureSkipVerify: d.config.DockerTLSVerify == "",
|
||||
}
|
||||
tlsc, err := tlsconfig.Client(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{TLSClientConfig: tlsc},
|
||||
CheckRedirect: client.CheckRedirect,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -42,7 +42,4 @@ type InfraProvider interface {
|
|||
ProvisioningType() enum.InfraProvisioningType
|
||||
// Exec executes a shell command in the infrastructure.
|
||||
Exec(ctx context.Context, infra Infrastructure, cmd []string) (io.Reader, io.Reader, error)
|
||||
// Client returns a client which can be used to connect the provided infra.
|
||||
// The responsibility of calling the close func lies with the user.
|
||||
Client(ctx context.Context, infra Infrastructure) (Client, error)
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
package infraprovider
|
||||
|
||||
import (
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
|
@ -24,18 +22,17 @@ import (
|
|||
var WireSet = wire.NewSet(
|
||||
ProvideDockerProvider,
|
||||
ProvideFactory,
|
||||
ProvideDockerClientFactory,
|
||||
)
|
||||
|
||||
func ProvideDockerProvider(config *types.Config) *DockerProvider {
|
||||
dockerConfig := Config{
|
||||
DockerHost: config.Docker.Host,
|
||||
DockerAPIVersion: config.Docker.APIVersion,
|
||||
DockerCertPath: config.Docker.CertPath,
|
||||
DockerTLSVerify: config.Docker.TLSVerify,
|
||||
}
|
||||
return NewDockerProvider(&dockerConfig)
|
||||
func ProvideDockerProvider(dockerClientFactory *DockerClientFactory) *DockerProvider {
|
||||
return NewDockerProvider(dockerClientFactory)
|
||||
}
|
||||
|
||||
func ProvideFactory(dockerProvider *DockerProvider) Factory {
|
||||
return NewFactory(dockerProvider)
|
||||
}
|
||||
|
||||
func ProvideDockerClientFactory(config *DockerConfig) *DockerClientFactory {
|
||||
return NewDockerClientFactory(config)
|
||||
}
|
||||
|
|
|
@ -386,4 +386,22 @@ type Config struct {
|
|||
// TLSVerify enables or disables TLS verification, off by default.
|
||||
TLSVerify string `envconfig:"GITNESS_DOCKER_TLS_VERIFY"`
|
||||
}
|
||||
|
||||
IDE struct {
|
||||
VSCodeWeb struct {
|
||||
// PortAndProtocol is the port on which the VS Code Web will be accessible.
|
||||
Port string `envconfig:"GITNESS_IDE_VSCODEWEB_PORT" default:"8089"`
|
||||
}
|
||||
}
|
||||
|
||||
Gitspace struct {
|
||||
// DefaultBaseImage is used to create the Gitspace when no devcontainer.json is absent or doesn't have image.
|
||||
DefaultBaseImage string `envconfig:"GITNESS_GITSPACE_DEFAULT_BASE_IMAGE" default:"mcr.microsoft.com/devcontainers/base:dev-ubuntu-24.04"` //nolint:lll
|
||||
// DefaultBindMountTargetPath is the target for bind mount in the Gitspace container.
|
||||
DefaultBindMountTargetPath string `envconfig:"GITNESS_GITSPACE_DEFAULT_BIND_MOUNT_TARGET_PATH" default:"/gitspace"` //nolint:lll
|
||||
// DefaultBindMountTargetPath is the source for bind mount in the Gitspace container.
|
||||
// Sub-directories will be created from this eg <DefaultBindMountSourceBasePath>/gitspace/space1/space2/config1
|
||||
// If left blank, it will be set to $HOME/.gitness
|
||||
DefaultBindMountSourceBasePath string `envconfig:"GITNESS_GITSPACE_DEFAULT_BIND_MOUNT_SOURCE_BASE_PATH"`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,7 @@ type IDEType string
|
|||
|
||||
func (IDEType) Enum() []interface{} { return toInterfaceSlice(ideTypes) }
|
||||
|
||||
var ideTypes = ([]IDEType{
|
||||
IDETypeVSCode, IDETypeVSCodeWeb,
|
||||
})
|
||||
var ideTypes = []IDEType{IDETypeVSCode, IDETypeVSCodeWeb}
|
||||
|
||||
const (
|
||||
IDETypeVSCode IDEType = "vs_code"
|
||||
|
|
Loading…
Reference in New Issue