mirror of https://github.com/harness/drone.git
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.pull/3597/head
parent
b7cca56ec7
commit
1086397b3f
|
@ -124,6 +124,10 @@ func (l *LogStreamInstance) Debug(msg string) {
|
|||
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) {
|
||||
l.Write("ERROR: " + msg + ": " + err.Error()) //nolint:errcheck
|
||||
}
|
||||
|
|
|
@ -17,11 +17,12 @@ package container
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/harness/gitness/app/gitspace/orchestrator/ide"
|
||||
"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/enum"
|
||||
|
||||
|
@ -40,11 +41,11 @@ func ExtractRunArgs(
|
|||
}
|
||||
|
||||
var runArgsMap = make(map[types.RunArg]*types.RunArgValue)
|
||||
primaryLoopCounter := 0
|
||||
for primaryLoopCounter < len(runArgsRaw) {
|
||||
currentArg := runArgsRaw[primaryLoopCounter]
|
||||
argLoopCounter := 0
|
||||
for argLoopCounter < len(runArgsRaw) {
|
||||
currentArg := runArgsRaw[argLoopCounter]
|
||||
if currentArg == "" || !isArg(currentArg) {
|
||||
primaryLoopCounter++
|
||||
argLoopCounter++
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -53,13 +54,13 @@ func ExtractRunArgs(
|
|||
|
||||
currentRunArgDefinition, isSupportedArg := supportedRunArgsMap[types.RunArg(argKey)]
|
||||
if !isSupportedArg {
|
||||
primaryLoopCounter++
|
||||
argLoopCounter++
|
||||
continue
|
||||
}
|
||||
updatedPrimaryLoopCounter, allowedValues, isAnyValueBlocked := getValues(runArgsRaw, argParts,
|
||||
primaryLoopCounter, currentRunArgDefinition)
|
||||
updatedArgLoopCounter, allowedValues, isAnyValueBlocked := getValues(runArgsRaw, argParts, argLoopCounter,
|
||||
currentRunArgDefinition)
|
||||
|
||||
primaryLoopCounter = updatedPrimaryLoopCounter
|
||||
argLoopCounter = updatedArgLoopCounter
|
||||
|
||||
if isAnyValueBlocked && len(allowedValues) == 0 {
|
||||
continue
|
||||
|
@ -84,45 +85,69 @@ func ExtractRunArgs(
|
|||
func getValues(
|
||||
runArgs []string,
|
||||
argParts []string,
|
||||
primaryLoopCounter int,
|
||||
argLoopCounter int,
|
||||
currentRunArgDefinition types.RunArgDefinition,
|
||||
) (int, []string, bool) {
|
||||
values := make([]string, 0)
|
||||
if len(argParts) > 1 {
|
||||
values = append(values, argParts[1])
|
||||
primaryLoopCounter++
|
||||
values = append(values, strings.TrimSpace(argParts[1]))
|
||||
argLoopCounter++
|
||||
} else {
|
||||
var secondaryLoopCounter = primaryLoopCounter + 1
|
||||
for secondaryLoopCounter < len(runArgs) {
|
||||
currentValue := runArgs[secondaryLoopCounter]
|
||||
var valueLoopCounter = argLoopCounter + 1
|
||||
for valueLoopCounter < len(runArgs) {
|
||||
currentValue := runArgs[valueLoopCounter]
|
||||
if isArg(currentValue) {
|
||||
break
|
||||
}
|
||||
values = append(values, currentValue)
|
||||
secondaryLoopCounter++
|
||||
values = append(values, strings.TrimSpace(currentValue))
|
||||
valueLoopCounter++
|
||||
}
|
||||
primaryLoopCounter = secondaryLoopCounter
|
||||
argLoopCounter = valueLoopCounter
|
||||
}
|
||||
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
|
||||
allowedValues := make([]string, 0)
|
||||
for _, v := range values {
|
||||
switch {
|
||||
case len(currentRunArgDefinition.AllowedValues) > 0:
|
||||
if _, ok := currentRunArgDefinition.AllowedValues[v]; ok {
|
||||
allowedValues = append(allowedValues, v)
|
||||
} else {
|
||||
isAnyValueBlocked = true
|
||||
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)
|
||||
} else {
|
||||
log.Warn().Msgf("Value %s for runArg %s not allowed", v, currentRunArgDefinition.Name)
|
||||
isAnyValueBlocked = true
|
||||
}
|
||||
}
|
||||
case len(currentRunArgDefinition.BlockedValues) > 0:
|
||||
if _, ok := currentRunArgDefinition.BlockedValues[v]; !ok {
|
||||
var isValueBlocked = false
|
||||
for blockedValue := range currentRunArgDefinition.BlockedValues {
|
||||
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
|
||||
}
|
||||
}
|
||||
if !isValueBlocked {
|
||||
allowedValues = append(allowedValues, v)
|
||||
} else {
|
||||
isAnyValueBlocked = true
|
||||
}
|
||||
default:
|
||||
allowedValues = append(allowedValues, v)
|
||||
|
@ -176,7 +201,7 @@ func ExtractIDECustomizations(
|
|||
var args = make(map[string]interface{})
|
||||
if ideService.Type() == enum.IDETypeVSCodeWeb || ideService.Type() == enum.IDETypeVSCode {
|
||||
if devcontainerConfig.Customizations.ExtractVSCodeSpec() != nil {
|
||||
args[types2.VSCodeCustomization] = *devcontainerConfig.Customizations.ExtractVSCodeSpec()
|
||||
args[gitspaceTypes.VSCodeCustomization] = *devcontainerConfig.Customizations.ExtractVSCodeSpec()
|
||||
}
|
||||
}
|
||||
return args
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
goruntime "runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -136,6 +137,8 @@ func CreateContainer(
|
|||
portMappings map[int]*types.PortMapping,
|
||||
env []string,
|
||||
runArgsMap map[types.RunArg]*types.RunArgValue,
|
||||
containerUser string,
|
||||
remoteUser string,
|
||||
) error {
|
||||
exposedPorts, portBindings := applyPortMappings(portMappings)
|
||||
|
||||
|
@ -161,6 +164,10 @@ func CreateContainer(
|
|||
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
|
||||
containerConfig := &container.Config{
|
||||
Hostname: getHostname(runArgsMap),
|
||||
|
@ -170,12 +177,12 @@ func CreateContainer(
|
|||
Entrypoint: entrypoint,
|
||||
Cmd: cmd,
|
||||
ExposedPorts: exposedPorts,
|
||||
Labels: getLabels(runArgsMap),
|
||||
Labels: labels,
|
||||
Healthcheck: healthCheckConfig,
|
||||
MacAddress: getMACAddress(runArgsMap),
|
||||
StopSignal: getStopSignal(runArgsMap),
|
||||
StopTimeout: stopTimeout,
|
||||
User: getUser(runArgsMap),
|
||||
User: containerUser,
|
||||
}
|
||||
|
||||
_, err = dockerClient.ContainerCreate(ctx, containerConfig, hostConfig, nil, nil, containerName)
|
||||
|
@ -278,10 +285,10 @@ func GetContainerInfo(
|
|||
containerName string,
|
||||
dockerClient *client.Client,
|
||||
portMappings map[int]*types.PortMapping,
|
||||
) (string, map[int]string, error) {
|
||||
) (string, map[int]string, string, error) {
|
||||
inspectResp, err := dockerClient.ContainerInspect(ctx, containerName)
|
||||
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)
|
||||
|
@ -289,7 +296,7 @@ func GetContainerInfo(
|
|||
portRaw := strings.Split(string(portAndProtocol), "/")[0]
|
||||
port, conversionErr := strconv.Atoi(portRaw)
|
||||
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 {
|
||||
|
@ -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(
|
||||
|
@ -429,7 +463,6 @@ func ExtractRunArgsWithLogging(
|
|||
runArgsRaw []string,
|
||||
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
||||
) (map[types.RunArg]*types.RunArgValue, error) {
|
||||
gitspaceLogger.Info("Extracting runsArgs")
|
||||
runArgsMap, err := ExtractRunArgs(ctx, spaceID, runArgProvider, runArgsRaw)
|
||||
if err != nil {
|
||||
return nil, logStreamWrapError(gitspaceLogger, "Error while extracting runArgs", err)
|
||||
|
@ -439,7 +472,9 @@ func ExtractRunArgsWithLogging(
|
|||
for key, value := range runArgsMap {
|
||||
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
|
||||
}
|
||||
|
@ -450,20 +485,38 @@ func GetContainerResponse(
|
|||
dockerClient *client.Client,
|
||||
containerName string,
|
||||
portMappings map[int]*types.PortMapping,
|
||||
codeRepoDir string,
|
||||
repoName string,
|
||||
) (*StartResponse, error) {
|
||||
id, ports, err := GetContainerInfo(ctx, containerName, dockerClient, portMappings)
|
||||
id, ports, remoteUser, err := GetContainerInfo(ctx, containerName, dockerClient, portMappings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
homeDir := GetUserHomeDir(remoteUser)
|
||||
codeRepoDir := filepath.Join(homeDir, repoName)
|
||||
|
||||
return &StartResponse{
|
||||
ContainerID: id,
|
||||
ContainerName: containerName,
|
||||
PublishedPorts: ports,
|
||||
AbsoluteRepoPath: codeRepoDir,
|
||||
RemoteUser: remoteUser,
|
||||
}, 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.
|
||||
func encodeAuthToBase64(authConfig registry.AuthConfig) (string, error) {
|
||||
authJSON, err := json.Marshal(authConfig)
|
||||
|
|
|
@ -159,11 +159,8 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace(
|
|||
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
|
||||
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.
|
||||
|
@ -179,25 +176,33 @@ func (e *EmbeddedDockerOrchestrator) startStoppedGitspace(
|
|||
containerName := GetGitspaceContainerName(gitspaceConfig)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
if startErr != nil {
|
||||
return startErr
|
||||
}
|
||||
|
||||
homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier)
|
||||
codeRepoDir := filepath.Join(homeDir, resolvedRepoDetails.RepoName)
|
||||
|
||||
exec := &devcontainer.Exec{
|
||||
ContainerName: containerName,
|
||||
DockerClient: dockerClient,
|
||||
HomeDir: homeDir,
|
||||
UserIdentifier: gitspaceConfig.GitspaceUser.Identifier,
|
||||
AccessKey: accessKey,
|
||||
AccessType: gitspaceConfig.GitspaceInstance.AccessType,
|
||||
ContainerName: containerName,
|
||||
DockerClient: dockerClient,
|
||||
HomeDir: homeDir,
|
||||
RemoteUser: remoteUser,
|
||||
AccessKey: accessKey,
|
||||
AccessType: gitspaceConfig.GitspaceInstance.AccessType,
|
||||
}
|
||||
|
||||
// Set up git credentials if needed
|
||||
|
@ -376,14 +381,10 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
|
|||
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
||||
imageAuthMap map[string]gitspaceTypes.DockerRegistryAuth,
|
||||
) error {
|
||||
homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier)
|
||||
containerName := GetGitspaceContainerName(gitspaceConfig)
|
||||
|
||||
devcontainerConfig := resolvedRepoDetails.DevcontainerConfig
|
||||
imageName := devcontainerConfig.Image
|
||||
if imageName == "" {
|
||||
imageName = defaultBaseImage
|
||||
}
|
||||
imageName := GetImage(devcontainerConfig, defaultBaseImage)
|
||||
|
||||
runArgsMap, err := ExtractRunArgsWithLogging(ctx, gitspaceConfig.SpaceID, e.runArgProvider,
|
||||
devcontainerConfig.RunArgs, gitspaceLogger)
|
||||
|
@ -395,6 +396,12 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
|
|||
if err := PullImage(ctx, imageName, dockerClient, runArgsMap, gitspaceLogger, imageAuthMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metadataFromImage, err := ExtractMetadataFromImage(ctx, imageName, dockerClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
portMappings := infrastructure.GitspacePortMappings
|
||||
forwardPorts := ExtractForwardPorts(devcontainerConfig)
|
||||
if len(forwardPorts) > 0 {
|
||||
|
@ -412,6 +419,15 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
|
|||
if len(environment) > 0 {
|
||||
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
|
||||
err = CreateContainer(
|
||||
ctx,
|
||||
|
@ -425,6 +441,8 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
|
|||
portMappings,
|
||||
environment,
|
||||
runArgsMap,
|
||||
containerUser,
|
||||
remoteUser,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -437,12 +455,12 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
|
|||
|
||||
// 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,
|
||||
ContainerName: containerName,
|
||||
DockerClient: dockerClient,
|
||||
HomeDir: homeDir,
|
||||
RemoteUser: remoteUser,
|
||||
AccessKey: *gitspaceConfig.GitspaceInstance.AccessKey,
|
||||
AccessType: gitspaceConfig.GitspaceInstance.AccessType,
|
||||
}
|
||||
|
||||
if err := e.setupGitspaceAndIDE(
|
||||
|
|
|
@ -372,8 +372,8 @@ func getHealthCheckConfig(runArgsMap map[types.RunArg]*types.RunArgValue) (*cont
|
|||
}
|
||||
|
||||
func getLabels(runArgsMap map[types.RunArg]*types.RunArgValue) map[string]string {
|
||||
arg, ok := runArgsMap[types.RunArgLabel]
|
||||
labelsMap := make(map[string]string)
|
||||
arg, ok := runArgsMap[types.RunArgLabel]
|
||||
if ok {
|
||||
labels := arg.Values
|
||||
for _, v := range labels {
|
||||
|
|
|
@ -19,6 +19,7 @@ type StartResponse struct {
|
|||
ContainerName string
|
||||
PublishedPorts map[int]string
|
||||
AbsoluteRepoPath string
|
||||
RemoteUser string
|
||||
}
|
||||
|
||||
type PostAction string
|
||||
|
|
|
@ -18,10 +18,14 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
types2 "github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
const (
|
||||
linuxHome = "/home"
|
||||
linuxHome = "/home"
|
||||
deprecatedRemoteUser = "harness"
|
||||
gitspaceRemoteUserLabel = "gitspace.remote.user"
|
||||
)
|
||||
|
||||
func GetGitspaceContainerName(config types.GitspaceConfig) string {
|
||||
|
@ -29,5 +33,56 @@ func GetGitspaceContainerName(config types.GitspaceConfig) string {
|
|||
}
|
||||
|
||||
func GetUserHomeDir(userIdentifier string) string {
|
||||
if userIdentifier == "root" {
|
||||
return "/root"
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -34,12 +34,12 @@ const RootUser = "root"
|
|||
const ErrMsgTCP = "unable to upgrade to tcp, received 200"
|
||||
|
||||
type Exec struct {
|
||||
ContainerName string
|
||||
DockerClient *client.Client
|
||||
HomeDir string
|
||||
UserIdentifier string
|
||||
AccessKey string
|
||||
AccessType enum.GitspaceAccessType
|
||||
ContainerName string
|
||||
DockerClient *client.Client
|
||||
HomeDir string
|
||||
RemoteUser string
|
||||
AccessKey string
|
||||
AccessType enum.GitspaceAccessType
|
||||
}
|
||||
|
||||
type execResult struct {
|
||||
|
@ -56,7 +56,7 @@ func (e *Exec) ExecuteCommand(
|
|||
workingDir string,
|
||||
outputCh chan []byte, // channel to stream output as []byte
|
||||
) error {
|
||||
user := e.UserIdentifier
|
||||
user := e.RemoteUser
|
||||
if root {
|
||||
user = RootUser
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ func (v *VSCode) Setup(
|
|||
osInfoScript := common.GetOSInfoScript()
|
||||
sshServerScript, err := template.GenerateScriptFromTemplate(
|
||||
templateSetupSSHServer, &template.SetupSSHServerPayload{
|
||||
Username: exec.UserIdentifier,
|
||||
Username: exec.RemoteUser,
|
||||
AccessType: exec.AccessType,
|
||||
OSInfoScript: osInfoScript,
|
||||
})
|
||||
|
|
|
@ -220,7 +220,7 @@ func generateIDEURL(
|
|||
Host: "", // Empty since we include the host and port in the path
|
||||
Path: fmt.Sprintf(
|
||||
"ssh-remote+%s@%s:%s",
|
||||
gitspaceConfig.GitspaceUser.Identifier,
|
||||
startResponse.RemoteUser,
|
||||
host,
|
||||
filepath.Join(forwardedPort, relativeRepoPath),
|
||||
),
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -2,36 +2,36 @@
|
|||
- name: --add-host
|
||||
short_hand:
|
||||
supported: true
|
||||
blocked_values: {}
|
||||
allowed_values: {}
|
||||
blocked_values: { }
|
||||
allowed_values: { }
|
||||
allow_multiple_occurrences: true
|
||||
|
||||
- name: --annotation
|
||||
short_hand:
|
||||
supported: true
|
||||
blocked_values: {}
|
||||
allowed_values: {}
|
||||
blocked_values: { }
|
||||
allowed_values: { }
|
||||
allow_multiple_occurrences: true
|
||||
|
||||
- name: --attach
|
||||
short_hand: -a
|
||||
supported: false
|
||||
blocked_values: {}
|
||||
allowed_values: {}
|
||||
blocked_values: { }
|
||||
allowed_values: { }
|
||||
allow_multiple_occurrences: true
|
||||
|
||||
- name: --blkio-weight
|
||||
short_hand:
|
||||
supported: true
|
||||
blocked_values: {}
|
||||
allowed_values: {}
|
||||
blocked_values: { }
|
||||
allowed_values: { }
|
||||
allow_multiple_occurrences: false
|
||||
|
||||
- name: --blkio-weight-device
|
||||
short_hand:
|
||||
supported: false
|
||||
blocked_values: {}
|
||||
allowed_values: {}
|
||||
blocked_values: { }
|
||||
allowed_values: { }
|
||||
allow_multiple_occurrences: true
|
||||
|
||||
- name: --cap-add
|
||||
|
@ -394,15 +394,16 @@
|
|||
- name: --label
|
||||
short_hand: -l
|
||||
supported: true
|
||||
blocked_values: { }
|
||||
blocked_values:
|
||||
^gitspace\.remote\.user=: true
|
||||
allowed_values: { }
|
||||
allow_multiple_occurrences: true
|
||||
|
||||
- name: --label-file
|
||||
short_hand:
|
||||
supported: false
|
||||
blocked_values: {}
|
||||
allowed_values: {}
|
||||
blocked_values: { }
|
||||
allowed_values: { }
|
||||
allow_multiple_occurrences: true
|
||||
|
||||
- name: --link
|
||||
|
@ -486,8 +487,8 @@
|
|||
short_hand:
|
||||
supported: true
|
||||
blocked_values:
|
||||
host: true
|
||||
none: true
|
||||
^host$: true
|
||||
^none$: true
|
||||
allowed_values: { }
|
||||
allow_multiple_occurrences: false
|
||||
|
||||
|
|
|
@ -16,40 +16,18 @@ package runarg
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
var _ Provider = (*StaticProvider)(nil)
|
||||
|
||||
//go:embed runArgs.yaml
|
||||
var supportedRunArgsRaw []byte
|
||||
|
||||
type StaticProvider struct {
|
||||
supportedRunArgsMap map[types.RunArg]types.RunArgDefinition
|
||||
}
|
||||
|
||||
func NewStaticProvider() (Provider, 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 &StaticProvider{supportedRunArgsMap: argsMap}, nil
|
||||
func NewStaticProvider(resolver *Resolver) (Provider, error) {
|
||||
return &StaticProvider{supportedRunArgsMap: resolver.ResolveSupportedRunArgs()}, nil
|
||||
}
|
||||
|
||||
// ProvideSupportedRunArgs provides a static map of supported run args.
|
||||
|
|
|
@ -20,8 +20,12 @@ import (
|
|||
|
||||
var WireSet = wire.NewSet(
|
||||
ProvideStaticProvider,
|
||||
ProvideResolver,
|
||||
)
|
||||
|
||||
func ProvideStaticProvider() (Provider, error) {
|
||||
return NewStaticProvider()
|
||||
func ProvideStaticProvider(resolver *Resolver) (Provider, error) {
|
||||
return NewStaticProvider(resolver)
|
||||
}
|
||||
func ProvideResolver() (*Resolver, error) {
|
||||
return NewResolver()
|
||||
}
|
||||
|
|
|
@ -63,11 +63,10 @@ type SetupVSCodeWebPayload struct {
|
|||
}
|
||||
|
||||
type SetupUserPayload struct {
|
||||
Username string
|
||||
AccessKey string
|
||||
AccessType enum.GitspaceAccessType
|
||||
HomeDir string
|
||||
OSInfoScript string
|
||||
Username string
|
||||
AccessKey string
|
||||
AccessType enum.GitspaceAccessType
|
||||
HomeDir string
|
||||
}
|
||||
|
||||
type SetupSSHServerPayload struct {
|
||||
|
|
|
@ -1,65 +1,41 @@
|
|||
#!/bin/sh
|
||||
|
||||
username={{ .Username }}
|
||||
username="{{ .Username }}"
|
||||
accessKey="{{ .AccessKey }}"
|
||||
homeDir={{ .HomeDir }}
|
||||
homeDir="{{ .HomeDir }}"
|
||||
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."
|
||||
# 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
|
||||
echo "Failed to create directory $homeDir."
|
||||
exit 1
|
||||
fi
|
||||
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
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create user $username."
|
||||
exit 1
|
||||
fi
|
||||
echo "Directory $homeDir already exists."
|
||||
fi
|
||||
|
||||
# Changing ownership of everything inside user home to the newly created user
|
||||
chown -R $username:$username $homeDir
|
||||
echo "Changing ownership of dir $homeDir to $username."
|
||||
chmod 755 $homeDir
|
||||
# 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
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Ensuring proper permissions for $homeDir..."
|
||||
chmod 755 "$homeDir"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to set permissions for $homeDir."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Directory setup for $username is complete."
|
||||
|
||||
if [ "ssh_key" = "$accessType" ] ; then
|
||||
echo "Add ssh key in $homeDir/.ssh/authorized_keys"
|
||||
|
@ -68,7 +44,6 @@ if [ "ssh_key" = "$accessType" ] ; then
|
|||
echo $accessKey > $homeDir/.ssh/authorized_keys
|
||||
chmod 600 $homeDir/.ssh/authorized_keys
|
||||
chown -R $username:$username $homeDir/.ssh
|
||||
echo "$username:$username" | chpasswd
|
||||
elif [ "user_credentials" = "$accessType" ] ; then
|
||||
echo "$username:$accessKey" | chpasswd
|
||||
else
|
||||
|
|
|
@ -47,8 +47,8 @@ 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
|
||||
# If AllowUsers exists, overwrite all existing users with new user
|
||||
sed -i "s/^AllowUsers.*/AllowUsers $username/" $config_file
|
||||
else
|
||||
# Otherwise, add a new AllowUsers line
|
||||
echo "AllowUsers $username" >> $config_file
|
||||
|
@ -67,10 +67,15 @@ echo "AuthorizedKeysFile .ssh/authorized_keys" >> $config_file
|
|||
echo "PubkeyAuthentication yes" >> $config_file
|
||||
else
|
||||
# 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
|
||||
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
|
||||
|
||||
mkdir -p /var/run/sshd
|
|
@ -40,28 +40,25 @@ func (u *ServiceImpl) Manage(
|
|||
exec *devcontainer.Exec,
|
||||
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
||||
) error {
|
||||
osInfoScript := common.GetOSInfoScript()
|
||||
script, err := template.GenerateScriptFromTemplate(
|
||||
templateManagerUser, &template.SetupUserPayload{
|
||||
Username: exec.UserIdentifier,
|
||||
AccessKey: exec.AccessKey,
|
||||
AccessType: exec.AccessType,
|
||||
HomeDir: exec.HomeDir,
|
||||
OSInfoScript: osInfoScript,
|
||||
Username: exec.RemoteUser,
|
||||
AccessKey: exec.AccessKey,
|
||||
AccessType: exec.AccessType,
|
||||
HomeDir: exec.HomeDir,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to generate scipt to manager user from template %s: %w", templateManagerUser, err)
|
||||
}
|
||||
|
||||
gitspaceLogger.Info("Setting up user inside container")
|
||||
gitspaceLogger.Info("Managing user output...")
|
||||
gitspaceLogger.Info("Configuring user directory and credentials inside container")
|
||||
err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, script, true, gitspaceLogger)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ const VSCodeProxyURI = "VSCODE_PROXY_URI"
|
|||
type GitspaceLogger interface {
|
||||
Info(msg string)
|
||||
Debug(msg string)
|
||||
Warn(msg string)
|
||||
Error(msg string, err error)
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,10 @@ func (z *ZerologAdapter) Debug(msg string) {
|
|||
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) {
|
||||
z.logger.Err(err).Msg("ERROR: " + msg)
|
||||
}
|
||||
|
|
|
@ -319,7 +319,11 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
statefulLogger := logutil.ProvideStatefulLogger(logStream)
|
||||
gitService := git2.ProvideGitServiceImpl()
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -27,7 +27,9 @@ type DevcontainerConfig struct {
|
|||
ForwardPorts []json.Number `json:"forwardPorts,omitempty"` //nolint:tagliatelle
|
||||
ContainerEnv map[string]string `json:"containerEnv,omitempty"` //nolint:tagliatelle
|
||||
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.
|
||||
|
|
Loading…
Reference in New Issue