feat: [CDE-473]: Adding support for containerUser and remoteUser. (#3086)

* Adding support for root as remoteUser
* Changing logger from gitspace to application in config util.
* Changing logger from gitspace to application in config util.
* feat: [CDE-473]: Adding support for containerUser and remoteUser.
This commit is contained in:
Dhruv Dhruv 2024-12-01 05:03:48 +00:00 committed by Harness
parent b7cca56ec7
commit 1086397b3f
22 changed files with 367 additions and 187 deletions

View File

@ -124,6 +124,10 @@ func (l *LogStreamInstance) Debug(msg string) {
l.Write("DEBUG: " + msg) //nolint:errcheck l.Write("DEBUG: " + msg) //nolint:errcheck
} }
func (l *LogStreamInstance) Warn(msg string) {
l.Write("WARN: " + msg) //nolint:errcheck
}
func (l *LogStreamInstance) Error(msg string, err error) { func (l *LogStreamInstance) Error(msg string, err error) {
l.Write("ERROR: " + msg + ": " + err.Error()) //nolint:errcheck l.Write("ERROR: " + msg + ": " + err.Error()) //nolint:errcheck
} }

View File

@ -17,11 +17,12 @@ package container
import ( import (
"context" "context"
"fmt" "fmt"
"regexp"
"strings" "strings"
"github.com/harness/gitness/app/gitspace/orchestrator/ide" "github.com/harness/gitness/app/gitspace/orchestrator/ide"
"github.com/harness/gitness/app/gitspace/orchestrator/runarg" "github.com/harness/gitness/app/gitspace/orchestrator/runarg"
types2 "github.com/harness/gitness/app/gitspace/types" gitspaceTypes "github.com/harness/gitness/app/gitspace/types"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum" "github.com/harness/gitness/types/enum"
@ -40,11 +41,11 @@ func ExtractRunArgs(
} }
var runArgsMap = make(map[types.RunArg]*types.RunArgValue) var runArgsMap = make(map[types.RunArg]*types.RunArgValue)
primaryLoopCounter := 0 argLoopCounter := 0
for primaryLoopCounter < len(runArgsRaw) { for argLoopCounter < len(runArgsRaw) {
currentArg := runArgsRaw[primaryLoopCounter] currentArg := runArgsRaw[argLoopCounter]
if currentArg == "" || !isArg(currentArg) { if currentArg == "" || !isArg(currentArg) {
primaryLoopCounter++ argLoopCounter++
continue continue
} }
@ -53,13 +54,13 @@ func ExtractRunArgs(
currentRunArgDefinition, isSupportedArg := supportedRunArgsMap[types.RunArg(argKey)] currentRunArgDefinition, isSupportedArg := supportedRunArgsMap[types.RunArg(argKey)]
if !isSupportedArg { if !isSupportedArg {
primaryLoopCounter++ argLoopCounter++
continue continue
} }
updatedPrimaryLoopCounter, allowedValues, isAnyValueBlocked := getValues(runArgsRaw, argParts, updatedArgLoopCounter, allowedValues, isAnyValueBlocked := getValues(runArgsRaw, argParts, argLoopCounter,
primaryLoopCounter, currentRunArgDefinition) currentRunArgDefinition)
primaryLoopCounter = updatedPrimaryLoopCounter argLoopCounter = updatedArgLoopCounter
if isAnyValueBlocked && len(allowedValues) == 0 { if isAnyValueBlocked && len(allowedValues) == 0 {
continue continue
@ -84,46 +85,70 @@ func ExtractRunArgs(
func getValues( func getValues(
runArgs []string, runArgs []string,
argParts []string, argParts []string,
primaryLoopCounter int, argLoopCounter int,
currentRunArgDefinition types.RunArgDefinition, currentRunArgDefinition types.RunArgDefinition,
) (int, []string, bool) { ) (int, []string, bool) {
values := make([]string, 0) values := make([]string, 0)
if len(argParts) > 1 { if len(argParts) > 1 {
values = append(values, argParts[1]) values = append(values, strings.TrimSpace(argParts[1]))
primaryLoopCounter++ argLoopCounter++
} else { } else {
var secondaryLoopCounter = primaryLoopCounter + 1 var valueLoopCounter = argLoopCounter + 1
for secondaryLoopCounter < len(runArgs) { for valueLoopCounter < len(runArgs) {
currentValue := runArgs[secondaryLoopCounter] currentValue := runArgs[valueLoopCounter]
if isArg(currentValue) { if isArg(currentValue) {
break break
} }
values = append(values, currentValue) values = append(values, strings.TrimSpace(currentValue))
secondaryLoopCounter++ valueLoopCounter++
} }
primaryLoopCounter = secondaryLoopCounter argLoopCounter = valueLoopCounter
} }
allowedValues, isAnyValueBlocked := filterAllowedValues(values, currentRunArgDefinition) allowedValues, isAnyValueBlocked := filterAllowedValues(values, currentRunArgDefinition)
return primaryLoopCounter, allowedValues, isAnyValueBlocked return argLoopCounter, allowedValues, isAnyValueBlocked
} }
func filterAllowedValues(values []string, currentRunArgDefinition types.RunArgDefinition) ([]string, bool) { func filterAllowedValues(
values []string,
currentRunArgDefinition types.RunArgDefinition,
) ([]string, bool) {
isAnyValueBlocked := false isAnyValueBlocked := false
allowedValues := make([]string, 0) allowedValues := make([]string, 0)
for _, v := range values { for _, v := range values {
switch { switch {
case len(currentRunArgDefinition.AllowedValues) > 0: case len(currentRunArgDefinition.AllowedValues) > 0:
if _, ok := currentRunArgDefinition.AllowedValues[v]; ok { for allowedValue := range currentRunArgDefinition.AllowedValues {
matches, err := regexp.MatchString(allowedValue, v)
if err != nil {
log.Warn().Err(err).Msgf("error checking allowed values for RunArg %s value %s",
currentRunArgDefinition.Name, v)
continue
}
if matches {
allowedValues = append(allowedValues, v) allowedValues = append(allowedValues, v)
} else { } else {
log.Warn().Msgf("Value %s for runArg %s not allowed", v, currentRunArgDefinition.Name)
isAnyValueBlocked = true isAnyValueBlocked = true
} }
}
case len(currentRunArgDefinition.BlockedValues) > 0: case len(currentRunArgDefinition.BlockedValues) > 0:
if _, ok := currentRunArgDefinition.BlockedValues[v]; !ok { var isValueBlocked = false
allowedValues = append(allowedValues, v) for blockedValue := range currentRunArgDefinition.BlockedValues {
} else { matches, err := regexp.MatchString(blockedValue, v)
if err != nil {
log.Warn().Err(err).Msgf("error checking blocked values for RunArg %s value %s",
currentRunArgDefinition.Name, v)
continue
}
if matches {
log.Warn().Msgf("Value %s for runArg %s not allowed", v, currentRunArgDefinition.Name)
isValueBlocked = true
isAnyValueBlocked = true isAnyValueBlocked = true
} }
}
if !isValueBlocked {
allowedValues = append(allowedValues, v)
}
default: default:
allowedValues = append(allowedValues, v) allowedValues = append(allowedValues, v)
} }
@ -176,7 +201,7 @@ func ExtractIDECustomizations(
var args = make(map[string]interface{}) var args = make(map[string]interface{})
if ideService.Type() == enum.IDETypeVSCodeWeb || ideService.Type() == enum.IDETypeVSCode { if ideService.Type() == enum.IDETypeVSCodeWeb || ideService.Type() == enum.IDETypeVSCode {
if devcontainerConfig.Customizations.ExtractVSCodeSpec() != nil { if devcontainerConfig.Customizations.ExtractVSCodeSpec() != nil {
args[types2.VSCodeCustomization] = *devcontainerConfig.Customizations.ExtractVSCodeSpec() args[gitspaceTypes.VSCodeCustomization] = *devcontainerConfig.Customizations.ExtractVSCodeSpec()
} }
} }
return args return args

View File

@ -21,6 +21,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"path/filepath"
goruntime "runtime" goruntime "runtime"
"strconv" "strconv"
"strings" "strings"
@ -136,6 +137,8 @@ func CreateContainer(
portMappings map[int]*types.PortMapping, portMappings map[int]*types.PortMapping,
env []string, env []string,
runArgsMap map[types.RunArg]*types.RunArgValue, runArgsMap map[types.RunArg]*types.RunArgValue,
containerUser string,
remoteUser string,
) error { ) error {
exposedPorts, portBindings := applyPortMappings(portMappings) exposedPorts, portBindings := applyPortMappings(portMappings)
@ -161,6 +164,10 @@ func CreateContainer(
cmd = []string{"-c", "trap 'exit 0' 15; sleep infinity & wait $!"} cmd = []string{"-c", "trap 'exit 0' 15; sleep infinity & wait $!"}
} }
labels := getLabels(runArgsMap)
// Setting the following so that it can be read later to form gitspace URL.
labels[gitspaceRemoteUserLabel] = remoteUser
// Create the container // Create the container
containerConfig := &container.Config{ containerConfig := &container.Config{
Hostname: getHostname(runArgsMap), Hostname: getHostname(runArgsMap),
@ -170,12 +177,12 @@ func CreateContainer(
Entrypoint: entrypoint, Entrypoint: entrypoint,
Cmd: cmd, Cmd: cmd,
ExposedPorts: exposedPorts, ExposedPorts: exposedPorts,
Labels: getLabels(runArgsMap), Labels: labels,
Healthcheck: healthCheckConfig, Healthcheck: healthCheckConfig,
MacAddress: getMACAddress(runArgsMap), MacAddress: getMACAddress(runArgsMap),
StopSignal: getStopSignal(runArgsMap), StopSignal: getStopSignal(runArgsMap),
StopTimeout: stopTimeout, StopTimeout: stopTimeout,
User: getUser(runArgsMap), User: containerUser,
} }
_, err = dockerClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, nil, containerName) _, err = dockerClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, nil, containerName)
@ -278,10 +285,10 @@ func GetContainerInfo(
containerName string, containerName string,
dockerClient *client.Client, dockerClient *client.Client,
portMappings map[int]*types.PortMapping, portMappings map[int]*types.PortMapping,
) (string, map[int]string, error) { ) (string, map[int]string, string, error) {
inspectResp, err := dockerClient.ContainerInspect(ctx, containerName) inspectResp, err := dockerClient.ContainerInspect(ctx, containerName)
if err != nil { if err != nil {
return "", nil, fmt.Errorf("could not inspect container %s: %w", containerName, err) return "", nil, "", fmt.Errorf("could not inspect container %s: %w", containerName, err)
} }
usedPorts := make(map[int]string) usedPorts := make(map[int]string)
@ -289,7 +296,7 @@ func GetContainerInfo(
portRaw := strings.Split(string(portAndProtocol), "/")[0] portRaw := strings.Split(string(portAndProtocol), "/")[0]
port, conversionErr := strconv.Atoi(portRaw) port, conversionErr := strconv.Atoi(portRaw)
if conversionErr != nil { if conversionErr != nil {
return "", nil, fmt.Errorf("could not convert port %s to int: %w", portRaw, conversionErr) return "", nil, "", fmt.Errorf("could not convert port %s to int: %w", portRaw, conversionErr)
} }
if portMappings[port] != nil { if portMappings[port] != nil {
@ -297,7 +304,34 @@ func GetContainerInfo(
} }
} }
return inspectResp.ID, usedPorts, nil remoteUser := ExtractRemoteUserFromLabels(inspectResp)
return inspectResp.ID, usedPorts, remoteUser, nil
}
func ExtractMetadataFromImage(
ctx context.Context,
imageName string,
dockerClient *client.Client,
) (map[string]any, error) {
imageInspect, _, err := dockerClient.ImageInspectWithRaw(ctx, imageName)
if err != nil {
return nil, fmt.Errorf("error while inspecting image: %w", err)
}
metadataMap := map[string]any{}
if metadata, ok := imageInspect.Config.Labels["devcontainer.metadata"]; ok {
dst := []map[string]any{}
unmarshalErr := json.Unmarshal([]byte(metadata), &dst)
if unmarshalErr != nil {
return nil, fmt.Errorf("error while unmarshalling metadata: %w", err)
}
for _, values := range dst {
for k, v := range values {
metadataMap[k] = v
}
}
}
return metadataMap, nil
} }
func PullImage( func PullImage(
@ -429,7 +463,6 @@ func ExtractRunArgsWithLogging(
runArgsRaw []string, runArgsRaw []string,
gitspaceLogger gitspaceTypes.GitspaceLogger, gitspaceLogger gitspaceTypes.GitspaceLogger,
) (map[types.RunArg]*types.RunArgValue, error) { ) (map[types.RunArg]*types.RunArgValue, error) {
gitspaceLogger.Info("Extracting runsArgs")
runArgsMap, err := ExtractRunArgs(ctx, spaceID, runArgProvider, runArgsRaw) runArgsMap, err := ExtractRunArgs(ctx, spaceID, runArgProvider, runArgsRaw)
if err != nil { if err != nil {
return nil, logStreamWrapError(gitspaceLogger, "Error while extracting runArgs", err) return nil, logStreamWrapError(gitspaceLogger, "Error while extracting runArgs", err)
@ -439,7 +472,9 @@ func ExtractRunArgsWithLogging(
for key, value := range runArgsMap { for key, value := range runArgsMap {
st = fmt.Sprintf("%s%s: %s\n", st, key, value) st = fmt.Sprintf("%s%s: %s\n", st, key, value)
} }
gitspaceLogger.Info(fmt.Sprintf("Extracted following runArgs\n%v", st)) gitspaceLogger.Info(fmt.Sprintf("Using the following runArgs\n%v", st))
} else {
gitspaceLogger.Info("No runArgs found")
} }
return runArgsMap, nil return runArgsMap, nil
} }
@ -450,20 +485,38 @@ func GetContainerResponse(
dockerClient *client.Client, dockerClient *client.Client,
containerName string, containerName string,
portMappings map[int]*types.PortMapping, portMappings map[int]*types.PortMapping,
codeRepoDir string, repoName string,
) (*StartResponse, error) { ) (*StartResponse, error) {
id, ports, err := GetContainerInfo(ctx, containerName, dockerClient, portMappings) id, ports, remoteUser, err := GetContainerInfo(ctx, containerName, dockerClient, portMappings)
if err != nil { if err != nil {
return nil, err return nil, err
} }
homeDir := GetUserHomeDir(remoteUser)
codeRepoDir := filepath.Join(homeDir, repoName)
return &StartResponse{ return &StartResponse{
ContainerID: id, ContainerID: id,
ContainerName: containerName, ContainerName: containerName,
PublishedPorts: ports, PublishedPorts: ports,
AbsoluteRepoPath: codeRepoDir, AbsoluteRepoPath: codeRepoDir,
RemoteUser: remoteUser,
}, nil }, nil
} }
func GetRemoteUserFromContainerLabel(
ctx context.Context,
containerName string,
dockerClient *client.Client,
) (string, error) {
inspectResp, err := dockerClient.ContainerInspect(ctx, containerName)
if err != nil {
return "", fmt.Errorf("could not inspect container %s: %w", containerName, err)
}
return ExtractRemoteUserFromLabels(inspectResp), nil
}
// Helper function to encode the AuthConfig into a Base64 string. // Helper function to encode the AuthConfig into a Base64 string.
func encodeAuthToBase64(authConfig registry.AuthConfig) (string, error) { func encodeAuthToBase64(authConfig registry.AuthConfig) (string, error) {
authJSON, err := json.Marshal(authConfig) authJSON, err := json.Marshal(authConfig)

View File

@ -159,11 +159,8 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace(
return nil, fmt.Errorf("gitspace %s is in a bad state: %s", containerName, state) return nil, fmt.Errorf("gitspace %s is in a bad state: %s", containerName, state)
} }
homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier)
codeRepoDir := filepath.Join(homeDir, resolvedRepoDetails.RepoName)
// Step 5: Retrieve container information and return response // Step 5: Retrieve container information and return response
return GetContainerResponse(ctx, dockerClient, containerName, infra.GitspacePortMappings, codeRepoDir) return GetContainerResponse(ctx, dockerClient, containerName, infra.GitspacePortMappings, resolvedRepoDetails.RepoName)
} }
// startStoppedGitspace starts the Gitspace container if it was stopped. // startStoppedGitspace starts the Gitspace container if it was stopped.
@ -179,23 +176,31 @@ func (e *EmbeddedDockerOrchestrator) startStoppedGitspace(
containerName := GetGitspaceContainerName(gitspaceConfig) containerName := GetGitspaceContainerName(gitspaceConfig)
if err != nil { if err != nil {
return fmt.Errorf("error getting log stream for gitspace ID %d: %w", gitspaceConfig.ID, err) return fmt.Errorf("error getting log stream for gitspace instance %s: %w",
gitspaceConfig.GitspaceInstance.Identifier, err)
} }
defer e.flushLogStream(logStreamInstance, gitspaceConfig.ID) defer e.flushLogStream(logStreamInstance, gitspaceConfig.ID)
remoteUser, err := GetRemoteUserFromContainerLabel(ctx, containerName, dockerClient)
if err != nil {
return fmt.Errorf("error getting remote user for gitspace instance %s: %w",
gitspaceConfig.GitspaceInstance.Identifier, err)
}
homeDir := GetUserHomeDir(remoteUser)
startErr := ManageContainer(ctx, ContainerActionStart, containerName, dockerClient, logStreamInstance) startErr := ManageContainer(ctx, ContainerActionStart, containerName, dockerClient, logStreamInstance)
if startErr != nil { if startErr != nil {
return startErr return startErr
} }
homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier)
codeRepoDir := filepath.Join(homeDir, resolvedRepoDetails.RepoName) codeRepoDir := filepath.Join(homeDir, resolvedRepoDetails.RepoName)
exec := &devcontainer.Exec{ exec := &devcontainer.Exec{
ContainerName: containerName, ContainerName: containerName,
DockerClient: dockerClient, DockerClient: dockerClient,
HomeDir: homeDir, HomeDir: homeDir,
UserIdentifier: gitspaceConfig.GitspaceUser.Identifier, RemoteUser: remoteUser,
AccessKey: accessKey, AccessKey: accessKey,
AccessType: gitspaceConfig.GitspaceInstance.AccessType, AccessType: gitspaceConfig.GitspaceInstance.AccessType,
} }
@ -376,14 +381,10 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
gitspaceLogger gitspaceTypes.GitspaceLogger, gitspaceLogger gitspaceTypes.GitspaceLogger,
imageAuthMap map[string]gitspaceTypes.DockerRegistryAuth, imageAuthMap map[string]gitspaceTypes.DockerRegistryAuth,
) error { ) error {
homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier)
containerName := GetGitspaceContainerName(gitspaceConfig) containerName := GetGitspaceContainerName(gitspaceConfig)
devcontainerConfig := resolvedRepoDetails.DevcontainerConfig devcontainerConfig := resolvedRepoDetails.DevcontainerConfig
imageName := devcontainerConfig.Image imageName := GetImage(devcontainerConfig, defaultBaseImage)
if imageName == "" {
imageName = defaultBaseImage
}
runArgsMap, err := ExtractRunArgsWithLogging(ctx, gitspaceConfig.SpaceID, e.runArgProvider, runArgsMap, err := ExtractRunArgsWithLogging(ctx, gitspaceConfig.SpaceID, e.runArgProvider,
devcontainerConfig.RunArgs, gitspaceLogger) devcontainerConfig.RunArgs, gitspaceLogger)
@ -395,6 +396,12 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
if err := PullImage(ctx, imageName, dockerClient, runArgsMap, gitspaceLogger, imageAuthMap); err != nil { if err := PullImage(ctx, imageName, dockerClient, runArgsMap, gitspaceLogger, imageAuthMap); err != nil {
return err return err
} }
metadataFromImage, err := ExtractMetadataFromImage(ctx, imageName, dockerClient)
if err != nil {
return err
}
portMappings := infrastructure.GitspacePortMappings portMappings := infrastructure.GitspacePortMappings
forwardPorts := ExtractForwardPorts(devcontainerConfig) forwardPorts := ExtractForwardPorts(devcontainerConfig)
if len(forwardPorts) > 0 { if len(forwardPorts) > 0 {
@ -412,6 +419,15 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
if len(environment) > 0 { if len(environment) > 0 {
gitspaceLogger.Info(fmt.Sprintf("Setting Environment : %v", environment)) gitspaceLogger.Info(fmt.Sprintf("Setting Environment : %v", environment))
} }
containerUser := GetContainerUser(runArgsMap, devcontainerConfig, metadataFromImage)
remoteUser := GetRemoteUser(devcontainerConfig, metadataFromImage, containerUser)
homeDir := GetUserHomeDir(remoteUser)
gitspaceLogger.Info(fmt.Sprintf("Container user: %s", containerUser))
gitspaceLogger.Info(fmt.Sprintf("Remote user: %s", remoteUser))
// Create the container // Create the container
err = CreateContainer( err = CreateContainer(
ctx, ctx,
@ -425,6 +441,8 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
portMappings, portMappings,
environment, environment,
runArgsMap, runArgsMap,
containerUser,
remoteUser,
) )
if err != nil { if err != nil {
return err return err
@ -440,7 +458,7 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
ContainerName: containerName, ContainerName: containerName,
DockerClient: dockerClient, DockerClient: dockerClient,
HomeDir: homeDir, HomeDir: homeDir,
UserIdentifier: gitspaceConfig.GitspaceUser.Identifier, RemoteUser: remoteUser,
AccessKey: *gitspaceConfig.GitspaceInstance.AccessKey, AccessKey: *gitspaceConfig.GitspaceInstance.AccessKey,
AccessType: gitspaceConfig.GitspaceInstance.AccessType, AccessType: gitspaceConfig.GitspaceInstance.AccessType,
} }

View File

@ -372,8 +372,8 @@ func getHealthCheckConfig(runArgsMap map[types.RunArg]*types.RunArgValue) (*cont
} }
func getLabels(runArgsMap map[types.RunArg]*types.RunArgValue) map[string]string { func getLabels(runArgsMap map[types.RunArg]*types.RunArgValue) map[string]string {
arg, ok := runArgsMap[types.RunArgLabel]
labelsMap := make(map[string]string) labelsMap := make(map[string]string)
arg, ok := runArgsMap[types.RunArgLabel]
if ok { if ok {
labels := arg.Values labels := arg.Values
for _, v := range labels { for _, v := range labels {

View File

@ -19,6 +19,7 @@ type StartResponse struct {
ContainerName string ContainerName string
PublishedPorts map[int]string PublishedPorts map[int]string
AbsoluteRepoPath string AbsoluteRepoPath string
RemoteUser string
} }
type PostAction string type PostAction string

View File

@ -18,10 +18,14 @@ import (
"path/filepath" "path/filepath"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
types2 "github.com/docker/docker/api/types"
) )
const ( const (
linuxHome = "/home" linuxHome = "/home"
deprecatedRemoteUser = "harness"
gitspaceRemoteUserLabel = "gitspace.remote.user"
) )
func GetGitspaceContainerName(config types.GitspaceConfig) string { func GetGitspaceContainerName(config types.GitspaceConfig) string {
@ -29,5 +33,56 @@ func GetGitspaceContainerName(config types.GitspaceConfig) string {
} }
func GetUserHomeDir(userIdentifier string) string { func GetUserHomeDir(userIdentifier string) string {
if userIdentifier == "root" {
return "/root"
}
return filepath.Join(linuxHome, userIdentifier) return filepath.Join(linuxHome, userIdentifier)
} }
func GetImage(devcontainerConfig types.DevcontainerConfig, defaultBaseImage string) string {
imageName := devcontainerConfig.Image
if imageName == "" {
imageName = defaultBaseImage
}
return imageName
}
func GetContainerUser(
runArgsMap map[types.RunArg]*types.RunArgValue,
devcontainerConfig types.DevcontainerConfig,
metadataFromImage map[string]any,
) string {
if containerUser := getUser(runArgsMap); containerUser != "" {
return containerUser
}
if devcontainerConfig.ContainerUser != "" {
return devcontainerConfig.ContainerUser
}
if containerUser, ok := metadataFromImage["containerUser"].(string); ok {
return containerUser
}
return ""
}
func ExtractRemoteUserFromLabels(inspectResp types2.ContainerJSON) string {
remoteUser := deprecatedRemoteUser
if remoteUserValue, ok := inspectResp.Config.Labels[gitspaceRemoteUserLabel]; ok {
remoteUser = remoteUserValue
}
return remoteUser
}
func GetRemoteUser(
devcontainerConfig types.DevcontainerConfig,
metadataFromImage map[string]any,
containerUser string,
) string {
if devcontainerConfig.RemoteUser != "" {
return devcontainerConfig.RemoteUser
}
if remoteUser, ok := metadataFromImage["remoteUser"].(string); ok {
return remoteUser
}
return containerUser
}

View File

@ -37,7 +37,7 @@ type Exec struct {
ContainerName string ContainerName string
DockerClient *client.Client DockerClient *client.Client
HomeDir string HomeDir string
UserIdentifier string RemoteUser string
AccessKey string AccessKey string
AccessType enum.GitspaceAccessType AccessType enum.GitspaceAccessType
} }
@ -56,7 +56,7 @@ func (e *Exec) ExecuteCommand(
workingDir string, workingDir string,
outputCh chan []byte, // channel to stream output as []byte outputCh chan []byte, // channel to stream output as []byte
) error { ) error {
user := e.UserIdentifier user := e.RemoteUser
if root { if root {
user = RootUser user = RootUser
} }

View File

@ -53,7 +53,7 @@ func (v *VSCode) Setup(
osInfoScript := common.GetOSInfoScript() osInfoScript := common.GetOSInfoScript()
sshServerScript, err := template.GenerateScriptFromTemplate( sshServerScript, err := template.GenerateScriptFromTemplate(
templateSetupSSHServer, &template.SetupSSHServerPayload{ templateSetupSSHServer, &template.SetupSSHServerPayload{
Username: exec.UserIdentifier, Username: exec.RemoteUser,
AccessType: exec.AccessType, AccessType: exec.AccessType,
OSInfoScript: osInfoScript, OSInfoScript: osInfoScript,
}) })

View File

@ -220,7 +220,7 @@ func generateIDEURL(
Host: "", // Empty since we include the host and port in the path Host: "", // Empty since we include the host and port in the path
Path: fmt.Sprintf( Path: fmt.Sprintf(
"ssh-remote+%s@%s:%s", "ssh-remote+%s@%s:%s",
gitspaceConfig.GitspaceUser.Identifier, startResponse.RemoteUser,
host, host,
filepath.Join(forwardedPort, relativeRepoPath), filepath.Join(forwardedPort, relativeRepoPath),
), ),

View File

@ -0,0 +1,54 @@
// 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 runarg
import (
"fmt"
"github.com/harness/gitness/types"
"gopkg.in/yaml.v3"
_ "embed"
)
//go:embed runArgs.yaml
var supportedRunArgsRaw []byte
type Resolver struct {
supportedRunArgsMap map[types.RunArg]types.RunArgDefinition
}
func NewResolver() (*Resolver, error) {
allRunArgs := make([]types.RunArgDefinition, 0)
err := yaml.Unmarshal(supportedRunArgsRaw, &allRunArgs)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal runArgs.yaml: %w", err)
}
argsMap := make(map[types.RunArg]types.RunArgDefinition)
for _, arg := range allRunArgs {
if arg.Supported {
argsMap[arg.Name] = arg
if arg.ShortHand != "" {
argsMap[arg.ShortHand] = arg
}
}
}
return &Resolver{supportedRunArgsMap: argsMap}, nil
}
func (r *Resolver) ResolveSupportedRunArgs() map[types.RunArg]types.RunArgDefinition {
return r.supportedRunArgsMap
}

View File

@ -394,7 +394,8 @@
- name: --label - name: --label
short_hand: -l short_hand: -l
supported: true supported: true
blocked_values: { } blocked_values:
^gitspace\.remote\.user=: true
allowed_values: { } allowed_values: { }
allow_multiple_occurrences: true allow_multiple_occurrences: true
@ -486,8 +487,8 @@
short_hand: short_hand:
supported: true supported: true
blocked_values: blocked_values:
host: true ^host$: true
none: true ^none$: true
allowed_values: { } allowed_values: { }
allow_multiple_occurrences: false allow_multiple_occurrences: false

View File

@ -16,40 +16,18 @@ package runarg
import ( import (
"context" "context"
"fmt"
"github.com/harness/gitness/types" "github.com/harness/gitness/types"
"gopkg.in/yaml.v3"
_ "embed"
) )
var _ Provider = (*StaticProvider)(nil) var _ Provider = (*StaticProvider)(nil)
//go:embed runArgs.yaml
var supportedRunArgsRaw []byte
type StaticProvider struct { type StaticProvider struct {
supportedRunArgsMap map[types.RunArg]types.RunArgDefinition supportedRunArgsMap map[types.RunArg]types.RunArgDefinition
} }
func NewStaticProvider() (Provider, error) { func NewStaticProvider(resolver *Resolver) (Provider, error) {
allRunArgs := make([]types.RunArgDefinition, 0) return &StaticProvider{supportedRunArgsMap: resolver.ResolveSupportedRunArgs()}, nil
err := yaml.Unmarshal(supportedRunArgsRaw, &allRunArgs)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal runArgs.yaml: %w", err)
}
argsMap := make(map[types.RunArg]types.RunArgDefinition)
for _, arg := range allRunArgs {
if arg.Supported {
argsMap[arg.Name] = arg
if arg.ShortHand != "" {
argsMap[arg.ShortHand] = arg
}
}
}
return &StaticProvider{supportedRunArgsMap: argsMap}, nil
} }
// ProvideSupportedRunArgs provides a static map of supported run args. // ProvideSupportedRunArgs provides a static map of supported run args.

View File

@ -20,8 +20,12 @@ import (
var WireSet = wire.NewSet( var WireSet = wire.NewSet(
ProvideStaticProvider, ProvideStaticProvider,
ProvideResolver,
) )
func ProvideStaticProvider() (Provider, error) { func ProvideStaticProvider(resolver *Resolver) (Provider, error) {
return NewStaticProvider() return NewStaticProvider(resolver)
}
func ProvideResolver() (*Resolver, error) {
return NewResolver()
} }

View File

@ -67,7 +67,6 @@ type SetupUserPayload struct {
AccessKey string AccessKey string
AccessType enum.GitspaceAccessType AccessType enum.GitspaceAccessType
HomeDir string HomeDir string
OSInfoScript string
} }
type SetupSSHServerPayload struct { type SetupSSHServerPayload struct {

View File

@ -1,65 +1,41 @@
#!/bin/sh #!/bin/sh
username={{ .Username }} username="{{ .Username }}"
accessKey="{{ .AccessKey }}" accessKey="{{ .AccessKey }}"
homeDir={{ .HomeDir }} homeDir="{{ .HomeDir }}"
accessType={{ .AccessType }} accessType={{ .AccessType }}
osInfoScript={{ .OSInfoScript }}
eval "$osInfoScript"
# Check if the user already exists
if id "$username" >/dev/null 2>&1; then
echo "User $username already exists."
else
# Create a new user
case "$(distro)" in
debian)
apt-get update && apt-get install -y adduser && apt-get install -y sudo
adduser --disabled-password --home "$homeDir" --gecos "" "$username"
usermod -aG sudo "$username"
echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers
;;
fedora)
useradd -m -d "$homeDir" "$username"
usermod -aG wheel "$username"
dnf install -y sudo
echo "%wheel ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers
;;
opensuse)
useradd -m -d "$homeDir" "$username"
passwd -l "$username" # Locks the password to prevent login
zypper in -y sudo
groupadd sudo
usermod -aG sudo "$username"
echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers
chown -R "$username":sudo "$homeDir"
;;
alpine)
adduser -h "$homeDir" -s /bin/ash -D "$username" # Default shell is ash for Alpine
;;
arch)
useradd -m -d "$homeDir" -s /bin/bash "$username"
;;
freebsd)
pw useradd -n "$username" -d "$homeDir" -m
;;
*)
echo "Unsupported distribution: $distro."
exit 1
;;
esac
# Check if the user's home directory exists
if [ ! -d "$homeDir" ]; then
echo "Directory $homeDir does not exist. Creating it..."
mkdir -p "$homeDir"
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Failed to create user $username." echo "Failed to create directory $homeDir."
exit 1
fi
else
echo "Directory $homeDir already exists."
fi
# Ensure the user has ownership and permissions to the home directory
currentOwner=$(stat -c '%U' "$homeDir")
if [ "$currentOwner" != "$username" ]; then
echo "Updating ownership of $homeDir to $username..."
chown -R "$username:$username" "$homeDir"
if [ $? -ne 0 ]; then
echo "Failed to update ownership of $homeDir."
exit 1 exit 1
fi fi
fi fi
# Changing ownership of everything inside user home to the newly created user echo "Ensuring proper permissions for $homeDir..."
chown -R $username:$username $homeDir chmod 755 "$homeDir"
echo "Changing ownership of dir $homeDir to $username." if [ $? -ne 0 ]; then
chmod 755 $homeDir echo "Failed to set permissions for $homeDir."
exit 1
fi
echo "Directory setup for $username is complete."
if [ "ssh_key" = "$accessType" ] ; then if [ "ssh_key" = "$accessType" ] ; then
echo "Add ssh key in $homeDir/.ssh/authorized_keys" echo "Add ssh key in $homeDir/.ssh/authorized_keys"
@ -68,7 +44,6 @@ if [ "ssh_key" = "$accessType" ] ; then
echo $accessKey > $homeDir/.ssh/authorized_keys echo $accessKey > $homeDir/.ssh/authorized_keys
chmod 600 $homeDir/.ssh/authorized_keys chmod 600 $homeDir/.ssh/authorized_keys
chown -R $username:$username $homeDir/.ssh chown -R $username:$username $homeDir/.ssh
echo "$username:$username" | chpasswd
elif [ "user_credentials" = "$accessType" ] ; then elif [ "user_credentials" = "$accessType" ] ; then
echo "$username:$accessKey" | chpasswd echo "$username:$accessKey" | chpasswd
else else

View File

@ -47,8 +47,8 @@ config_file='/etc/ssh/sshd_config'
grep -q "^AllowUsers" $config_file grep -q "^AllowUsers" $config_file
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
# If AllowUsers exists, add the user to it # If AllowUsers exists, overwrite all existing users with new user
sed -i "/^AllowUsers/ s/$/ $username/" $config_file sed -i "s/^AllowUsers.*/AllowUsers $username/" $config_file
else else
# Otherwise, add a new AllowUsers line # Otherwise, add a new AllowUsers line
echo "AllowUsers $username" >> $config_file echo "AllowUsers $username" >> $config_file
@ -67,10 +67,15 @@ echo "AuthorizedKeysFile .ssh/authorized_keys" >> $config_file
echo "PubkeyAuthentication yes" >> $config_file echo "PubkeyAuthentication yes" >> $config_file
else else
# Ensure password authentication is enabled # Ensure password authentication is enabled
sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' $config_file
if ! grep -q "^PasswordAuthentication yes" $config_file; then if ! grep -q "^PasswordAuthentication yes" $config_file; then
echo "PasswordAuthentication yes" >> $config_file echo "PasswordAuthentication yes" >> $config_file
fi fi
if ! grep -q "^PermitEmptyPasswords yes" $config_file; then
echo "PermitEmptyPasswords yes" >> $config_file
fi
if ! grep -q "^PermitRootLogin yes" $config_file; then
echo "PermitRootLogin yes" >> $config_file
fi
fi fi
mkdir -p /var/run/sshd mkdir -p /var/run/sshd

View File

@ -40,28 +40,25 @@ func (u *ServiceImpl) Manage(
exec *devcontainer.Exec, exec *devcontainer.Exec,
gitspaceLogger gitspaceTypes.GitspaceLogger, gitspaceLogger gitspaceTypes.GitspaceLogger,
) error { ) error {
osInfoScript := common.GetOSInfoScript()
script, err := template.GenerateScriptFromTemplate( script, err := template.GenerateScriptFromTemplate(
templateManagerUser, &template.SetupUserPayload{ templateManagerUser, &template.SetupUserPayload{
Username: exec.UserIdentifier, Username: exec.RemoteUser,
AccessKey: exec.AccessKey, AccessKey: exec.AccessKey,
AccessType: exec.AccessType, AccessType: exec.AccessType,
HomeDir: exec.HomeDir, HomeDir: exec.HomeDir,
OSInfoScript: osInfoScript,
}) })
if err != nil { if err != nil {
return fmt.Errorf( return fmt.Errorf(
"failed to generate scipt to manager user from template %s: %w", templateManagerUser, err) "failed to generate scipt to manager user from template %s: %w", templateManagerUser, err)
} }
gitspaceLogger.Info("Setting up user inside container") gitspaceLogger.Info("Configuring user directory and credentials inside container")
gitspaceLogger.Info("Managing user output...")
err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, script, true, gitspaceLogger) err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, script, true, gitspaceLogger)
if err != nil { if err != nil {
return fmt.Errorf("failed to setup user: %w", err) return fmt.Errorf("failed to setup user: %w", err)
} }
gitspaceLogger.Info("Successfully setup user") gitspaceLogger.Info("Successfully configured the user directory and credentials.")
return nil return nil
} }

View File

@ -29,6 +29,7 @@ const VSCodeProxyURI = "VSCODE_PROXY_URI"
type GitspaceLogger interface { type GitspaceLogger interface {
Info(msg string) Info(msg string)
Debug(msg string) Debug(msg string)
Warn(msg string)
Error(msg string, err error) Error(msg string, err error)
} }

View File

@ -32,6 +32,10 @@ func (z *ZerologAdapter) Debug(msg string) {
z.logger.Debug().Msg("DEBUG: " + msg) z.logger.Debug().Msg("DEBUG: " + msg)
} }
func (z *ZerologAdapter) Warn(msg string) {
z.logger.Warn().Msg("WARN: " + msg)
}
func (z *ZerologAdapter) Error(msg string, err error) { func (z *ZerologAdapter) Error(msg string, err error) {
z.logger.Err(err).Msg("ERROR: " + msg) z.logger.Err(err).Msg("ERROR: " + msg)
} }

View File

@ -319,7 +319,11 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
statefulLogger := logutil.ProvideStatefulLogger(logStream) statefulLogger := logutil.ProvideStatefulLogger(logStream)
gitService := git2.ProvideGitServiceImpl() gitService := git2.ProvideGitServiceImpl()
userService := user2.ProvideUserServiceImpl() userService := user2.ProvideUserServiceImpl()
runargProvider, err := runarg.ProvideStaticProvider() runargResolver, err := runarg.ProvideResolver()
if err != nil {
return nil, err
}
runargProvider, err := runarg.ProvideStaticProvider(runargResolver)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -28,6 +28,8 @@ type DevcontainerConfig struct {
ContainerEnv map[string]string `json:"containerEnv,omitempty"` //nolint:tagliatelle ContainerEnv map[string]string `json:"containerEnv,omitempty"` //nolint:tagliatelle
Customizations DevContainerConfigCustomizations `json:"customizations,omitempty"` Customizations DevContainerConfigCustomizations `json:"customizations,omitempty"`
RunArgs []string `json:"runArgs,omitempty"` //nolint:tagliatelle RunArgs []string `json:"runArgs,omitempty"` //nolint:tagliatelle
ContainerUser string `json:"containerUser,omitempty"` //nolint:tagliatelle
RemoteUser string `json:"remoteUser,omitempty"` //nolint:tagliatelle
} }
// LifecycleCommand supports multiple formats for lifecycle commands. // LifecycleCommand supports multiple formats for lifecycle commands.