mirror of
https://code.forgejo.org/forgejo/runner.git
synced 2025-09-15 18:57:01 +00:00
fix: image credentials for services must not override container image credentials (#181)
- do not override username and password when looping over services - split prepareJobContainer out of startJobContainer - split getNetworkName out as it is used by both - add unit tests for prepareJobContainer - make containre.NewContainer mockable - add MockVariable helper Closes forgejo/runner#575 --- Note to reviewers: do not show whitespace change, the refactor will show in a minimal way. When the fix is reverted the tests fail as follows: ``` Diff: --- Expected +++ Actual @@ -81,4 +81,4 @@ Image: (string) (len=10) "some:image", - Username: (string) (len=17) "containerusername", - Password: (string) (len=17) "containerpassword", + Username: (string) (len=16) "service2username", + Password: (string) (len=16) "service2password", Entrypoint: ([]string) (len=3) { Test: TestStartJobContainer/Overlapping ``` Reviewed-on: https://code.forgejo.org/forgejo/act/pulls/181 Reviewed-by: Michael Kriese <michael.kriese@gmx.de> Co-authored-by: Earl Warren <contact@earl-warren.org> Co-committed-by: Earl Warren <contact@earl-warren.org>
This commit is contained in:
parent
6620cc1d18
commit
c2f91f63df
4 changed files with 346 additions and 151 deletions
|
@ -43,7 +43,7 @@ import (
|
|||
)
|
||||
|
||||
// NewContainer creates a reference to a container
|
||||
func NewContainer(input *NewContainerInput) ExecutionsEnvironment {
|
||||
var NewContainer = func(input *NewContainerInput) ExecutionsEnvironment {
|
||||
cr := new(containerReference)
|
||||
cr.input = input
|
||||
cr.toolCache = input.ToolCache
|
||||
|
|
|
@ -391,8 +391,18 @@ func (rc *RunContext) startHostEnvironment() common.Executor {
|
|||
}
|
||||
}
|
||||
|
||||
func (rc *RunContext) startJobContainer() common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
func (rc *RunContext) getNetworkName(_ context.Context) (networkName string, createAndDeleteNetwork bool) {
|
||||
// specify the network to which the container will connect when `docker create` stage. (like execute command line: docker create --network <networkName> <image>)
|
||||
networkName = string(rc.Config.ContainerNetworkMode)
|
||||
if networkName == "" {
|
||||
// if networkName is empty string, will create a new network for the containers.
|
||||
// and it will be removed after at last.
|
||||
networkName, createAndDeleteNetwork = rc.networkName()
|
||||
}
|
||||
return networkName, createAndDeleteNetwork
|
||||
}
|
||||
|
||||
func (rc *RunContext) prepareJobContainer(ctx context.Context) error {
|
||||
logger := common.Logger(ctx)
|
||||
image := rc.platformImage(ctx)
|
||||
rawLogger := logger.WithField("raw_output", true)
|
||||
|
@ -427,15 +437,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||
ext := container.LinuxContainerEnvironmentExtensions{}
|
||||
binds, mounts := rc.GetBindsAndMounts()
|
||||
|
||||
// specify the network to which the container will connect when `docker create` stage. (like execute command line: docker create --network <networkName> <image>)
|
||||
networkName := string(rc.Config.ContainerNetworkMode)
|
||||
var createAndDeleteNetwork bool
|
||||
if networkName == "" {
|
||||
// if networkName is empty string, will create a new network for the containers.
|
||||
// and it will be removed after at last.
|
||||
networkName, createAndDeleteNetwork = rc.networkName()
|
||||
}
|
||||
|
||||
networkName, createAndDeleteNetwork := rc.getNetworkName(ctx)
|
||||
// add service containers
|
||||
for serviceID, spec := range rc.Run.Job().Services {
|
||||
// interpolate env
|
||||
|
@ -452,7 +454,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||
interpolatedCmd = append(interpolatedCmd, rc.ExprEval.Interpolate(ctx, v))
|
||||
}
|
||||
|
||||
username, password, err = rc.handleServiceCredentials(ctx, spec.Credentials)
|
||||
username, password, err := rc.handleServiceCredentials(ctx, spec.Credentials)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to handle service %s credentials: %w", serviceID, err)
|
||||
}
|
||||
|
@ -562,6 +564,15 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||
return errors.New("Failed to create job container")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *RunContext) startJobContainer() common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
if err := rc.prepareJobContainer(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
networkName, _ := rc.getNetworkName(ctx)
|
||||
networkConfig := network.CreateOptions{
|
||||
Driver: "bridge",
|
||||
Scope: "local",
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
package runner
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nektos/act/pkg/container"
|
||||
"github.com/nektos/act/pkg/exprparser"
|
||||
"github.com/nektos/act/pkg/model"
|
||||
"github.com/nektos/act/pkg/testutils"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
log "github.com/sirupsen/logrus"
|
||||
assert "github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
@ -704,3 +710,171 @@ func Test_createSimpleContainerName(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareJobContainer(t *testing.T) {
|
||||
yaml := `
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
job:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: some:image
|
||||
credentials:
|
||||
username: containerusername
|
||||
password: containerpassword
|
||||
services:
|
||||
service1:
|
||||
image: service1:image
|
||||
credentials:
|
||||
username: service1username
|
||||
password: service1password
|
||||
service2:
|
||||
image: service2:image
|
||||
credentials:
|
||||
username: service2username
|
||||
password: service2password
|
||||
steps:
|
||||
- run: echo ok
|
||||
`
|
||||
workflow, err := model.ReadWorkflow(strings.NewReader(yaml))
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
step actionStep
|
||||
inputs []container.NewContainerInput
|
||||
}{
|
||||
{
|
||||
name: "Overlapping",
|
||||
step: &stepActionRemote{
|
||||
Step: &model.Step{
|
||||
Uses: "org/repo/path@ref",
|
||||
},
|
||||
RunContext: &RunContext{
|
||||
Config: &Config{
|
||||
Workdir: "/my/workdir",
|
||||
},
|
||||
Run: &model.Run{
|
||||
JobID: "job",
|
||||
Workflow: workflow,
|
||||
},
|
||||
},
|
||||
env: map[string]string{},
|
||||
},
|
||||
inputs: []container.NewContainerInput{
|
||||
{
|
||||
Name: "WORKFLOW-JOB-service1-24d1b6963554cd6e1a2f9bfcd21b822bf5b42547db24667196ac45f89072fdd9",
|
||||
Image: "service1:image",
|
||||
Username: "service1username",
|
||||
Password: "service1password",
|
||||
Entrypoint: nil,
|
||||
Cmd: []string{},
|
||||
WorkingDir: "",
|
||||
Env: []string{},
|
||||
ToolCache: "/opt/hostedtoolcache",
|
||||
Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"},
|
||||
Mounts: map[string]string{},
|
||||
NetworkMode: "WORKFLOW_JOB-job-network",
|
||||
Privileged: false,
|
||||
UsernsMode: "",
|
||||
Platform: "",
|
||||
NetworkAliases: []string{"service1"},
|
||||
ExposedPorts: nat.PortSet{},
|
||||
PortBindings: nat.PortMap{},
|
||||
ConfigOptions: "",
|
||||
JobOptions: "",
|
||||
AutoRemove: false,
|
||||
ValidVolumes: []string{
|
||||
"WORKFLOW_JOB",
|
||||
"WORKFLOW_JOB-env",
|
||||
"/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "WORKFLOW-JOB-service2-7137cecabbdb942ae7bbfc8953de8f2a68e8dc9c92ad98cd6d095481b216f979",
|
||||
Image: "service2:image",
|
||||
Username: "service2username",
|
||||
Password: "service2password",
|
||||
Entrypoint: nil,
|
||||
Cmd: []string{},
|
||||
WorkingDir: "",
|
||||
Env: []string{},
|
||||
ToolCache: "/opt/hostedtoolcache",
|
||||
Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"},
|
||||
Mounts: map[string]string{},
|
||||
NetworkMode: "WORKFLOW_JOB-job-network",
|
||||
Privileged: false,
|
||||
UsernsMode: "",
|
||||
Platform: "",
|
||||
NetworkAliases: []string{"service2"},
|
||||
ExposedPorts: nat.PortSet{},
|
||||
PortBindings: nat.PortMap{},
|
||||
ConfigOptions: "",
|
||||
JobOptions: "",
|
||||
AutoRemove: false,
|
||||
ValidVolumes: []string{
|
||||
"WORKFLOW_JOB",
|
||||
"WORKFLOW_JOB-env",
|
||||
"/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "WORKFLOW_JOB",
|
||||
Image: "some:image",
|
||||
Username: "containerusername",
|
||||
Password: "containerpassword",
|
||||
Entrypoint: []string{"tail", "-f", "/dev/null"},
|
||||
Cmd: nil,
|
||||
WorkingDir: "/my/workdir",
|
||||
Env: []string{},
|
||||
ToolCache: "/opt/hostedtoolcache",
|
||||
Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"},
|
||||
Mounts: map[string]string{
|
||||
"WORKFLOW_JOB": "/my/workdir",
|
||||
"WORKFLOW_JOB-env": "/var/run/act",
|
||||
},
|
||||
NetworkMode: "WORKFLOW_JOB-job-network",
|
||||
Privileged: false,
|
||||
UsernsMode: "",
|
||||
Platform: "",
|
||||
NetworkAliases: []string{""},
|
||||
ExposedPorts: nil,
|
||||
PortBindings: nil,
|
||||
ConfigOptions: "",
|
||||
JobOptions: "",
|
||||
AutoRemove: false,
|
||||
ValidVolumes: []string{
|
||||
"WORKFLOW_JOB",
|
||||
"WORKFLOW_JOB-env",
|
||||
"/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
containerInputs := make([]container.NewContainerInput, 0, 5)
|
||||
newContainer := container.NewContainer
|
||||
defer testutils.MockVariable(&container.NewContainer, func(input *container.NewContainerInput) container.ExecutionsEnvironment {
|
||||
c := *input
|
||||
c.Stdout = nil
|
||||
c.Stderr = nil
|
||||
c.Env = []string{}
|
||||
containerInputs = append(containerInputs, c)
|
||||
return newContainer(input)
|
||||
})()
|
||||
|
||||
ctx := context.Background()
|
||||
rc := testCase.step.getRunContext()
|
||||
rc.ExprEval = rc.NewExpressionEvaluator(ctx)
|
||||
|
||||
require.NoError(t, rc.prepareJobContainer(ctx))
|
||||
slices.SortFunc(containerInputs, func(a, b container.NewContainerInput) int { return cmp.Compare(a.Name, b.Name) })
|
||||
assert.EqualValues(t, testCase.inputs, containerInputs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
10
act/testutils/mockvariable.go
Normal file
10
act/testutils/mockvariable.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package testutils
|
||||
|
||||
func MockVariable[T any](p *T, v T) (reset func()) {
|
||||
old := *p
|
||||
*p = v
|
||||
return func() { *p = old }
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue