Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type Input struct {
useNewActionCache bool
localRepository []string
listOptions bool
passEnvVarsToDockerBuild string
}

func (i *Input) resolve(path string) string {
Expand Down
8 changes: 8 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ func Execute(ctx context.Context, version string) {
}

func createRootCommand(ctx context.Context, input *Input, version string) *cobra.Command {
customEnvVarFlag := "pass-envvars-to-docker-build"
customEnvVarDefault := "HTTP_PROXY,HTTPS_PROXY,NO_PROXY,http_proxy,https_proxy,no_proxy"

rootCmd := &cobra.Command{
Use: "act [event name to run] [flags]\n\nIf no event name passed, will default to \"on: push\"\nIf actions handles only one event it will be used as default instead of \"on: push\"",
Short: "Run GitHub actions locally by specifying the event name (e.g. `push`) or an action name directly.",
Expand Down Expand Up @@ -127,6 +130,10 @@ func createRootCommand(ctx context.Context, input *Input, version string) *cobra
rootCmd.PersistentFlags().BoolVarP(&input.useNewActionCache, "use-new-action-cache", "", false, "Enable using the new Action Cache for storing Actions locally")
rootCmd.PersistentFlags().StringArrayVarP(&input.localRepository, "local-repository", "", []string{}, "Replaces the specified repository and ref with a local folder (e.g. https://github.com/test/test@v0=/home/act/test or test/test@v0=/home/act/test, the latter matches any hosts or protocols)")
rootCmd.PersistentFlags().BoolVar(&input.listOptions, "list-options", false, "Print a json structure of compatible options")
// do not set customEnvVarDefault as default for customEnvVarFlag so no values are passed to build if not specified by user
rootCmd.PersistentFlags().StringVar(&input.passEnvVarsToDockerBuild, customEnvVarFlag, "", fmt.Sprintf("A comma separated list of keys of env vars to pass as build args to build of Docker actions, e.g. %s. Use flag with no value to get these default proxy values.", customEnvVarDefault))
rootCmd.PersistentFlags().Lookup(customEnvVarFlag).NoOptDefVal = customEnvVarDefault

rootCmd.SetArgs(args())
return rootCmd
}
Expand Down Expand Up @@ -635,6 +642,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
ReplaceGheActionTokenWithGithubCom: input.replaceGheActionTokenWithGithubCom,
Matrix: matrixes,
ContainerNetworkMode: docker_container.NetworkMode(input.networkName),
PassEnvVarsToDockerBuild: input.passEnvVarsToDockerBuild,
}
if input.useNewActionCache || len(input.localRepository) > 0 {
if input.actionOfflineMode {
Expand Down
1 change: 1 addition & 0 deletions pkg/container/container_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type NewDockerBuildExecutorInput struct {
BuildContext io.Reader
ImageTag string
Platform string
BuildArgs map[string]string
}

// NewDockerPullExecutorInput the input for the NewDockerPullExecutor function
Expand Down
28 changes: 26 additions & 2 deletions pkg/container/docker_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ package container

import (
"context"
"fmt"
"io"
"net/url"
"os"
"path/filepath"

Expand All @@ -19,14 +21,29 @@ import (
"github.com/nektos/act/pkg/common"
)

// redact password from the proxy url
func redactProxyURL(proxyURLStr string) string {
u, err := url.Parse(proxyURLStr)
if err != nil {
// user has sent some dodgy value so we'll just accept it
return proxyURLStr
}
return u.Redacted()
}

// NewDockerBuildExecutor function to create a run executor for the container
func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
return func(ctx context.Context) error {
logger := common.Logger(ctx)

buildArgsStr := ""
for k, v := range input.BuildArgs {
buildArgsStr += fmt.Sprintf("--build-arg %s=%s ", k, redactProxyURL(v))
}
if input.Platform != "" {
logger.Infof("%sdocker build -t %s --platform %s %s", logPrefix, input.ImageTag, input.Platform, input.ContextDir)
logger.Infof("%sdocker build %s-t %s --platform %s %s", logPrefix, buildArgsStr, input.ImageTag, input.Platform, input.ContextDir)
} else {
logger.Infof("%sdocker build -t %s %s", logPrefix, input.ImageTag, input.ContextDir)
logger.Infof("%sdocker build %s-t %s %s", logPrefix, buildArgsStr, input.ImageTag, input.ContextDir)
}
if common.Dryrun(ctx) {
return nil
Expand All @@ -40,13 +57,20 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {

logger.Debugf("Building image from '%v'", input.ContextDir)

buildArgs := map[string]*string{}
for k, v := range input.BuildArgs {
val := v
buildArgs[k] = &val
}

tags := []string{input.ImageTag}
options := types.ImageBuildOptions{
Tags: tags,
Remove: true,
Platform: input.Platform,
AuthConfigs: LoadDockerAuthConfigs(ctx),
Dockerfile: input.Dockerfile,
BuildArgs: buildArgs,
}
var buildContext io.ReadCloser
if input.BuildContext != nil {
Expand Down
41 changes: 41 additions & 0 deletions pkg/container/docker_build_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package container

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestRedactProxyURL(t *testing.T) {
tests := []struct {
input string
expected string
}{
{
input: "https://foo:[email protected]:1234",
expected: "https://foo:[email protected]:1234",
},
{
input: "https://foo:[email protected]",
expected: "https://foo:[email protected]",
},
{
input: "https://foo:bar%[email protected]:1234",
expected: "https://foo:[email protected]:1234",
},
{
input: "https://example.com:1234",
expected: "https://example.com:1234",
},
{
input: "https://[email protected]",
expected: "https://[email protected]",
},
}
for _, tc := range tests {
t.Run(tc.input, func(t *testing.T) {
redacted := redactProxyURL(tc.input)
assert.Equal(t, redacted, tc.expected)
})
}
}
19 changes: 19 additions & 0 deletions pkg/runner/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,21 @@ func removeGitIgnore(ctx context.Context, directory string) error {
return nil
}

func buildArgsFromCustomEnvVars(envVarKeys string) map[string]string {
buildArgs := map[string]string{}
if envVarKeys == "" {
return buildArgs
}
for _, key := range strings.Split(envVarKeys, ",") {
trimmedKey := strings.TrimSpace(key)
if trimmedKey == "" {
continue
}
buildArgs[trimmedKey] = os.Getenv(trimmedKey)
}
return buildArgs
}

// TODO: break out parts of function to reduce complexicity
//
//nolint:gocyclo
Expand Down Expand Up @@ -299,12 +314,16 @@ func execAsDocker(ctx context.Context, step actionStep, actionName, basedir, sub
}
defer buildContext.Close()
}

buildArgs := buildArgsFromCustomEnvVars(rc.Config.PassEnvVarsToDockerBuild)

prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
ContextDir: filepath.Join(basedir, contextDir),
Dockerfile: fileName,
ImageTag: image,
BuildContext: buildContext,
Platform: rc.Config.ContainerArchitecture,
BuildArgs: buildArgs,
})
} else {
logger.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture)
Expand Down
94 changes: 94 additions & 0 deletions pkg/runner/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,97 @@ func TestActionRunner(t *testing.T) {
})
}
}

func TestActionRunner_buildArgsFromCustomEnvVars(t *testing.T) {
table := []struct {
name string
envVars string
expectedBuildArgs map[string]string
}{
{
name: "no build args passed for empty input",
envVars: "",
expectedBuildArgs: map[string]string{},
},
{
name: "default proxy vars",
envVars: "HTTP_PROXY,HTTPS_PROXY,NO_PROXY,http_proxy,https_proxy,no_proxy",
expectedBuildArgs: map[string]string{
"HTTP_PROXY": "HTTP_PROXY_VAL",
"HTTPS_PROXY": "HTTPS_PROXY_VAL",
"NO_PROXY": "NO_PROXY_VAL",
"http_proxy": "http_proxy_val",
"https_proxy": "https_proxy_val",
"no_proxy": "no_proxy_val",
},
},
{
name: "nonexistent key is empty string",
envVars: "nonexistent",
expectedBuildArgs: map[string]string{
"nonexistent": "",
},
},
{
name: "single key is ok",
envVars: "HTTP_PROXY",
expectedBuildArgs: map[string]string{
"HTTP_PROXY": "HTTP_PROXY_VAL",
},
},
{
name: "comma as key returns empty",
envVars: ",",
expectedBuildArgs: map[string]string{},
},
{
name: "whitespace key returns empty",
envVars: " ",
expectedBuildArgs: map[string]string{},
},
{
name: "multiple whitespace keys returns empty",
envVars: " , ",
expectedBuildArgs: map[string]string{},
},
{
name: "embedded empty key ignored",
envVars: "HTTP_PROXY,,NO_PROXY",
expectedBuildArgs: map[string]string{
"HTTP_PROXY": "HTTP_PROXY_VAL",
"NO_PROXY": "NO_PROXY_VAL",
},
},
{
name: "embedded whitespace key ignored",
envVars: "HTTP_PROXY, ,NO_PROXY",
expectedBuildArgs: map[string]string{
"HTTP_PROXY": "HTTP_PROXY_VAL",
"NO_PROXY": "NO_PROXY_VAL",
},
},
{
name: "whitespace around key is ignored",
envVars: "HTTP_PROXY , HTTPS_PROXY , NO_PROXY",
expectedBuildArgs: map[string]string{
"HTTP_PROXY": "HTTP_PROXY_VAL",
"HTTPS_PROXY": "HTTPS_PROXY_VAL",
"NO_PROXY": "NO_PROXY_VAL",
},
},
}

for _, tt := range table {
t.Setenv("HTTP_PROXY", "HTTP_PROXY_VAL")
t.Setenv("HTTPS_PROXY", "HTTPS_PROXY_VAL")
t.Setenv("NO_PROXY", "NO_PROXY_VAL")
t.Setenv("http_proxy", "http_proxy_val")
t.Setenv("https_proxy", "https_proxy_val")
t.Setenv("no_proxy", "no_proxy_val")
t.Setenv("nonexistent", "")
t.Run(tt.name, func(t *testing.T) {
buildArgs := buildArgsFromCustomEnvVars(tt.envVars)
assert.Equal(t, tt.expectedBuildArgs, buildArgs)
})
}
}
1 change: 1 addition & 0 deletions pkg/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Config struct {
Matrix map[string]map[string]bool // Matrix config to run
ContainerNetworkMode docker_container.NetworkMode // the network mode of job containers (the value of --network)
ActionCache ActionCache // Use a custom ActionCache Implementation
PassEnvVarsToDockerBuild string // Comma separated list of keys of env vars to pass to build of Docker actions
}

type caller struct {
Expand Down