mirror of
https://code.forgejo.org/forgejo/runner.git
synced 2025-09-15 18:57:01 +00:00
$GITHUB_PATH support (#566)
* Regression in the .golangci.yml file * This looks like an even better fix to #451 The previous solution only prevented the `starting container process caused "exec: \"bash\"` error when someone added an "extra" path in the workflow using `::add-path` * Add support for >> $GITHUB_PATH * The newRunCommand has too high cyclomatic complexity * Add "linux/arm64" to new test * The cyclop linter was complaining so I extracted some funcs * Close some readers * Fix typo * fix: add missing composite function * Fix regress from merging * Keep the error messages as is * consolidate with master * Close the tar reader on defer * New way to get ContainerWorkdir * Remove arch from runner test * Separate the UpdateFromEnv and UpdateFromPath Co-authored-by: hackercat <me@hackerc.at>
This commit is contained in:
parent
dd198c2dd3
commit
707067856b
7 changed files with 233 additions and 134 deletions
|
@ -112,6 +112,7 @@ func (sc *StepContext) mergeEnv() map[string]string {
|
|||
env = mergeMaps(rc.GetEnv(), step.GetEnv())
|
||||
}
|
||||
|
||||
env["PATH"] = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`
|
||||
if (rc.ExtraPath != nil) && (len(rc.ExtraPath) > 0) {
|
||||
env["PATH"] = strings.Join(rc.ExtraPath, `:`)
|
||||
}
|
||||
|
@ -134,6 +135,10 @@ func (sc *StepContext) setupEnv(ctx context.Context) (ExpressionEvaluator, error
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = rc.JobContainer.UpdateFromPath(&sc.Env)(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
evaluator := sc.NewExpressionEvaluator()
|
||||
sc.interpolateEnv(evaluator)
|
||||
|
@ -390,14 +395,7 @@ func (sc *StepContext) runAction(actionDir string, actionPath string) common.Exe
|
|||
return func(ctx context.Context) error {
|
||||
action := sc.Action
|
||||
log.Debugf("About to run action %v", action)
|
||||
for inputID, input := range action.Inputs {
|
||||
envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_")
|
||||
envKey = fmt.Sprintf("INPUT_%s", envKey)
|
||||
if _, ok := sc.Env[envKey]; !ok {
|
||||
sc.Env[envKey] = rc.ExprEval.Interpolate(input.Default)
|
||||
}
|
||||
}
|
||||
|
||||
sc.populateEnvsFromInput(action, rc)
|
||||
actionLocation := ""
|
||||
if actionPath != "" {
|
||||
actionLocation = path.Join(actionDir, actionPath)
|
||||
|
@ -434,133 +432,9 @@ func (sc *StepContext) runAction(actionDir string, actionPath string) common.Exe
|
|||
log.Debugf("executing remote job container: %s", containerArgs)
|
||||
return rc.execJobContainer(containerArgs, sc.Env)(ctx)
|
||||
case model.ActionRunsUsingDocker:
|
||||
var prepImage common.Executor
|
||||
var image string
|
||||
if strings.HasPrefix(action.Runs.Image, "docker://") {
|
||||
image = strings.TrimPrefix(action.Runs.Image, "docker://")
|
||||
} else {
|
||||
image = fmt.Sprintf("%s:%s", regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-"), "latest")
|
||||
image = fmt.Sprintf("act-%s", strings.TrimLeft(image, "-"))
|
||||
image = strings.ToLower(image)
|
||||
contextDir := filepath.Join(actionDir, actionPath, action.Runs.Main)
|
||||
|
||||
anyArchExists, err := container.ImageExistsLocally(ctx, image, "any")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
correctArchExists, err := container.ImageExistsLocally(ctx, image, rc.Config.ContainerArchitecture)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if anyArchExists && !correctArchExists {
|
||||
wasRemoved, err := container.RemoveImage(ctx, image, true, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !wasRemoved {
|
||||
return fmt.Errorf("failed to remove image '%s'", image)
|
||||
}
|
||||
}
|
||||
|
||||
if !correctArchExists {
|
||||
log.Debugf("image '%s' for architecture '%s' will be built from context '%s", image, rc.Config.ContainerArchitecture, contextDir)
|
||||
prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||
ContextDir: contextDir,
|
||||
ImageTag: image,
|
||||
Platform: rc.Config.ContainerArchitecture,
|
||||
})
|
||||
} else {
|
||||
log.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture)
|
||||
}
|
||||
}
|
||||
|
||||
cmd, err := shellquote.Split(step.With["args"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cmd) == 0 {
|
||||
cmd = action.Runs.Args
|
||||
}
|
||||
entrypoint := strings.Fields(step.With["entrypoint"])
|
||||
if len(entrypoint) == 0 {
|
||||
entrypoint = action.Runs.Entrypoint
|
||||
}
|
||||
stepContainer := sc.newStepContainer(ctx, image, cmd, entrypoint)
|
||||
return common.NewPipelineExecutor(
|
||||
prepImage,
|
||||
stepContainer.Pull(rc.Config.ForcePull),
|
||||
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers),
|
||||
stepContainer.Create(),
|
||||
stepContainer.Start(true),
|
||||
).Finally(
|
||||
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers),
|
||||
)(ctx)
|
||||
return sc.execAsDocker(ctx, action, actionName, actionDir, actionPath, rc, step)
|
||||
case model.ActionRunsUsingComposite:
|
||||
err := maybeCopyToActionDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for outputName, output := range action.Outputs {
|
||||
re := regexp.MustCompile(`\${{ steps\.([a-zA-Z_][a-zA-Z0-9_-]+)\.outputs\.([a-zA-Z_][a-zA-Z0-9_-]+) }}`)
|
||||
matches := re.FindStringSubmatch(output.Value)
|
||||
if len(matches) > 2 {
|
||||
if sc.RunContext.OutputMappings == nil {
|
||||
sc.RunContext.OutputMappings = make(map[MappableOutput]MappableOutput)
|
||||
}
|
||||
|
||||
k := MappableOutput{StepID: matches[1], OutputName: matches[2]}
|
||||
v := MappableOutput{StepID: step.ID, OutputName: outputName}
|
||||
sc.RunContext.OutputMappings[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
var executors []common.Executor
|
||||
stepID := 0
|
||||
for _, compositeStep := range action.Runs.Steps {
|
||||
stepClone := compositeStep
|
||||
// Take a copy of the run context structure (rc is a pointer)
|
||||
// Then take the address of the new structure
|
||||
rcCloneStr := *rc
|
||||
rcClone := &rcCloneStr
|
||||
if stepClone.ID == "" {
|
||||
stepClone.ID = fmt.Sprintf("composite-%d", stepID)
|
||||
stepID++
|
||||
}
|
||||
rcClone.CurrentStep = stepClone.ID
|
||||
|
||||
if err := compositeStep.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup the outputs for the composite steps
|
||||
if _, ok := rcClone.StepResults[stepClone.ID]; !ok {
|
||||
rcClone.StepResults[stepClone.ID] = &stepResult{
|
||||
Success: true,
|
||||
Outputs: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
stepClone.Run = strings.ReplaceAll(stepClone.Run, "${{ github.action_path }}", filepath.Join(containerActionDir, actionName))
|
||||
|
||||
stepContext := StepContext{
|
||||
RunContext: rcClone,
|
||||
Step: &stepClone,
|
||||
Env: mergeMaps(sc.Env, stepClone.Env),
|
||||
}
|
||||
|
||||
// Interpolate the outer inputs into the composite step with items
|
||||
exprEval := sc.NewExpressionEvaluator()
|
||||
for k, v := range stepContext.Step.With {
|
||||
if strings.Contains(v, "inputs") {
|
||||
stepContext.Step.With[k] = exprEval.Interpolate(v)
|
||||
}
|
||||
}
|
||||
|
||||
executors = append(executors, stepContext.Executor())
|
||||
}
|
||||
return common.NewPipelineExecutor(executors...)(ctx)
|
||||
return sc.execAsComposite(ctx, step, actionDir, rc, containerActionDir, actionName, actionPath, action, maybeCopyToActionDir)
|
||||
default:
|
||||
return fmt.Errorf(fmt.Sprintf("The runs.using key must be one of: %v, got %s", []string{
|
||||
model.ActionRunsUsingDocker,
|
||||
|
@ -571,6 +445,149 @@ func (sc *StepContext) runAction(actionDir string, actionPath string) common.Exe
|
|||
}
|
||||
}
|
||||
|
||||
func (sc *StepContext) execAsDocker(ctx context.Context, action *model.Action, actionName string, actionDir string, actionPath string, rc *RunContext, step *model.Step) error {
|
||||
var prepImage common.Executor
|
||||
var image string
|
||||
if strings.HasPrefix(action.Runs.Image, "docker://") {
|
||||
image = strings.TrimPrefix(action.Runs.Image, "docker://")
|
||||
} else {
|
||||
image = fmt.Sprintf("%s:%s", regexp.MustCompile("[^a-zA-Z0-9]").ReplaceAllString(actionName, "-"), "latest")
|
||||
image = fmt.Sprintf("act-%s", strings.TrimLeft(image, "-"))
|
||||
image = strings.ToLower(image)
|
||||
contextDir := filepath.Join(actionDir, actionPath, action.Runs.Main)
|
||||
|
||||
anyArchExists, err := container.ImageExistsLocally(ctx, image, "any")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
correctArchExists, err := container.ImageExistsLocally(ctx, image, rc.Config.ContainerArchitecture)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if anyArchExists && !correctArchExists {
|
||||
wasRemoved, err := container.RemoveImage(ctx, image, true, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !wasRemoved {
|
||||
return fmt.Errorf("failed to remove image '%s'", image)
|
||||
}
|
||||
}
|
||||
|
||||
if !correctArchExists {
|
||||
log.Debugf("image '%s' for architecture '%s' will be built from context '%s", image, rc.Config.ContainerArchitecture, contextDir)
|
||||
prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||
ContextDir: contextDir,
|
||||
ImageTag: image,
|
||||
Platform: rc.Config.ContainerArchitecture,
|
||||
})
|
||||
} else {
|
||||
log.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture)
|
||||
}
|
||||
}
|
||||
|
||||
cmd, err := shellquote.Split(step.With["args"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cmd) == 0 {
|
||||
cmd = action.Runs.Args
|
||||
}
|
||||
entrypoint := strings.Fields(step.With["entrypoint"])
|
||||
if len(entrypoint) == 0 {
|
||||
entrypoint = action.Runs.Entrypoint
|
||||
}
|
||||
stepContainer := sc.newStepContainer(ctx, image, cmd, entrypoint)
|
||||
return common.NewPipelineExecutor(
|
||||
prepImage,
|
||||
stepContainer.Pull(rc.Config.ForcePull),
|
||||
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers),
|
||||
stepContainer.Create(),
|
||||
stepContainer.Start(true),
|
||||
).Finally(
|
||||
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers),
|
||||
)(ctx)
|
||||
}
|
||||
|
||||
func (sc *StepContext) execAsComposite(ctx context.Context, step *model.Step, _ string, rc *RunContext, containerActionDir string, actionName string, _ string, action *model.Action, maybeCopyToActionDir func() error) error {
|
||||
err := maybeCopyToActionDir()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for outputName, output := range action.Outputs {
|
||||
re := regexp.MustCompile(`\${{ steps\.([a-zA-Z_][a-zA-Z0-9_-]+)\.outputs\.([a-zA-Z_][a-zA-Z0-9_-]+) }}`)
|
||||
matches := re.FindStringSubmatch(output.Value)
|
||||
if len(matches) > 2 {
|
||||
if sc.RunContext.OutputMappings == nil {
|
||||
sc.RunContext.OutputMappings = make(map[MappableOutput]MappableOutput)
|
||||
}
|
||||
|
||||
k := MappableOutput{StepID: matches[1], OutputName: matches[2]}
|
||||
v := MappableOutput{StepID: step.ID, OutputName: outputName}
|
||||
sc.RunContext.OutputMappings[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
executors := make([]common.Executor, 0, len(action.Runs.Steps))
|
||||
stepID := 0
|
||||
for _, compositeStep := range action.Runs.Steps {
|
||||
stepClone := compositeStep
|
||||
// Take a copy of the run context structure (rc is a pointer)
|
||||
// Then take the address of the new structure
|
||||
rcCloneStr := *rc
|
||||
rcClone := &rcCloneStr
|
||||
if stepClone.ID == "" {
|
||||
stepClone.ID = fmt.Sprintf("composite-%d", stepID)
|
||||
stepID++
|
||||
}
|
||||
rcClone.CurrentStep = stepClone.ID
|
||||
|
||||
if err := compositeStep.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup the outputs for the composite steps
|
||||
if _, ok := rcClone.StepResults[stepClone.ID]; !ok {
|
||||
rcClone.StepResults[stepClone.ID] = &stepResult{
|
||||
Success: true,
|
||||
Outputs: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
stepClone.Run = strings.ReplaceAll(stepClone.Run, "${{ github.action_path }}", filepath.Join(containerActionDir, actionName))
|
||||
|
||||
stepContext := StepContext{
|
||||
RunContext: rcClone,
|
||||
Step: &stepClone,
|
||||
Env: mergeMaps(sc.Env, stepClone.Env),
|
||||
}
|
||||
|
||||
// Interpolate the outer inputs into the composite step with items
|
||||
exprEval := sc.NewExpressionEvaluator()
|
||||
for k, v := range stepContext.Step.With {
|
||||
if strings.Contains(v, "inputs") {
|
||||
stepContext.Step.With[k] = exprEval.Interpolate(v)
|
||||
}
|
||||
}
|
||||
|
||||
executors = append(executors, stepContext.Executor())
|
||||
}
|
||||
return common.NewPipelineExecutor(executors...)(ctx)
|
||||
}
|
||||
|
||||
func (sc *StepContext) populateEnvsFromInput(action *model.Action, rc *RunContext) {
|
||||
for inputID, input := range action.Inputs {
|
||||
envKey := regexp.MustCompile("[^A-Z0-9-]").ReplaceAllString(strings.ToUpper(inputID), "_")
|
||||
envKey = fmt.Sprintf("INPUT_%s", envKey)
|
||||
if _, ok := sc.Env[envKey]; !ok {
|
||||
sc.Env[envKey] = rc.ExprEval.Interpolate(input.Default)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type remoteAction struct {
|
||||
URL string
|
||||
Org string
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue