2022-01-17 04:06:44 +00:00
|
|
|
package registryclient
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-05-25 11:03:36 +02:00
|
|
|
"crypto/tls"
|
2022-05-23 15:15:25 +02:00
|
|
|
"fmt"
|
2022-09-30 15:25:19 +08:00
|
|
|
"io"
|
2022-05-25 11:03:36 +02:00
|
|
|
"net/http"
|
2022-04-22 00:10:02 -07:00
|
|
|
|
2022-01-28 19:33:27 +00:00
|
|
|
ecr "github.com/awslabs/amazon-ecr-credential-helper/ecr-login"
|
|
|
|
"github.com/chrismellard/docker-credential-acr-env/pkg/credhelper"
|
2022-01-17 04:06:44 +00:00
|
|
|
"github.com/google/go-containerregistry/pkg/authn"
|
2022-05-17 07:56:48 +02:00
|
|
|
"github.com/google/go-containerregistry/pkg/authn/github"
|
2022-05-23 15:15:25 +02:00
|
|
|
"github.com/google/go-containerregistry/pkg/name"
|
2022-01-28 19:33:27 +00:00
|
|
|
"github.com/google/go-containerregistry/pkg/v1/google"
|
2022-05-25 11:03:36 +02:00
|
|
|
gcrremote "github.com/google/go-containerregistry/pkg/v1/remote"
|
|
|
|
"github.com/sigstore/cosign/pkg/oci/remote"
|
2022-01-17 04:06:44 +00:00
|
|
|
"k8s.io/client-go/kubernetes"
|
|
|
|
)
|
|
|
|
|
2022-12-07 16:08:37 +01:00
|
|
|
var baseKeychain = authn.NewMultiKeychain(
|
|
|
|
authn.DefaultKeychain,
|
|
|
|
google.Keychain,
|
|
|
|
authn.NewKeychainFromHelper(ecr.NewECRHelper(ecr.WithLogger(io.Discard))),
|
|
|
|
authn.NewKeychainFromHelper(credhelper.NewACRCredentialsHelper()),
|
|
|
|
github.Keychain,
|
|
|
|
)
|
2022-05-25 11:03:36 +02:00
|
|
|
|
|
|
|
// Client provides registry related objects.
|
|
|
|
type Client interface {
|
2022-12-07 16:08:37 +01:00
|
|
|
// getKeychain provides keychain object.
|
|
|
|
getKeychain() authn.Keychain
|
2022-05-25 11:03:36 +02:00
|
|
|
|
2022-12-07 16:08:37 +01:00
|
|
|
// getTransport provides transport object.
|
|
|
|
getTransport() http.RoundTripper
|
2022-05-25 11:03:36 +02:00
|
|
|
|
|
|
|
// FetchImageDescriptor fetches Descriptor from registry with given imageRef
|
|
|
|
// and provides access to metadata about remote artifact.
|
2022-12-07 16:08:37 +01:00
|
|
|
FetchImageDescriptor(context.Context, string) (*gcrremote.Descriptor, error)
|
2022-05-25 11:03:36 +02:00
|
|
|
|
2022-12-07 16:08:37 +01:00
|
|
|
// // RefreshKeychainPullSecrets loads fresh data from pull secrets and updates Keychain.
|
|
|
|
// // If pull secrets are empty - returns.
|
|
|
|
RefreshKeychainPullSecrets(context.Context) error
|
2022-05-25 11:03:36 +02:00
|
|
|
|
2022-12-07 16:08:37 +01:00
|
|
|
// BuildRemoteOption builds remote.Option based on client.
|
|
|
|
BuildRemoteOption() remote.Option
|
2022-05-25 11:03:36 +02:00
|
|
|
}
|
|
|
|
|
2022-12-07 16:08:37 +01:00
|
|
|
type client struct {
|
|
|
|
keychain authn.Keychain
|
|
|
|
transport *http.Transport
|
|
|
|
pullSecretRefresher func(context.Context, *client) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Option is an option to initialize registry client.
|
|
|
|
type Option = func(*client) error
|
|
|
|
|
|
|
|
// New creates a new Client with options
|
|
|
|
func New(options ...Option) (Client, error) {
|
2022-05-25 11:03:36 +02:00
|
|
|
c := &client{
|
2022-12-07 16:08:37 +01:00
|
|
|
keychain: baseKeychain,
|
|
|
|
transport: gcrremote.DefaultTransport.(*http.Transport),
|
2022-05-25 11:03:36 +02:00
|
|
|
}
|
|
|
|
for _, opt := range options {
|
|
|
|
if err := opt(c); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return c, nil
|
2022-05-23 15:15:25 +02:00
|
|
|
}
|
2022-01-17 04:06:44 +00:00
|
|
|
|
2022-12-07 16:08:37 +01:00
|
|
|
// New creates a new Client with options
|
|
|
|
func NewOrDie(options ...Option) Client {
|
|
|
|
c, err := New(options...)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return c
|
|
|
|
}
|
2022-05-25 11:03:36 +02:00
|
|
|
|
|
|
|
// WithKeychainPullSecrets provides initialize registry client option that allows to use pull secrets.
|
2022-12-07 16:08:37 +01:00
|
|
|
func WithKeychainPullSecrets(ctx context.Context, kubClient kubernetes.Interface, namespace, serviceAccount string, imagePullSecrets ...string) Option {
|
2022-05-25 11:03:36 +02:00
|
|
|
return func(c *client) error {
|
2022-12-07 16:08:37 +01:00
|
|
|
c.pullSecretRefresher = func(ctx context.Context, c *client) error {
|
|
|
|
freshKeychain, err := generateKeychainForPullSecrets(ctx, kubClient, namespace, serviceAccount, imagePullSecrets...)
|
2022-05-25 11:03:36 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.keychain = authn.NewMultiKeychain(
|
2022-12-07 16:08:37 +01:00
|
|
|
baseKeychain,
|
2022-05-25 11:03:36 +02:00
|
|
|
freshKeychain,
|
|
|
|
)
|
|
|
|
return nil
|
|
|
|
}
|
2022-12-07 16:08:37 +01:00
|
|
|
return c.pullSecretRefresher(ctx, c)
|
2022-01-17 04:06:44 +00:00
|
|
|
}
|
2022-05-25 11:03:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// WithKeychainPullSecrets provides initialize registry client option that allows to use insecure registries.
|
|
|
|
func WithAllowInsecureRegistry() Option {
|
|
|
|
return func(c *client) error {
|
2022-08-24 15:08:24 +02:00
|
|
|
c.transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec
|
2022-05-25 11:03:36 +02:00
|
|
|
return nil
|
2022-01-17 04:06:44 +00:00
|
|
|
}
|
2022-05-23 15:15:25 +02:00
|
|
|
}
|
2022-01-17 04:06:44 +00:00
|
|
|
|
2022-12-07 16:08:37 +01:00
|
|
|
// WithLocalKeychain provides initialize keychain with the default local keychain.
|
|
|
|
func WithLocalKeychain() Option {
|
|
|
|
return func(c *client) error {
|
|
|
|
c.pullSecretRefresher = nil
|
|
|
|
c.keychain = authn.DefaultKeychain
|
|
|
|
return nil
|
|
|
|
}
|
2022-05-25 11:03:36 +02:00
|
|
|
}
|
|
|
|
|
2022-12-07 16:08:37 +01:00
|
|
|
// RefreshKeychainPullSecrets loads fresh data from pull secrets and updates Keychain.
|
|
|
|
// If pull secrets are empty - returns.
|
|
|
|
func (c *client) RefreshKeychainPullSecrets(ctx context.Context) error {
|
|
|
|
if c.pullSecretRefresher == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return c.pullSecretRefresher(ctx, c)
|
2022-05-25 11:03:36 +02:00
|
|
|
}
|
|
|
|
|
2022-12-07 16:08:37 +01:00
|
|
|
// BuildRemoteOption builds remote.Option based on client.
|
|
|
|
func (c *client) BuildRemoteOption() remote.Option {
|
|
|
|
return remote.WithRemoteOptions(
|
|
|
|
gcrremote.WithAuthFromKeychain(c.keychain),
|
|
|
|
gcrremote.WithTransport(c.transport),
|
|
|
|
)
|
2022-05-25 11:03:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// FetchImageDescriptor fetches Descriptor from registry with given imageRef
|
|
|
|
// and provides access to metadata about remote artifact.
|
2022-12-07 16:08:37 +01:00
|
|
|
func (c *client) FetchImageDescriptor(ctx context.Context, imageRef string) (*gcrremote.Descriptor, error) {
|
2022-05-25 11:03:36 +02:00
|
|
|
parsedRef, err := name.ParseReference(imageRef)
|
2022-05-23 15:15:25 +02:00
|
|
|
if err != nil {
|
2022-05-25 11:03:36 +02:00
|
|
|
return nil, fmt.Errorf("failed to parse image reference: %s, error: %v", imageRef, err)
|
2022-05-23 15:15:25 +02:00
|
|
|
}
|
2022-12-07 16:08:37 +01:00
|
|
|
desc, err := gcrremote.Get(parsedRef, gcrremote.WithAuthFromKeychain(c.keychain), gcrremote.WithContext(ctx))
|
2022-05-23 15:15:25 +02:00
|
|
|
if err != nil {
|
2022-05-25 11:03:36 +02:00
|
|
|
return nil, fmt.Errorf("failed to fetch image reference: %s, error: %v", imageRef, err)
|
2022-05-23 15:15:25 +02:00
|
|
|
}
|
2022-05-25 11:03:36 +02:00
|
|
|
return desc, nil
|
|
|
|
}
|
|
|
|
|
2022-12-07 16:08:37 +01:00
|
|
|
func (c *client) getKeychain() authn.Keychain {
|
|
|
|
return c.keychain
|
2022-05-25 11:03:36 +02:00
|
|
|
}
|
|
|
|
|
2022-12-07 16:08:37 +01:00
|
|
|
func (c *client) getTransport() http.RoundTripper {
|
|
|
|
return c.transport
|
2022-01-17 04:06:44 +00:00
|
|
|
}
|