diff options
| author | jannfis <jann@mistrust.net> | 2020-11-23 20:02:48 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-11-23 20:02:48 +0100 |
| commit | e3b13f16bfc543ffe98fac6b84b309fc8bf719ff (patch) | |
| tree | d8311564105e01002bdb053a89b1b9c0c80ac812 /pkg | |
| parent | 6723a25cfcd3f117f0d234b5449db7a6cc68bc2d (diff) | |
feat: Support for getting pull creds from external scripts (#121)
* feat: Support for getting pull creds from external scripts
* spelling
* be more verbose on error
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/image/credentials.go | 37 | ||||
| -rw-r--r-- | pkg/image/credentials_test.go | 67 |
2 files changed, 104 insertions, 0 deletions
diff --git a/pkg/image/credentials.go b/pkg/image/credentials.go index 4410ccb..cccbb1d 100644 --- a/pkg/image/credentials.go +++ b/pkg/image/credentials.go @@ -5,7 +5,11 @@ import ( "encoding/json" "fmt" "os" + "os/exec" "strings" + "time" + + argoexec "github.com/argoproj/pkg/exec" "github.com/argoproj-labs/argocd-image-updater/pkg/client" "github.com/argoproj-labs/argocd-image-updater/pkg/log" @@ -18,6 +22,7 @@ const ( CredentialSourcePullSecret CredentialSourceType = 1 CredentialSourceSecret CredentialSourceType = 2 CredentialSourceEnv CredentialSourceType = 3 + CredentialSourceExt CredentialSourceType = 4 ) type CredentialSource struct { @@ -27,6 +32,7 @@ type CredentialSource struct { SecretName string SecretField string EnvName string + ScriptPath string } type Credential struct { @@ -70,6 +76,9 @@ func ParseCredentialSource(credentialSource string, requirePrefix bool) (*Creden case "env": err = src.parseEnvDefinition(tokens[1]) src.Type = CredentialSourceEnv + case "ext": + err = src.parseExtDefinition(tokens[1]) + src.Type = CredentialSourceExt default: err = fmt.Errorf("unknown credential source: %s", tokens[0]) } @@ -121,6 +130,27 @@ func (src *CredentialSource) FetchCredentials(registryURL string, kubeclient *cl return nil, err } return &creds, nil + case CredentialSourceExt: + if !strings.HasPrefix(src.ScriptPath, "/") { + return nil, fmt.Errorf("path to script must be absolute, but is '%s'", src.ScriptPath) + } + _, err := os.Stat(src.ScriptPath) + if err != nil { + return nil, fmt.Errorf("could not stat %s: %v", src.ScriptPath, err) + } + cmd := exec.Command(src.ScriptPath) + out, err := argoexec.RunCommandExt(cmd, argoexec.CmdOpts{Timeout: 10 * time.Second}) + if err != nil { + return nil, fmt.Errorf("error executing %s: %v", src.ScriptPath, err) + } + tokens := strings.SplitN(out, ":", 2) + if len(tokens) != 2 { + return nil, fmt.Errorf("invalid script output, must be single line with syntax <username>:<password>") + } + creds.Username = tokens[0] + creds.Password = tokens[1] + return &creds, nil + default: return nil, fmt.Errorf("unknown credential type") } @@ -164,6 +194,13 @@ func (src *CredentialSource) parseEnvDefinition(definition string) error { return nil } +// Parse an external script definition +// nolint:unparam +func (src *CredentialSource) parseExtDefinition(definition string) error { + src.ScriptPath = definition + return nil +} + // This unmarshals & parses Docker's config.json file, returning username and // password for given registry URL func parseDockerConfigJson(registryURL string, jsonSource string) (string, string, error) { diff --git a/pkg/image/credentials_test.go b/pkg/image/credentials_test.go index 9e2a056..8c2682d 100644 --- a/pkg/image/credentials_test.go +++ b/pkg/image/credentials_test.go @@ -2,6 +2,7 @@ package image import ( "os" + "path" "testing" "github.com/argoproj-labs/argocd-image-updater/pkg/client" @@ -84,6 +85,21 @@ func Test_ParseCredentialAnnotation(t *testing.T) { assert.Error(t, err) assert.Nil(t, src) }) + + t.Run("Parse valid credentials definition from environment", func(t *testing.T) { + src, err := ParseCredentialSource("env:DUMMY_SECRET", false) + require.NoError(t, err) + require.NotNil(t, src) + assert.Equal(t, "DUMMY_SECRET", src.EnvName) + }) + + t.Run("Parse valid credentials definition from environment", func(t *testing.T) { + src, err := ParseCredentialSource("env:DUMMY_SECRET", false) + require.NoError(t, err) + require.NotNil(t, src) + assert.Equal(t, "DUMMY_SECRET", src.EnvName) + }) + } func Test_ParseCredentialReference(t *testing.T) { @@ -198,6 +214,57 @@ func Test_FetchCredentialsFromEnv(t *testing.T) { }) } +func Test_FetchCredentialsFromExt(t *testing.T) { + t.Run("Fetch credentials from external script - valid output", func(t *testing.T) { + pwd, err := os.Getwd() + require.NoError(t, err) + credSrc := &CredentialSource{ + Type: CredentialSourceExt, + Registry: "https://registry-1.docker.io/v2", + ScriptPath: path.Join(pwd, "..", "..", "test", "testdata", "scripts", "get-credentials-valid.sh"), + } + creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil) + require.NoError(t, err) + require.NotNil(t, creds) + assert.Equal(t, "username", creds.Username) + assert.Equal(t, "password", creds.Password) + }) + t.Run("Fetch credentials from external script - invalid script output", func(t *testing.T) { + pwd, err := os.Getwd() + require.NoError(t, err) + credSrc := &CredentialSource{ + Type: CredentialSourceExt, + Registry: "https://registry-1.docker.io/v2", + ScriptPath: path.Join(pwd, "..", "..", "test", "testdata", "scripts", "get-credentials-invalid.sh"), + } + creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil) + require.Errorf(t, err, "invalid script output") + require.Nil(t, creds) + }) + t.Run("Fetch credentials from external script - script does not exist", func(t *testing.T) { + pwd, err := os.Getwd() + require.NoError(t, err) + credSrc := &CredentialSource{ + Type: CredentialSourceExt, + Registry: "https://registry-1.docker.io/v2", + ScriptPath: path.Join(pwd, "..", "..", "test", "testdata", "scripts", "get-credentials-notexist.sh"), + } + creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil) + require.Errorf(t, err, "no such file or directory") + require.Nil(t, creds) + }) + t.Run("Fetch credentials from external script - relative path", func(t *testing.T) { + credSrc := &CredentialSource{ + Type: CredentialSourceExt, + Registry: "https://registry-1.docker.io/v2", + ScriptPath: "get-credentials-notexist.sh", + } + creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil) + require.Errorf(t, err, "path to script must be absolute") + require.Nil(t, creds) + }) +} + func Test_ParseDockerConfig(t *testing.T) { t.Run("Parse valid Docker configuration with matching registry", func(t *testing.T) { config := fixture.MustReadFile("../../test/testdata/docker/valid-config.json") |
