Skip to content

Commit 5d86d20

Browse files
authored
Add --registry-authfile to specify a specific auth file for the registry push (knative#3208)
* Add --registry-authfile to specify a specific auth file for the registry push * Add option to deploy command too * Run hack/update-codegen.sh * Use t.TempDir() to create temporary authfile in * Use t.Fatalf() instead of t.Errorf() with a return * Marked some helper functions with t.Helper()
1 parent f785746 commit 5d86d20

File tree

7 files changed

+162
-45
lines changed

7 files changed

+162
-45
lines changed

cmd/build.go

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
pack "knative.dev/func/pkg/builders/buildpacks"
1414
"knative.dev/func/pkg/builders/s2i"
1515
"knative.dev/func/pkg/config"
16+
"knative.dev/func/pkg/docker"
1617
fn "knative.dev/func/pkg/functions"
1718
"knative.dev/func/pkg/oci"
1819
)
@@ -29,7 +30,7 @@ SYNOPSIS
2930
{{rootCmdUse}} build [-r|--registry] [--builder] [--builder-image]
3031
[--push] [--username] [--password] [--token]
3132
[--platform] [-p|--path] [-c|--confirm] [-v|--verbose]
32-
[--build-timestamp] [--registry-insecure]
33+
[--build-timestamp] [--registry-insecure] [--registry-authfile]
3334
3435
DESCRIPTION
3536
@@ -69,7 +70,7 @@ EXAMPLES
6970
SuggestFor: []string{"biuld", "buidl", "built"},
7071
PreRunE: bindEnv("image", "path", "builder", "registry", "confirm",
7172
"push", "builder-image", "base-image", "platform", "verbose",
72-
"build-timestamp", "registry-insecure", "username", "password", "token"),
73+
"build-timestamp", "registry-insecure", "registry-authfile", "username", "password", "token"),
7374
RunE: func(cmd *cobra.Command, args []string) error {
7475
return runBuild(cmd, args, newClient)
7576
},
@@ -102,6 +103,7 @@ EXAMPLES
102103
cmd.Flags().StringP("registry", "r", cfg.Registry,
103104
"Container registry + registry namespace. (ex 'ghcr.io/myuser'). The full image name is automatically determined using this along with function name. ($FUNC_REGISTRY)")
104105
cmd.Flags().Bool("registry-insecure", cfg.RegistryInsecure, "Skip TLS certificate verification when communicating in HTTPS with the registry ($FUNC_REGISTRY_INSECURE)")
106+
cmd.Flags().String("registry-authfile", "", "Path to a authentication file containing registry credentials ($FUNC_REGISTRY_AUTHFILE)")
105107

106108
// Function-Context Flags:
107109
// Options whose value is available on the function with context only
@@ -293,6 +295,9 @@ type buildConfig struct {
293295
// Build with the current timestamp as the created time for docker image.
294296
// This is only useful for buildpacks builder.
295297
WithTimestamp bool
298+
299+
// RegistryAuthfile is the path to a docker-config file containing registry credentials.
300+
RegistryAuthfile string
296301
}
297302

298303
// newBuildConfig gathers options into a single build request.
@@ -305,16 +310,17 @@ func newBuildConfig() buildConfig {
305310
Verbose: viper.GetBool("verbose"),
306311
RegistryInsecure: viper.GetBool("registry-insecure"),
307312
},
308-
BuilderImage: viper.GetString("builder-image"),
309-
BaseImage: viper.GetString("base-image"),
310-
Image: viper.GetString("image"),
311-
Path: viper.GetString("path"),
312-
Platform: viper.GetString("platform"),
313-
Push: viper.GetBool("push"),
314-
Username: viper.GetString("username"),
315-
Password: viper.GetString("password"),
316-
Token: viper.GetString("token"),
317-
WithTimestamp: viper.GetBool("build-timestamp"),
313+
BuilderImage: viper.GetString("builder-image"),
314+
BaseImage: viper.GetString("base-image"),
315+
Image: viper.GetString("image"),
316+
Path: viper.GetString("path"),
317+
Platform: viper.GetString("platform"),
318+
Push: viper.GetBool("push"),
319+
Username: viper.GetString("username"),
320+
Password: viper.GetString("password"),
321+
Token: viper.GetString("token"),
322+
WithTimestamp: viper.GetBool("build-timestamp"),
323+
RegistryAuthfile: viper.GetString("registry-authfile"),
318324
}
319325
}
320326

