parent
129df249c9
commit
633f555c5c
|
|
@ -19,13 +19,11 @@ package cmd
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/cache"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/logging"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -65,20 +63,6 @@ var RootCmd = &cobra.Command{
|
|||
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 {
|
||||
exit(errors.Wrap(err, "Failed warming cache"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,20 +17,33 @@ limitations under the License.
|
|||
package creds
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/v1/google"
|
||||
)
|
||||
|
||||
var (
|
||||
setupKeyChainOnce sync.Once
|
||||
keyChain authn.Keychain
|
||||
setupKeychainOnce sync.Once
|
||||
keychain authn.Keychain
|
||||
)
|
||||
|
||||
// GetKeychain returns a keychain for accessing container registries.
|
||||
func GetKeychain() authn.Keychain {
|
||||
setupKeyChainOnce.Do(func() {
|
||||
keyChain = authn.NewMultiKeychain(authn.DefaultKeychain)
|
||||
setupKeychainOnce.Do(func() {
|
||||
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/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/authn/k8schain"
|
||||
"github.com/google/go-containerregistry/pkg/v1/google"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
setupKeyChainOnce sync.Once
|
||||
keyChain authn.Keychain
|
||||
setupKeychainOnce sync.Once
|
||||
keychain authn.Keychain
|
||||
)
|
||||
|
||||
// GetKeychain returns a keychain for accessing container registries.
|
||||
func GetKeychain() authn.Keychain {
|
||||
setupKeyChainOnce.Do(func() {
|
||||
keyChain = authn.NewMultiKeychain(authn.DefaultKeychain)
|
||||
setupKeychainOnce.Do(func() {
|
||||
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
|
||||
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)
|
||||
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()
|
||||
// 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) {
|
||||
newReg, err := name.NewRegistry(registryName, name.WeakValidation, name.Insecure)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -17,15 +17,8 @@ limitations under the License.
|
|||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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