drone/app/gitspace/orchestrator/container/devcontainer_config_utils.go

209 lines
6.2 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package container
import (
"context"
"fmt"
"regexp"
"strings"
"github.com/harness/gitness/app/gitspace/orchestrator/ide"
"github.com/harness/gitness/app/gitspace/orchestrator/runarg"
gitspaceTypes "github.com/harness/gitness/app/gitspace/types"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
func ExtractRunArgs(
ctx context.Context,
spaceID int64,
runArgProvider runarg.Provider,
runArgsRaw []string,
) (map[types.RunArg]*types.RunArgValue, error) {
supportedRunArgsMap, err := runArgProvider.ProvideSupportedRunArgs(ctx, spaceID)
if err != nil {
return nil, err
}
var runArgsMap = make(map[types.RunArg]*types.RunArgValue)
argLoopCounter := 0
for argLoopCounter < len(runArgsRaw) {
currentArg := runArgsRaw[argLoopCounter]
if currentArg == "" || !isArg(currentArg) {
argLoopCounter++
continue
}
argParts := strings.SplitN(currentArg, "=", 2)
argKey := argParts[0]
currentRunArgDefinition, isSupportedArg := supportedRunArgsMap[types.RunArg(argKey)]
if !isSupportedArg {
argLoopCounter++
continue
}
updatedArgLoopCounter, allowedValues, isAnyValueBlocked := getValues(runArgsRaw, argParts, argLoopCounter,
currentRunArgDefinition)
argLoopCounter = updatedArgLoopCounter
if isAnyValueBlocked && len(allowedValues) == 0 {
continue
}
currentRunArgValue := types.RunArgValue{
Name: currentRunArgDefinition.Name,
Values: allowedValues,
}
existingRunArgValue, isAlreadyPresent := runArgsMap[currentRunArgDefinition.Name]
if isAlreadyPresent && currentRunArgDefinition.AllowMultipleOccurences {
existingRunArgValue.Values = append(existingRunArgValue.Values, currentRunArgValue.Values...)
} else {
runArgsMap[currentRunArgDefinition.Name] = &currentRunArgValue
}
}
return runArgsMap, nil
}
func getValues(
runArgs []string,
argParts []string,
argLoopCounter int,
currentRunArgDefinition types.RunArgDefinition,
) (int, []string, bool) {
values := make([]string, 0)
if len(argParts) > 1 {
values = append(values, strings.TrimSpace(argParts[1]))
argLoopCounter++
} else {
var valueLoopCounter = argLoopCounter + 1
for valueLoopCounter < len(runArgs) {
currentValue := runArgs[valueLoopCounter]
if isArg(currentValue) {
break
}
values = append(values, strings.TrimSpace(currentValue))
valueLoopCounter++
}
argLoopCounter = valueLoopCounter
}
allowedValues, isAnyValueBlocked := filterAllowedValues(values, currentRunArgDefinition)
return argLoopCounter, allowedValues, isAnyValueBlocked
}
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:
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:
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)
}
default:
allowedValues = append(allowedValues, v)
}
}
return allowedValues, isAnyValueBlocked
}
func isArg(str string) bool {
return strings.HasPrefix(str, "--") || strings.HasPrefix(str, "-")
}
func ExtractEnv(devcontainerConfig types.DevcontainerConfig, runArgsMap map[types.RunArg]*types.RunArgValue) []string {
var env []string
for key, value := range devcontainerConfig.ContainerEnv {
env = append(env, fmt.Sprintf("%s=%s", key, value))
}
envFromRunArgs := getEnv(runArgsMap)
env = append(env, envFromRunArgs...)
return env
}
func ExtractForwardPorts(devcontainerConfig types.DevcontainerConfig) []int {
var ports []int
for _, strPort := range devcontainerConfig.ForwardPorts {
portAsInt, err := strPort.Int64() // Using Atoi to convert string to int
if err != nil {
log.Warn().Msgf("Error converting port string '%s' to int: %v", strPort, err)
continue // Skip the invalid port
}
ports = append(ports, int(portAsInt))
}
return ports
}
func ExtractLifecycleCommands(actionType PostAction, devcontainerConfig types.DevcontainerConfig) []string {
switch actionType {
case PostCreateAction:
return devcontainerConfig.PostCreateCommand.ToCommandArray()
case PostStartAction:
return devcontainerConfig.PostStartCommand.ToCommandArray()
default:
return []string{} // Return empty string if actionType is not recognized
}
}
func ExtractIDECustomizations(
ideService ide.IDE,
devcontainerConfig types.DevcontainerConfig,
) map[string]interface{} {
var args = make(map[string]interface{})
if ideService.Type() == enum.IDETypeVSCodeWeb || ideService.Type() == enum.IDETypeVSCode {
if devcontainerConfig.Customizations.ExtractVSCodeSpec() != nil {
args[gitspaceTypes.VSCodeCustomizationArg] = *devcontainerConfig.Customizations.ExtractVSCodeSpec()
}
}
return args
}