From d2ef16b698b380cbe0f650416b3664cb7555a2dc Mon Sep 17 00:00:00 2001 From: Till! Date: Sat, 27 Nov 2021 19:05:56 +0100 Subject: [PATCH] container credentials (#868) * Chore: add a snapshot target * Update: support credentials for private containers Resolves: #788 * fix: rework container credentials Signed-off-by: hackercat * fix: check if Credentials are not nil * fix: return on missing credentials key Co-authored-by: hackercat Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- act/model/workflow.go | 19 +++++++++-------- act/model/workflow_test.go | 30 ++++++++++++++++++++++++++ act/runner/run_context.go | 43 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/act/model/workflow.go b/act/model/workflow.go index 5b664b53..eb6e4865 100644 --- a/act/model/workflow.go +++ b/act/model/workflow.go @@ -295,15 +295,16 @@ func commonKeysMatch(a map[string]interface{}, b map[string]interface{}) bool { // ContainerSpec is the specification of the container to use for the job type ContainerSpec struct { - Image string `yaml:"image"` - Env map[string]string `yaml:"env"` - Ports []string `yaml:"ports"` - Volumes []string `yaml:"volumes"` - Options string `yaml:"options"` - Entrypoint string - Args string - Name string - Reuse bool + Image string `yaml:"image"` + Env map[string]string `yaml:"env"` + Ports []string `yaml:"ports"` + Volumes []string `yaml:"volumes"` + Options string `yaml:"options"` + Credentials map[string]string `yaml:"credentials"` + Entrypoint string + Args string + Name string + Reuse bool } // Step is the structure of one step in a job diff --git a/act/model/workflow_test.go b/act/model/workflow_test.go index 914e89c0..86b43807 100644 --- a/act/model/workflow_test.go +++ b/act/model/workflow_test.go @@ -99,6 +99,36 @@ jobs: assert.Contains(t, workflow.Jobs["test2"].Container().Env["foo"], "bar") } +func TestReadWorkflow_ObjectContainer(t *testing.T) { + yaml := ` +name: local-action-docker-url + +jobs: + test: + container: + image: r.example.org/something:latest + credentials: + username: registry-username + password: registry-password + env: + HOME: /home/user + runs-on: ubuntu-latest + steps: + - uses: ./actions/docker-url +` + + workflow, err := ReadWorkflow(strings.NewReader(yaml)) + assert.NoError(t, err, "read workflow should succeed") + assert.Len(t, workflow.Jobs, 1) + + container := workflow.GetJob("test").Container() + + assert.Contains(t, container.Image, "r.example.org/something:latest") + assert.Contains(t, container.Env["HOME"], "/home/user") + assert.Contains(t, container.Credentials["username"], "registry-username") + assert.Contains(t, container.Credentials["password"], "registry-password") +} + func TestReadWorkflow_StepsTypes(t *testing.T) { yaml := ` name: invalid step definition diff --git a/act/runner/run_context.go b/act/runner/run_context.go index 50c59cdd..e813c0af 100755 --- a/act/runner/run_context.go +++ b/act/runner/run_context.go @@ -153,6 +153,11 @@ func (rc *RunContext) startJobContainer() common.Executor { return true }) + username, password, err := rc.handleCredentials() + if err != nil { + return fmt.Errorf("failed to handle credentials: %s", err) + } + common.Logger(ctx).Infof("\U0001f680 Start image=%s", image) name := rc.jobContainerName() @@ -169,8 +174,8 @@ func (rc *RunContext) startJobContainer() common.Executor { Entrypoint: []string{"/usr/bin/tail", "-f", "/dev/null"}, WorkingDir: rc.Config.ContainerWorkdir(), Image: image, - Username: rc.Config.Secrets["DOCKER_USERNAME"], - Password: rc.Config.Secrets["DOCKER_PASSWORD"], + Username: username, + Password: password, Name: name, Env: envList, Mounts: mounts, @@ -836,3 +841,37 @@ func (rc *RunContext) localCheckoutPath() (string, bool) { } return "", false } + +func (rc *RunContext) handleCredentials() (username, password string, err error) { + // TODO: remove below 2 lines when we can release act with breaking changes + username = rc.Config.Secrets["DOCKER_USERNAME"] + password = rc.Config.Secrets["DOCKER_PASSWORD"] + + container := rc.Run.Job().Container() + if container == nil || container.Credentials == nil { + return + } + + if container.Credentials != nil && len(container.Credentials) != 2 { + err = fmt.Errorf("invalid property count for key 'credentials:'") + return + } + + ee := rc.NewExpressionEvaluator() + var ok bool + if username, ok = ee.InterpolateWithStringCheck(container.Credentials["username"]); !ok { + err = fmt.Errorf("failed to interpolate container.credentials.username") + return + } + if password, ok = ee.InterpolateWithStringCheck(container.Credentials["password"]); !ok { + err = fmt.Errorf("failed to interpolate container.credentials.password") + return + } + + if container.Credentials["username"] == "" || container.Credentials["password"] == "" { + err = fmt.Errorf("container.credentials cannot be empty") + return + } + + return username, password, err +}