@@ -479,10 +485,12 @@ func (c buildConfig) Validate(cmd *cobra.Command) (err error) {
479485
// deployment is not the contiainer, but rather the running service.
480486
func (c buildConfig) clientOptions() ([]fn.Option, error) {
481487
o := []fn.Option{fn.WithRegistry(c.Registry)}
488+
489+
t := newTransport(c.RegistryInsecure)
490+
creds := newCredentialsProvider(config.Dir(), t, c.RegistryAuthfile)
491+
482492
switch c.Builder {
483493
case builders.Host:
484-
t := newTransport(c.RegistryInsecure) // may provide a custom impl which proxies
485-
creds := newCredentialsProvider(config.Dir(), t)
486494
o = append(o,
487495
fn.WithBuilder(oci.NewBuilder(builders.Host, c.Verbose)),
488496
fn.WithPusher(oci.NewPusher(c.RegistryInsecure, false, c.Verbose,
@@ -495,12 +503,20 @@ func (c buildConfig) clientOptions() ([]fn.Option, error) {
495503
fn.WithBuilder(pack.NewBuilder(
496504
pack.WithName(builders.Pack),
497505
pack.WithTimestamp(c.WithTimestamp),
498-
pack.WithVerbose(c.Verbose))))
506+
pack.WithVerbose(c.Verbose))),
507+
fn.WithPusher(docker.NewPusher(
508+
docker.WithCredentialsProvider(creds),
509+
docker.WithTransport(t),
510+
docker.WithVerbose(c.Verbose))))
499511
case builders.S2I:
500512
o = append(o,
501513
fn.WithBuilder(s2i.NewBuilder(
502514
s2i.WithName(builders.S2I),
503-
s2i.WithVerbose(c.Verbose))))
515+
s2i.WithVerbose(c.Verbose))),
516+
fn.WithPusher(docker.NewPusher(
517+
docker.WithCredentialsProvider(creds),
518+
docker.WithTransport(t),
519+
docker.WithVerbose(c.Verbose))))
504520
default:
505521
return o, builders.ErrUnknownBuilder{Name: c.Builder, Known: KnownBuilders()}
506522
}

