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
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
}
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
}

View File

@ -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

View File

@ -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)

View File

@ -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(

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 {
arg, ok := runArgsMap[types.RunArgLabel]
labelsMap := make(map[string]string)
arg, ok := runArgsMap[types.RunArgLabel]
if ok {
labels := arg.Values
for _, v := range labels {

View File

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

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
})

View File

@ -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),
),

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

@ -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

View File

@ -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.

View File

@ -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()
}

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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.