parent
129df249c9
commit
633f555c5c
|
|
@ -19,13 +19,11 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/cache"
|
"github.com/GoogleContainerTools/kaniko/pkg/cache"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/logging"
|
"github.com/GoogleContainerTools/kaniko/pkg/logging"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
@ -65,20 +63,6 @@ var RootCmd = &cobra.Command{
|
||||||
exit(errors.Wrap(err, "Failed to create cache directory"))
|
exit(errors.Wrap(err, "Failed to create cache directory"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isGCR := false
|
|
||||||
for _, image := range opts.Images {
|
|
||||||
if strings.Contains(image, "gcr.io") || strings.Contains(image, ".pkg.dev") {
|
|
||||||
isGCR = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Historically kaniko was pre-configured by default with gcr credential helper,
|
|
||||||
// in here we keep the backwards compatibility by enabling the GCR helper only
|
|
||||||
// when gcr.io (or pkg.dev) is in one of the destinations.
|
|
||||||
if isGCR {
|
|
||||||
util.ConfigureGCR("")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cache.WarmCache(opts); err != nil {
|
if err := cache.WarmCache(opts); err != nil {
|
||||||
exit(errors.Wrap(err, "Failed warming cache"))
|
exit(errors.Wrap(err, "Failed warming cache"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,20 +17,33 @@ limitations under the License.
|
||||||
package creds
|
package creds
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/go-containerregistry/pkg/authn"
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/google"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
setupKeyChainOnce sync.Once
|
setupKeychainOnce sync.Once
|
||||||
keyChain authn.Keychain
|
keychain authn.Keychain
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetKeychain returns a keychain for accessing container registries.
|
// GetKeychain returns a keychain for accessing container registries.
|
||||||
func GetKeychain() authn.Keychain {
|
func GetKeychain() authn.Keychain {
|
||||||
setupKeyChainOnce.Do(func() {
|
setupKeychainOnce.Do(func() {
|
||||||
keyChain = authn.NewMultiKeychain(authn.DefaultKeychain)
|
keychain = authn.DefaultKeychain
|
||||||
|
|
||||||
|
// Historically kaniko was pre-configured by default with gcr
|
||||||
|
// credential helper, in here we keep the backwards
|
||||||
|
// compatibility by enabling the GCR helper only when gcr.io
|
||||||
|
// (or pkg.dev) is in one of the destinations.
|
||||||
|
gauth, err := google.NewEnvAuthenticator()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to setup Google env authenticator, ignoring: %v", err)
|
||||||
|
} else {
|
||||||
|
keychain = authn.NewMultiKeychain(authn.DefaultKeychain, gcrKeychain{gauth})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return keyChain
|
return keychain
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,30 @@ import (
|
||||||
"github.com/genuinetools/bpfd/proc"
|
"github.com/genuinetools/bpfd/proc"
|
||||||
"github.com/google/go-containerregistry/pkg/authn"
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
"github.com/google/go-containerregistry/pkg/authn/k8schain"
|
"github.com/google/go-containerregistry/pkg/authn/k8schain"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/google"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
setupKeyChainOnce sync.Once
|
setupKeychainOnce sync.Once
|
||||||
keyChain authn.Keychain
|
keychain authn.Keychain
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetKeychain returns a keychain for accessing container registries.
|
// GetKeychain returns a keychain for accessing container registries.
|
||||||
func GetKeychain() authn.Keychain {
|
func GetKeychain() authn.Keychain {
|
||||||
setupKeyChainOnce.Do(func() {
|
setupKeychainOnce.Do(func() {
|
||||||
keyChain = authn.NewMultiKeychain(authn.DefaultKeychain)
|
keychain = authn.DefaultKeychain
|
||||||
|
|
||||||
|
// Historically kaniko was pre-configured by default with gcr
|
||||||
|
// credential helper, in here we keep the backwards
|
||||||
|
// compatibility by enabling the GCR helper only when gcr.io
|
||||||
|
// (or pkg.dev) is in one of the destinations.
|
||||||
|
gauth, err := google.NewEnvAuthenticator()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("Failed to setup Google env authenticator, ignoring: %v", err)
|
||||||
|
} else {
|
||||||
|
keychain = authn.NewMultiKeychain(authn.DefaultKeychain, gcrKeychain{gauth})
|
||||||
|
}
|
||||||
|
|
||||||
// Add the Kubernetes keychain if we're on Kubernetes
|
// Add the Kubernetes keychain if we're on Kubernetes
|
||||||
if proc.GetContainerRuntime(0, 0) == proc.RuntimeKubernetes {
|
if proc.GetContainerRuntime(0, 0) == proc.RuntimeKubernetes {
|
||||||
|
|
@ -43,8 +55,8 @@ func GetKeychain() authn.Keychain {
|
||||||
logrus.Warnf("Error setting up k8schain. Using default keychain %s", err)
|
logrus.Warnf("Error setting up k8schain. Using default keychain %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
keyChain = authn.NewMultiKeychain(keyChain, k8sc)
|
keychain = authn.NewMultiKeychain(keychain, k8sc)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return keyChain
|
return keychain
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Google LLC
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package creds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gcrKeychain struct {
|
||||||
|
authr authn.Authenticator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gcrKeychain) Resolve(r authn.Resource) (authn.Authenticator, error) {
|
||||||
|
if r.RegistryStr() == "gcr.io" ||
|
||||||
|
strings.HasSuffix(r.RegistryStr(), ".gcr.io") ||
|
||||||
|
strings.HasSuffix(r.RegistryStr(), ".pkg.dev") {
|
||||||
|
|
||||||
|
return g.authr, nil
|
||||||
|
}
|
||||||
|
return authn.Anonymous, nil
|
||||||
|
}
|
||||||
|
|
@ -95,14 +95,6 @@ func CheckPushPermissions(opts *config.KanikoOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
registryName := destRef.Repository.Registry.Name()
|
registryName := destRef.Repository.Registry.Name()
|
||||||
// Historically kaniko was pre-configured by default with gcr credential helper,
|
|
||||||
// in here we keep the backwards compatibility by enabling the GCR helper only
|
|
||||||
// when gcr.io (or pkg.dev) is in one of the destinations.
|
|
||||||
if registryName == "gcr.io" || strings.HasSuffix(registryName, ".gcr.io") || strings.HasSuffix(registryName, ".pkg.dev") {
|
|
||||||
if err := util.ConfigureGCR(fmt.Sprintf("--registries=%s", registryName)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opts.Insecure || opts.InsecureRegistries.Contains(registryName) {
|
if opts.Insecure || opts.InsecureRegistries.Contains(registryName) {
|
||||||
newReg, err := name.NewRegistry(registryName, name.WeakValidation, name.Insecure)
|
newReg, err := name.NewRegistry(registryName, name.WeakValidation, name.Insecure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,8 @@ limitations under the License.
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/afero"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DockerConfLocation returns the file system location of the Docker
|
// DockerConfLocation returns the file system location of the Docker
|
||||||
|
|
@ -49,21 +42,3 @@ func DockerConfLocation() string {
|
||||||
}
|
}
|
||||||
return string(os.PathSeparator) + filepath.Join("kaniko", ".docker", configFile)
|
return string(os.PathSeparator) + filepath.Join("kaniko", ".docker", configFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConfigureGCR(flags string) error {
|
|
||||||
// Checking for existence of docker.config as it's normally required for
|
|
||||||
// authenticated registries and prevent overwriting user provided docker conf
|
|
||||||
_, err := afero.NewOsFs().Stat(DockerConfLocation())
|
|
||||||
dockerConfNotExists := os.IsNotExist(err)
|
|
||||||
if dockerConfNotExists {
|
|
||||||
cmd := exec.Command("docker-credential-gcr", "configure-docker", flags)
|
|
||||||
var out bytes.Buffer
|
|
||||||
cmd.Stderr = &out
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return errors.Wrap(err, fmt.Sprintf("error while configuring docker-credential-gcr helper: %s : %s", cmd.String(), out.String()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logrus.Warnf("\nSkip running docker-credential-gcr as user provided docker configuration exists at %s", DockerConfLocation())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
7
vendor/github.com/google/go-containerregistry/pkg/v1/google/README.md
generated
vendored
Normal file
7
vendor/github.com/google/go-containerregistry/pkg/v1/google/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# `google`
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/google)
|
||||||
|
|
||||||
|
The `google` package provides:
|
||||||
|
* Some google-specific authentication methods.
|
||||||
|
* Some [GCR](gcr.io)-specific listing methods.
|
||||||
180
vendor/github.com/google/go-containerregistry/pkg/v1/google/auth.go
generated
vendored
Normal file
180
vendor/github.com/google/go-containerregistry/pkg/v1/google/auth.go
generated
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
// Copyright 2018 Google LLC All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
|
"github.com/google/go-containerregistry/pkg/logs"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
googauth "golang.org/x/oauth2/google"
|
||||||
|
)
|
||||||
|
|
||||||
|
const cloudPlatformScope = "https://www.googleapis.com/auth/cloud-platform"
|
||||||
|
|
||||||
|
// GetGcloudCmd is exposed so we can test this.
|
||||||
|
var GetGcloudCmd = func() *exec.Cmd {
|
||||||
|
// This is odd, but basically what docker-credential-gcr does.
|
||||||
|
//
|
||||||
|
// config-helper is undocumented, but it's purportedly the only supported way
|
||||||
|
// of accessing tokens (`gcloud auth print-access-token` is discouraged).
|
||||||
|
//
|
||||||
|
// --force-auth-refresh means we are getting a token that is valid for about
|
||||||
|
// an hour (we reuse it until it's expired).
|
||||||
|
return exec.Command("gcloud", "config", "config-helper", "--force-auth-refresh", "--format=json(credential)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEnvAuthenticator returns an authn.Authenticator that generates access
|
||||||
|
// tokens from the environment we're running in.
|
||||||
|
//
|
||||||
|
// See: https://godoc.org/golang.org/x/oauth2/google#FindDefaultCredentials
|
||||||
|
func NewEnvAuthenticator() (authn.Authenticator, error) {
|
||||||
|
ts, err := googauth.DefaultTokenSource(context.Background(), cloudPlatformScope)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := ts.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tokenSourceAuth{oauth2.ReuseTokenSource(token, ts)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGcloudAuthenticator returns an oauth2.TokenSource that generates access
|
||||||
|
// tokens by shelling out to the gcloud sdk.
|
||||||
|
func NewGcloudAuthenticator() (authn.Authenticator, error) {
|
||||||
|
if _, err := exec.LookPath("gcloud"); err != nil {
|
||||||
|
// gcloud is not available, fall back to anonymous
|
||||||
|
logs.Warn.Println("gcloud binary not found")
|
||||||
|
return authn.Anonymous, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := gcloudSource{GetGcloudCmd()}
|
||||||
|
|
||||||
|
// Attempt to fetch a token to ensure gcloud is installed and we can run it.
|
||||||
|
token, err := ts.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tokenSourceAuth{oauth2.ReuseTokenSource(token, ts)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSONKeyAuthenticator returns a Basic authenticator which uses Service Account
|
||||||
|
// as a way of authenticating with Google Container Registry.
|
||||||
|
// More information: https://cloud.google.com/container-registry/docs/advanced-authentication#json_key_file
|
||||||
|
func NewJSONKeyAuthenticator(serviceAccountJSON string) authn.Authenticator {
|
||||||
|
return &authn.Basic{
|
||||||
|
Username: "_json_key",
|
||||||
|
Password: serviceAccountJSON,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTokenAuthenticator returns an oauth2.TokenSource that generates access
|
||||||
|
// tokens by using the Google SDK to produce JWT tokens from a Service Account.
|
||||||
|
// More information: https://godoc.org/golang.org/x/oauth2/google#JWTAccessTokenSourceFromJSON
|
||||||
|
func NewTokenAuthenticator(serviceAccountJSON string, scope string) (authn.Authenticator, error) {
|
||||||
|
ts, err := googauth.JWTAccessTokenSourceFromJSON([]byte(serviceAccountJSON), string(scope))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tokenSourceAuth{oauth2.ReuseTokenSource(nil, ts)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTokenSourceAuthenticator converts an oauth2.TokenSource into an authn.Authenticator.
|
||||||
|
func NewTokenSourceAuthenticator(ts oauth2.TokenSource) authn.Authenticator {
|
||||||
|
return &tokenSourceAuth{ts}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenSourceAuth turns an oauth2.TokenSource into an authn.Authenticator.
|
||||||
|
type tokenSourceAuth struct {
|
||||||
|
oauth2.TokenSource
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorization implements authn.Authenticator.
|
||||||
|
func (tsa *tokenSourceAuth) Authorization() (*authn.AuthConfig, error) {
|
||||||
|
token, err := tsa.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &authn.AuthConfig{
|
||||||
|
Username: "_token",
|
||||||
|
Password: token.AccessToken,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// gcloudOutput represents the output of the gcloud command we invoke.
|
||||||
|
//
|
||||||
|
// `gcloud config config-helper --format=json(credential)` looks something like:
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "credential": {
|
||||||
|
// "access_token": "ya29.abunchofnonsense",
|
||||||
|
// "token_expiry": "2018-12-02T04:08:13Z"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
type gcloudOutput struct {
|
||||||
|
Credential struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenExpiry string `json:"token_expiry"`
|
||||||
|
} `json:"credential"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type gcloudSource struct {
|
||||||
|
// This is passed in so that we mock out gcloud and test Token.
|
||||||
|
cmd *exec.Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token implements oauath2.TokenSource.
|
||||||
|
func (gs gcloudSource) Token() (*oauth2.Token, error) {
|
||||||
|
cmd := gs.cmd
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
|
||||||
|
// Don't attempt to interpret stderr, just pass it through.
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("error executing `gcloud config config-helper`: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
creds := gcloudOutput{}
|
||||||
|
if err := json.Unmarshal(out.Bytes(), &creds); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse `gcloud config config-helper` output: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expiry, err := time.Parse(time.RFC3339, creds.Credential.TokenExpiry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse gcloud token expiry: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := oauth2.Token{
|
||||||
|
AccessToken: creds.Credential.AccessToken,
|
||||||
|
Expiry: expiry,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &token, nil
|
||||||
|
}
|
||||||
16
vendor/github.com/google/go-containerregistry/pkg/v1/google/doc.go
generated
vendored
Normal file
16
vendor/github.com/google/go-containerregistry/pkg/v1/google/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright 2018 Google LLC All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package google provides facilities for listing images in gcr.io.
|
||||||
|
package google
|
||||||
81
vendor/github.com/google/go-containerregistry/pkg/v1/google/keychain.go
generated
vendored
Normal file
81
vendor/github.com/google/go-containerregistry/pkg/v1/google/keychain.go
generated
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright 2018 Google LLC All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keychain exports an instance of the google Keychain.
|
||||||
|
var Keychain authn.Keychain = &googleKeychain{}
|
||||||
|
|
||||||
|
type googleKeychain struct {
|
||||||
|
once sync.Once
|
||||||
|
auth authn.Authenticator
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve implements authn.Keychain a la docker-credential-gcr.
|
||||||
|
//
|
||||||
|
// This behaves similarly to the GCR credential helper, but reuses tokens until
|
||||||
|
// they expire.
|
||||||
|
//
|
||||||
|
// We can't easily add this behavior to our credential helper implementation
|
||||||
|
// of authn.Authenticator because the credential helper protocol doesn't include
|
||||||
|
// expiration information, see here:
|
||||||
|
// https://godoc.org/github.com/docker/docker-credential-helpers/credentials#Credentials
|
||||||
|
//
|
||||||
|
// In addition to being a performance optimization, the reuse of these access
|
||||||
|
// tokens works around a bug in gcloud. It appears that attempting to invoke
|
||||||
|
// `gcloud config config-helper` multiple times too quickly will fail:
|
||||||
|
// https://github.com/GoogleCloudPlatform/docker-credential-gcr/issues/54
|
||||||
|
//
|
||||||
|
// We could upstream this behavior into docker-credential-gcr by parsing
|
||||||
|
// gcloud's output and persisting its tokens across invocations, but then
|
||||||
|
// we have to deal with invalidating caches across multiple runs (no fun).
|
||||||
|
//
|
||||||
|
// In general, we don't worry about that here because we expect to use the same
|
||||||
|
// gcloud configuration in the scope of this one process.
|
||||||
|
func (gk *googleKeychain) Resolve(target authn.Resource) (authn.Authenticator, error) {
|
||||||
|
// Only authenticate GCR and AR so it works with authn.NewMultiKeychain to fallback.
|
||||||
|
host := target.RegistryStr()
|
||||||
|
if host != "gcr.io" && !strings.HasSuffix(host, ".gcr.io") && !strings.HasSuffix(host, ".pkg.dev") {
|
||||||
|
return authn.Anonymous, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gk.once.Do(func() {
|
||||||
|
gk.auth, gk.err = resolve()
|
||||||
|
})
|
||||||
|
|
||||||
|
return gk.auth, gk.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolve() (authn.Authenticator, error) {
|
||||||
|
auth, envErr := NewEnvAuthenticator()
|
||||||
|
if envErr == nil {
|
||||||
|
return auth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, gErr := NewGcloudAuthenticator()
|
||||||
|
if gErr == nil {
|
||||||
|
return auth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("failed to create token source from env: %v or gcloud: %v", envErr, gErr)
|
||||||
|
}
|
||||||
259
vendor/github.com/google/go-containerregistry/pkg/v1/google/list.go
generated
vendored
Normal file
259
vendor/github.com/google/go-containerregistry/pkg/v1/google/list.go
generated
vendored
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
// Copyright 2018 Google LLC All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
|
"github.com/google/go-containerregistry/pkg/logs"
|
||||||
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListerOption is a functional option for List and Walk.
|
||||||
|
// TODO: Can we somehow reuse the remote options here?
|
||||||
|
type ListerOption func(*lister) error
|
||||||
|
|
||||||
|
type lister struct {
|
||||||
|
auth authn.Authenticator
|
||||||
|
transport http.RoundTripper
|
||||||
|
repo name.Repository
|
||||||
|
client *http.Client
|
||||||
|
ctx context.Context
|
||||||
|
userAgent string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLister(repo name.Repository, options ...ListerOption) (*lister, error) {
|
||||||
|
l := &lister{
|
||||||
|
auth: authn.Anonymous,
|
||||||
|
transport: http.DefaultTransport,
|
||||||
|
repo: repo,
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
if err := option(l); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the transport in something that logs requests and responses.
|
||||||
|
// It's expensive to generate the dumps, so skip it if we're writing
|
||||||
|
// to nothing.
|
||||||
|
if logs.Enabled(logs.Debug) {
|
||||||
|
l.transport = transport.NewLogger(l.transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the transport in something that can retry network flakes.
|
||||||
|
l.transport = transport.NewRetry(l.transport)
|
||||||
|
|
||||||
|
// Wrap this last to prevent transport.New from double-wrapping.
|
||||||
|
if l.userAgent != "" {
|
||||||
|
l.transport = transport.NewUserAgent(l.transport, l.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
scopes := []string{repo.Scope(transport.PullScope)}
|
||||||
|
tr, err := transport.NewWithContext(l.ctx, repo.Registry, l.auth, l.transport, scopes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.client = &http.Client{Transport: tr}
|
||||||
|
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lister) list(repo name.Repository) (*Tags, error) {
|
||||||
|
uri := url.URL{
|
||||||
|
Scheme: repo.Registry.Scheme(),
|
||||||
|
Host: repo.Registry.RegistryStr(),
|
||||||
|
Path: fmt.Sprintf("/v2/%s/tags/list", repo.RepositoryStr()),
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(l.ctx, http.MethodGet, uri.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := l.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if err := transport.CheckError(resp, http.StatusOK); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := Tags{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&tags); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawManifestInfo struct {
|
||||||
|
Size string `json:"imageSizeBytes"`
|
||||||
|
MediaType string `json:"mediaType"`
|
||||||
|
Created string `json:"timeCreatedMs"`
|
||||||
|
Uploaded string `json:"timeUploadedMs"`
|
||||||
|
Tags []string `json:"tag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManifestInfo is a Manifests entry is the output of List and Walk.
|
||||||
|
type ManifestInfo struct {
|
||||||
|
Size uint64 `json:"imageSizeBytes"`
|
||||||
|
MediaType string `json:"mediaType"`
|
||||||
|
Created time.Time `json:"timeCreatedMs"`
|
||||||
|
Uploaded time.Time `json:"timeUploadedMs"`
|
||||||
|
Tags []string `json:"tag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromUnixMs(ms int64) time.Time {
|
||||||
|
sec := ms / 1000
|
||||||
|
ns := (ms % 1000) * 1000000
|
||||||
|
return time.Unix(sec, ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUnixMs(t time.Time) string {
|
||||||
|
return strconv.FormatInt(t.UnixNano()/1000000, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler
|
||||||
|
func (m ManifestInfo) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(rawManifestInfo{
|
||||||
|
Size: strconv.FormatUint(m.Size, 10),
|
||||||
|
MediaType: m.MediaType,
|
||||||
|
Created: toUnixMs(m.Created),
|
||||||
|
Uploaded: toUnixMs(m.Uploaded),
|
||||||
|
Tags: m.Tags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler
|
||||||
|
func (m *ManifestInfo) UnmarshalJSON(data []byte) error {
|
||||||
|
raw := rawManifestInfo{}
|
||||||
|
if err := json.Unmarshal(data, &raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw.Size != "" {
|
||||||
|
size, err := strconv.ParseUint(string(raw.Size), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Size = size
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw.Created != "" {
|
||||||
|
created, err := strconv.ParseInt(string(raw.Created), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Created = fromUnixMs(created)
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw.Uploaded != "" {
|
||||||
|
uploaded, err := strconv.ParseInt(string(raw.Uploaded), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Uploaded = fromUnixMs(uploaded)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.MediaType = raw.MediaType
|
||||||
|
m.Tags = raw.Tags
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags is the result of List and Walk.
|
||||||
|
type Tags struct {
|
||||||
|
Children []string `json:"child"`
|
||||||
|
Manifests map[string]ManifestInfo `json:"manifest"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// List calls /tags/list for the given repository.
|
||||||
|
func List(repo name.Repository, options ...ListerOption) (*Tags, error) {
|
||||||
|
l, err := newLister(repo, options...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.list(repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalkFunc is the type of the function called for each repository visited by
|
||||||
|
// Walk. This implements a similar API to filepath.Walk.
|
||||||
|
//
|
||||||
|
// The repo argument contains the argument to Walk as a prefix; that is, if Walk
|
||||||
|
// is called with "gcr.io/foo", which is a repository containing the repository
|
||||||
|
// "bar", the walk function will be called with argument "gcr.io/foo/bar".
|
||||||
|
// The tags and error arguments are the result of calling List on repo.
|
||||||
|
//
|
||||||
|
// TODO: Do we want a SkipDir error, as in filepath.WalkFunc?
|
||||||
|
type WalkFunc func(repo name.Repository, tags *Tags, err error) error
|
||||||
|
|
||||||
|
func walk(repo name.Repository, tags *Tags, walkFn WalkFunc, options ...ListerOption) error {
|
||||||
|
if tags == nil {
|
||||||
|
// This shouldn't happen.
|
||||||
|
return fmt.Errorf("tags nil for %q", repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := walkFn(repo, tags, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range tags.Children {
|
||||||
|
child, err := name.NewRepository(fmt.Sprintf("%s/%s", repo, path), name.StrictValidation)
|
||||||
|
if err != nil {
|
||||||
|
// We don't expect this ever, so don't pass it through to walkFn.
|
||||||
|
return fmt.Errorf("unexpected path failure: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
childTags, err := List(child, options...)
|
||||||
|
if err != nil {
|
||||||
|
if err := walkFn(child, nil, err); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := walk(child, childTags, walkFn, options...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We made it!
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk recursively descends repositories, calling walkFn.
|
||||||
|
func Walk(root name.Repository, walkFn WalkFunc, options ...ListerOption) error {
|
||||||
|
tags, err := List(root, options...)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(root, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return walk(root, tags, walkFn, options...)
|
||||||
|
}
|
||||||
77
vendor/github.com/google/go-containerregistry/pkg/v1/google/options.go
generated
vendored
Normal file
77
vendor/github.com/google/go-containerregistry/pkg/v1/google/options.go
generated
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright 2018 Google LLC All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
|
"github.com/google/go-containerregistry/pkg/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithTransport is a functional option for overriding the default transport
|
||||||
|
// on a remote image
|
||||||
|
func WithTransport(t http.RoundTripper) ListerOption {
|
||||||
|
return func(l *lister) error {
|
||||||
|
l.transport = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAuth is a functional option for overriding the default authenticator
|
||||||
|
// on a remote image
|
||||||
|
func WithAuth(auth authn.Authenticator) ListerOption {
|
||||||
|
return func(l *lister) error {
|
||||||
|
l.auth = auth
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAuthFromKeychain is a functional option for overriding the default
|
||||||
|
// authenticator on a remote image using an authn.Keychain
|
||||||
|
func WithAuthFromKeychain(keys authn.Keychain) ListerOption {
|
||||||
|
return func(l *lister) error {
|
||||||
|
auth, err := keys.Resolve(l.repo.Registry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if auth == authn.Anonymous {
|
||||||
|
logs.Warn.Printf("No matching credentials were found for %q, falling back on anonymous", l.repo.Registry)
|
||||||
|
}
|
||||||
|
l.auth = auth
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContext is a functional option for overriding the default
|
||||||
|
// context.Context for HTTP request to list remote images
|
||||||
|
func WithContext(ctx context.Context) ListerOption {
|
||||||
|
return func(l *lister) error {
|
||||||
|
l.ctx = ctx
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUserAgent adds the given string to the User-Agent header for any HTTP
|
||||||
|
// requests. This header will also include "go-containerregistry/${version}".
|
||||||
|
//
|
||||||
|
// If you want to completely overwrite the User-Agent header, use WithTransport.
|
||||||
|
func WithUserAgent(ua string) ListerOption {
|
||||||
|
return func(l *lister) error {
|
||||||
|
l.userAgent = ua
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue