Skip to content

Commit dbea944

Browse files
committed
Add --registry-authfile to specify a specific auth file for the registry push
1 parent 51e2b5d commit dbea944

5 files changed

Lines changed: 154 additions & 35 deletions

File tree

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
}

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 docker-config 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

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: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,91 @@ func TestCredentialsHomePermissions(t *testing.T) {
815815
}
816816
}
817817

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

820905
func resetHomeDir(t *testing.T) {

0 commit comments

Comments
 (0)