cmd/client.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ func NewTestClient(options ...fn.Option) ClientFactory {
5656
// 'Verbose' indicates the system should write out a higher amount of logging.
5757
func NewClient(cfg ClientConfig, options ...fn.Option) (*fn.Client, func()) {
5858
var (
59-
t = newTransport(cfg.InsecureSkipVerify) // may provide a custom impl which proxies
60-
c = newCredentialsProvider(config.Dir(), t) // for accessing registries
61-
d = newKnativeDeployer(cfg.Verbose) // default deployer (can be overridden via options)
59+
t = newTransport(cfg.InsecureSkipVerify) // may provide a custom impl which proxies
60+
c = newCredentialsProvider(config.Dir(), t, "") // for accessing registries
61+
d = newKnativeDeployer(cfg.Verbose) // default deployer (can be overridden via options)
6262
pp = newTektonPipelinesProvider(c, cfg.Verbose)
6363
o = []fn.Option{ // standard (shared) options for all commands
6464
fn.WithVerbose(cfg.Verbose),
@@ -101,7 +101,8 @@ func newTransport(insecureSkipVerify bool) fnhttp.RoundTripCloser {
101101
// newCredentialsProvider returns a credentials provider which possibly
102102
// has cluster-flavor specific additional credential loaders to take advantage
103103
// of features or configuration nuances of cluster variants.
104-
func newCredentialsProvider(configPath string, t http.RoundTripper) oci.CredentialsProvider {
104+
// If authFilePath is provided (non-empty), it will be used as the primary auth file.
105+
func newCredentialsProvider(configPath string, t http.RoundTripper, authFilePath string) oci.CredentialsProvider {
105106
additionalLoaders := append(k8s.GetOpenShiftDockerCredentialLoaders(), k8s.GetGoogleCredentialLoader()...)
106107
additionalLoaders = append(additionalLoaders, k8s.GetECRCredentialLoader()...)
107108
additionalLoaders = append(additionalLoaders, k8s.GetACRCredentialLoader()...)
@@ -112,6 +113,11 @@ func newCredentialsProvider(configPath string, t http.RoundTripper) oci.Credenti
112113
creds.WithAdditionalCredentialLoaders(additionalLoaders...),
113114
}
114115

116+
// If a custom auth file path is provided, use it
117+
if authFilePath != "" {
118+
options = append(options, creds.WithAuthFilePath(authFilePath))
119+
}
120+
115121
// Other cluster variants can be supported here
116122
return creds.NewCredentialsProvider(configPath, options...)
117123
}

cmd/deploy.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ SYNOPSIS
3737
[-b|--build] [--builder] [--builder-image] [-p|--push]
3838
[--domain] [--platform] [--build-timestamp] [--pvc-size]
3939
[--service-account] [-c|--confirm] [-v|--verbose]
40-
[--registry-insecure] [--remote-storage-class]
40+
[--registry-insecure] [--registry-authfile] [--remote-storage-class]
4141
4242
DESCRIPTION
4343
@@ -132,8 +132,9 @@ EXAMPLES
132132
PreRunE: bindEnv("build", "build-timestamp", "builder", "builder-image",
133133
"base-image", "confirm", "domain", "env", "git-branch", "git-dir",
134134
"git-url", "image", "namespace", "path", "platform", "push", "pvc-size",
135-
"service-account", "deployer", "registry", "registry-insecure", "remote",
136-
"username", "password", "token", "verbose", "remote-storage-class"),
135+
"service-account", "deployer", "registry", "registry-insecure",
136+
"registry-authfile", "remote", "username", "password", "token", "verbose",
137+
"remote-storage-class"),
137138
RunE: func(cmd *cobra.Command, args []string) error {
138139
return runDeploy(cmd, newClient)
139140
},
@@ -161,6 +162,7 @@ EXAMPLES
161162
cmd.Flags().StringP("registry", "r", cfg.Registry,
162163
"Container registry + registry namespace. (ex 'ghcr.io/myuser'). The full image name is automatically determined using this along with function name. ($FUNC_REGISTRY)")
163164
cmd.Flags().Bool("registry-insecure", cfg.RegistryInsecure, "Skip TLS certificate verification when communicating in HTTPS with the registry ($FUNC_REGISTRY_INSECURE)")
165+
cmd.Flags().String("registry-authfile", "", "Path to a authentication file containing registry credentials ($FUNC_REGISTRY_AUTHFILE)")
164166

165167
// Function-Context Flags:
166168
// Options whose value is available on the function with context only
@@ -918,6 +920,13 @@ func (c deployConfig) clientOptions() ([]fn.Option, error) {
918920
return o, err
919921
}
920922

923+
t := newTransport(c.RegistryInsecure)
924+
creds := newCredentialsProvider(config.Dir(), t, c.RegistryAuthfile)
925+
926+
// Override the pipelines provider to use custom credentials
927+
// This is needed for remote builds (deploy --remote)
928+
o = append(o, fn.WithPipelinesProvider(newTektonPipelinesProvider(creds, c.Verbose)))
929+
921930
// Add the appropriate deployer based on deploy type
922931
deployer := c.Deployer
923932
if deployer == "" {

docs/reference/func_build.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ SYNOPSIS
1212
func build [-r|--registry] [--builder] [--builder-image]
1313
[--push] [--username] [--password] [--token]
1414
[--platform] [-p|--path] [-c|--confirm] [-v|--verbose]
15-
[--build-timestamp] [--registry-insecure]
15+
[--build-timestamp] [--registry-insecure] [--registry-authfile]
1616

1717
DESCRIPTION
1818

@@ -57,19 +57,20 @@ func build
5757
### Options
5858

5959
```
60-
--base-image string Override the base image for your function (host builder only)
61-
--build-timestamp Use the actual time as the created time for the docker image. This is only useful for buildpacks builder.
62-
-b, --builder string Builder to use when creating the function's container. Currently supported builders are "host", "pack" and "s2i". ($FUNC_BUILDER) (default "pack")
63-
--builder-image string Specify a custom builder image for use by the builder other than its default. ($FUNC_BUILDER_IMAGE)
64-
-c, --confirm Prompt to confirm options interactively ($FUNC_CONFIRM)
65-
-h, --help help for build
66-
-i, --image string Full image name in the form [registry]/[namespace]/[name]:[tag] (optional). This option takes precedence over --registry ($FUNC_IMAGE)
67-
-p, --path string Path to the function. Default is current directory ($FUNC_PATH)
68-
--platform string Optionally specify a target platform, for example "linux/amd64" when using the s2i build strategy
69-
-u, --push Attempt to push the function image to the configured registry after being successfully built
70-
-r, --registry string Container registry + registry namespace. (ex 'ghcr.io/myuser'). The full image name is automatically determined using this along with function name. ($FUNC_REGISTRY)
71-
--registry-insecure Skip TLS certificate verification when communicating in HTTPS with the registry ($FUNC_REGISTRY_INSECURE)
72-
-v, --verbose Print verbose logs ($FUNC_VERBOSE)
60+
--base-image string Override the base image for your function (host builder only)
61+
--build-timestamp Use the actual time as the created time for the docker image. This is only useful for buildpacks builder.
62+
-b, --builder string Builder to use when creating the function's container. Currently supported builders are "host", "pack" and "s2i". ($FUNC_BUILDER) (default "pack")
63+
--builder-image string Specify a custom builder image for use by the builder other than its default. ($FUNC_BUILDER_IMAGE)
64+
-c, --confirm Prompt to confirm options interactively ($FUNC_CONFIRM)
65+
-h, --help help for build
66+
-i, --image string Full image name in the form [registry]/[namespace]/[name]:[tag] (optional). This option takes precedence over --registry ($FUNC_IMAGE)
67+
-p, --path string Path to the function. Default is current directory ($FUNC_PATH)
68+
--platform string Optionally specify a target platform, for example "linux/amd64" when using the s2i build strategy
69+
-u, --push Attempt to push the function image to the configured registry after being successfully built
70+
-r, --registry string Container registry + registry namespace. (ex 'ghcr.io/myuser'). The full image name is automatically determined using this along with function name. ($FUNC_REGISTRY)
71+
--registry-authfile string Path to a authentication file containing registry credentials ($FUNC_REGISTRY_AUTHFILE)
72+
--registry-insecure Skip TLS certificate verification when communicating in HTTPS with the registry ($FUNC_REGISTRY_INSECURE)
73+
-v, --verbose Print verbose logs ($FUNC_VERBOSE)
7374
```
7475

7576
### SEE ALSO

docs/reference/func_deploy.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ SYNOPSIS
1414
[-b|--build] [--builder] [--builder-image] [-p|--push]
1515
[--domain] [--platform] [--build-timestamp] [--pvc-size]
1616
[--service-account] [-c|--confirm] [-v|--verbose]
17-
[--registry-insecure] [--remote-storage-class]
17+
[--registry-insecure] [--registry-authfile] [--remote-storage-class]
1818

1919
DESCRIPTION
2020

@@ -133,6 +133,7 @@ func deploy
133133
-u, --push Push the function image to registry before deploying. ($FUNC_PUSH) (default true)
134134
--pvc-size string When triggering a remote deployment, set a custom volume size to allocate for the build operation ($FUNC_PVC_SIZE)
135135
-r, --registry string Container registry + registry namespace. (ex 'ghcr.io/myuser'). The full image name is automatically determined using this along with function name. ($FUNC_REGISTRY)
136+
--registry-authfile string Path to a authentication file containing registry credentials ($FUNC_REGISTRY_AUTHFILE)
136137
--registry-insecure Skip TLS certificate verification when communicating in HTTPS with the registry ($FUNC_REGISTRY_INSECURE)
137138
-R, --remote Trigger a remote deployment. Default is to deploy and build from the local system ($FUNC_REMOTE)
138139
--remote-storage-class string Specify a storage class to use for the volume on-cluster during remote builds

pkg/creds/credentials.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ type credentialsProvider struct {
9494

9595
type Opt func(opts *credentialsProvider)
9696

97+
// WithAuthFilePath sets a custom path to a docker-config file containing registry credentials.
98+
// If not specified, the default path (configPath/auth.json) will be used.
99+
func WithAuthFilePath(path string) Opt {
100+
return func(opts *credentialsProvider) {
101+
opts.authFilePath = path
102+
}
103+
}
104+
97105
// WithPromptForCredentials sets custom callback that is supposed to
98106
// interactively ask for credentials in case the credentials cannot be found in configuration files.
99107
// The callback may be called multiple times in case incorrect credentials were returned before.
@@ -187,7 +195,10 @@ func NewCredentialsProvider(configPath string, opts ...Opt) oci.CredentialsProvi
187195
return oci.Credentials{}, ErrCredentialsNotFound
188196
})
189197

190-
c.authFilePath = filepath.Join(configPath, "auth.json")
198+
// Set authFilePath if not already set by WithAuthFilePath option
199+
if c.authFilePath == "" {
200+
c.authFilePath = filepath.Join(configPath, "auth.json")
201+
}
191202
sys := &containersTypes.SystemContext{
192203
AuthFilePath: c.authFilePath,
193204
}

pkg/creds/credentials_test.go

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -642,11 +642,9 @@ func TestCredentialsWithoutHome(t *testing.T) {
642642
)
643643

644644
got, err := credentialsProvider(context.Background(), tt.args.registry+"/someorg/someimage:sometag")
645-
646645
// ASSERT
647646
if err != nil {
648-
t.Errorf("%v", err)
649-
return
647+
t.Fatalf("unexpected error: %v", err)
650648
}
651649
if !reflect.DeepEqual(got, tt.want) {
652650
t.Errorf("got: %v, want: %v", got, tt.want)
@@ -803,8 +801,7 @@ func TestCredentialsHomePermissions(t *testing.T) {
803801

804802
got, err := credentialsProvider(context.Background(), tt.args.registry+"/someorg/someimage:sometag")
805803
if err != nil {
806-
t.Errorf("%v", err)
807-
return
804+
t.Fatalf("unexpected error: %v", err)
808805
}
809806
if !reflect.DeepEqual(got, tt.want) {
810807
t.Errorf("got: %v, want: %v", got, tt.want)
@@ -815,10 +812,85 @@ func TestCredentialsHomePermissions(t *testing.T) {
815812
}
816813
}
817814

815+
func TestCredentialsFromAuthfile(t *testing.T) {
816+
tests := []struct {
817+
name string
818+
verifyCredentials creds.VerifyCredentialsCallback
819+
authFileContent string
820+
image string
821+
want Credentials
822+
}{
823+
{
824+
name: "Single registry auth file",
825+
verifyCredentials: correctVerifyCbk,
826+
authFileContent: fmt.Sprintf(`
827+
{
828+
"auths": {
829+
"docker.io": {
830+
"auth": "%s"
831+
}
832+
}
833+
}
834+
`, base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", dockerIoUser, dockerIoUserPwd)))),
835+
image: "docker.io/someorg/someimage:sometag",
836+
want: Credentials{Username: dockerIoUser, Password: dockerIoUserPwd},
837+
},
838+
{
839+
name: "Auth file with multiple registries",
840+
verifyCredentials: correctVerifyCbk,
841+
authFileContent: fmt.Sprintf(`
842+
{
843+
"auths": {
844+
"docker.io": {
845+
"auth": "%s"
846+
},
847+
"quay.io": {
848+
"auth": "%s"
849+
}
850+
}
851+
}
852+
`, base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", dockerIoUser, dockerIoUserPwd))),
853+
base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", quayIoUser, quayIoUserPwd)))),
854+
image: "quay.io/someorg/someimage:sometag",
855+
want: Credentials{Username: quayIoUser, Password: quayIoUserPwd},
856+
},
857+
}
858+
859+
// reset HOME to the original value after tests since they may change it
860+
defer func() {
861+
os.Setenv("HOME", homeTempDir)
862+
}()
863+
864+
for _, tt := range tests {
865+
t.Run(tt.name, func(t *testing.T) {
866+
resetHomeDir(t)
867+
868+
authFile := fmt.Sprintf("%s/authfile.json", t.TempDir())
869+
if err := os.WriteFile(authFile, []byte(tt.authFileContent), 06444); err != nil {
870+
t.Fatalf("failed to write auth file: %s", err)
871+
}
872+
873+
credentialsProvider := creds.NewCredentialsProvider(
874+
testConfigPath(t),
875+
creds.WithVerifyCredentials(tt.verifyCredentials),
876+
creds.WithAuthFilePath(authFile),
877+
)
878+
879+
got, err := credentialsProvider(context.Background(), tt.image)
880+
if err != nil {
881+
t.Fatalf("unexpected error: %v", err)
882+
}
883+
if !reflect.DeepEqual(got, tt.want) {
884+
t.Errorf("got: %v, want: %v", got, tt.want)
885+
}
886+
})
887+
}
888+
}
889+
818890
// ********************** helper functions below **************************** \\
819891

820892
func resetHomeDir(t *testing.T) {
821-
t.TempDir()
893+
t.Helper()
822894
if err := os.RemoveAll(homeTempDir); err != nil {
823895
t.Fatal(err)
824896
}
@@ -829,6 +901,7 @@ func resetHomeDir(t *testing.T) {
829901

830902
// resetHomePermissions resets the HOME perms to 0700 (same as resetHomeDir(t))
831903
func resetHomePermissions(t *testing.T) {
904+
t.Helper()
832905
if err := os.Chmod(homeTempDir, 0700); err != nil {
833906
t.Fatal(err)
834907
}

0 commit comments

Comments
 (0)