mirror of https://github.com/knative/func.git
oci: refactors pusher auth
Signed-off-by: kapil <kapilsareen584@gmail.com>
This commit is contained in:
parent
61ecd623a2
commit
b52ddfb7f2
|
@ -394,9 +394,14 @@ func (c buildConfig) clientOptions() ([]fn.Option, error) {
|
|||
o := []fn.Option{fn.WithRegistry(c.Registry)}
|
||||
switch c.Builder {
|
||||
case builders.Host:
|
||||
t := newTransport(c.RegistryInsecure) // may provide a custom impl which proxies
|
||||
creds := newCredentialsProvider(config.Dir(), t)
|
||||
o = append(o,
|
||||
fn.WithBuilder(oci.NewBuilder(builders.Host, c.Verbose)),
|
||||
fn.WithPusher(oci.NewPusher(c.RegistryInsecure, false, c.Verbose)))
|
||||
fn.WithPusher(oci.NewPusher(c.RegistryInsecure, false, c.Verbose,
|
||||
oci.WithCredentialsProvider(creds),
|
||||
oci.WithVerbose(c.Verbose))),
|
||||
)
|
||||
case builders.Pack:
|
||||
o = append(o,
|
||||
fn.WithBuilder(pack.NewBuilder(
|
||||
|
|
|
@ -8,12 +8,13 @@ import (
|
|||
"knative.dev/func/cmd/prompt"
|
||||
"knative.dev/func/pkg/builders/buildpacks"
|
||||
"knative.dev/func/pkg/config"
|
||||
"knative.dev/func/pkg/creds"
|
||||
"knative.dev/func/pkg/docker"
|
||||
"knative.dev/func/pkg/docker/creds"
|
||||
fn "knative.dev/func/pkg/functions"
|
||||
fnhttp "knative.dev/func/pkg/http"
|
||||
"knative.dev/func/pkg/k8s"
|
||||
"knative.dev/func/pkg/knative"
|
||||
"knative.dev/func/pkg/oci"
|
||||
"knative.dev/func/pkg/pipelines/tekton"
|
||||
)
|
||||
|
||||
|
@ -100,19 +101,22 @@ func newTransport(insecureSkipVerify bool) fnhttp.RoundTripCloser {
|
|||
// newCredentialsProvider returns a credentials provider which possibly
|
||||
// has cluster-flavor specific additional credential loaders to take advantage
|
||||
// of features or configuration nuances of cluster variants.
|
||||
func newCredentialsProvider(configPath string, t http.RoundTripper) docker.CredentialsProvider {
|
||||
func newCredentialsProvider(configPath string, t http.RoundTripper) oci.CredentialsProvider {
|
||||
additionalLoaders := append(k8s.GetOpenShiftDockerCredentialLoaders(), k8s.GetGoogleCredentialLoader()...)
|
||||
additionalLoaders = append(additionalLoaders, k8s.GetECRCredentialLoader()...)
|
||||
additionalLoaders = append(additionalLoaders, k8s.GetACRCredentialLoader()...)
|
||||
options := []creds.Opt{
|
||||
creds.WithPromptForCredentials(prompt.NewPromptForCredentials(os.Stdin, os.Stdout, os.Stderr)),
|
||||
creds.WithPromptForCredentialStore(prompt.NewPromptForCredentialStore()),
|
||||
creds.WithTransport(t),
|
||||
creds.WithAdditionalCredentialLoaders(k8s.GetOpenShiftDockerCredentialLoaders()...),
|
||||
creds.WithAdditionalCredentialLoaders(additionalLoaders...),
|
||||
}
|
||||
|
||||
// Other cluster variants can be supported here
|
||||
return creds.NewCredentialsProvider(configPath, options...)
|
||||
}
|
||||
|
||||
func newTektonPipelinesProvider(creds docker.CredentialsProvider, verbose bool) *tekton.PipelinesProvider {
|
||||
func newTektonPipelinesProvider(creds oci.CredentialsProvider, verbose bool) *tekton.PipelinesProvider {
|
||||
options := []tekton.Opt{
|
||||
tekton.WithCredentialsProvider(creds),
|
||||
tekton.WithVerbose(verbose),
|
||||
|
|
|
@ -11,14 +11,14 @@ import (
|
|||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
"golang.org/x/term"
|
||||
|
||||
"knative.dev/func/pkg/docker"
|
||||
"knative.dev/func/pkg/docker/creds"
|
||||
"knative.dev/func/pkg/creds"
|
||||
"knative.dev/func/pkg/oci"
|
||||
)
|
||||
|
||||
func NewPromptForCredentials(in io.Reader, out, errOut io.Writer) func(repository string) (docker.Credentials, error) {
|
||||
func NewPromptForCredentials(in io.Reader, out, errOut io.Writer) func(repository string) (oci.Credentials, error) {
|
||||
firstTime := true
|
||||
return func(repository string) (docker.Credentials, error) {
|
||||
var result docker.Credentials
|
||||
return func(repository string) (oci.Credentials, error) {
|
||||
var result oci.Credentials
|
||||
if firstTime {
|
||||
firstTime = false
|
||||
fmt.Fprintf(out, "Please provide credentials for image repository '%s'.\n", repository)
|
||||
|
@ -56,7 +56,7 @@ func NewPromptForCredentials(in io.Reader, out, errOut io.Writer) func(repositor
|
|||
if isTerm {
|
||||
err := survey.Ask(qs, &result, survey.WithStdio(fr, out.(terminal.FileWriter), errOut))
|
||||
if err != nil {
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
} else {
|
||||
reader := bufio.NewReader(in)
|
||||
|
@ -64,18 +64,18 @@ func NewPromptForCredentials(in io.Reader, out, errOut io.Writer) func(repositor
|
|||
fmt.Fprintf(out, "Username: ")
|
||||
u, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
u = strings.Trim(u, "\r\n")
|
||||
|
||||
fmt.Fprintf(out, "Password: ")
|
||||
p, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
p = strings.Trim(p, "\r\n")
|
||||
|
||||
result = docker.Credentials{Username: u, Password: p}
|
||||
result = oci.Credentials{Username: u, Password: p}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
|
|
@ -12,8 +12,7 @@ import (
|
|||
"github.com/Netflix/go-expect"
|
||||
"github.com/creack/pty"
|
||||
"github.com/hinshun/vt10x"
|
||||
|
||||
"knative.dev/func/pkg/docker"
|
||||
"knative.dev/func/pkg/oci"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -21,7 +20,7 @@ const (
|
|||
)
|
||||
|
||||
func Test_NewPromptForCredentials(t *testing.T) {
|
||||
expectedCreds := docker.Credentials{
|
||||
expectedCreds := oci.Credentials{
|
||||
Username: "testuser",
|
||||
Password: "testpwd",
|
||||
}
|
||||
|
|
|
@ -22,10 +22,10 @@ import (
|
|||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
||||
|
||||
"knative.dev/func/pkg/docker"
|
||||
"knative.dev/func/pkg/oci"
|
||||
)
|
||||
|
||||
type CredentialsCallback func(registry string) (docker.Credentials, error)
|
||||
type CredentialsCallback func(registry string) (oci.Credentials, error)
|
||||
|
||||
var ErrUnauthorized = errors.New("bad credentials")
|
||||
|
||||
|
@ -33,7 +33,7 @@ var ErrCredentialsNotFound = errors.New("credentials not found")
|
|||
|
||||
// VerifyCredentialsCallback checks if credentials are authorized for image push.
|
||||
// If credentials are incorrect this callback shall return ErrUnauthorized.
|
||||
type VerifyCredentialsCallback func(ctx context.Context, image string, credentials docker.Credentials) error
|
||||
type VerifyCredentialsCallback func(ctx context.Context, image string, credentials oci.Credentials) error
|
||||
|
||||
type keyChain struct {
|
||||
user string
|
||||
|
@ -48,7 +48,7 @@ func (k keyChain) Resolve(resource authn.Resource) (authn.Authenticator, error)
|
|||
}
|
||||
|
||||
// CheckAuth verifies that credentials can be used for image push
|
||||
func CheckAuth(ctx context.Context, image string, credentials docker.Credentials, trans http.RoundTripper) error {
|
||||
func CheckAuth(ctx context.Context, image string, credentials oci.Credentials, trans http.RoundTripper) error {
|
||||
|
||||
ref, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
|
@ -142,9 +142,8 @@ func WithAdditionalCredentialLoaders(loaders ...CredentialsCallback) Opt {
|
|||
// The picked value will be saved in the func config.
|
||||
//
|
||||
// To verify that credentials are correct custom callback can be used (see WithVerifyCredentials).
|
||||
func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsProvider {
|
||||
func NewCredentialsProvider(configPath string, opts ...Opt) oci.CredentialsProvider {
|
||||
var c credentialsProvider
|
||||
|
||||
for _, o := range opts {
|
||||
o(&c)
|
||||
}
|
||||
|
@ -154,7 +153,7 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
|
|||
}
|
||||
|
||||
if c.verifyCredentials == nil {
|
||||
c.verifyCredentials = func(ctx context.Context, registry string, credentials docker.Credentials) error {
|
||||
c.verifyCredentials = func(ctx context.Context, registry string, credentials oci.Credentials) error {
|
||||
return CheckAuth(ctx, registry, credentials, c.transport)
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +174,7 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
|
|||
|
||||
if _, err := os.Stat(c.authFilePath); err == nil {
|
||||
defaultCredentialLoaders = append(defaultCredentialLoaders,
|
||||
func(registry string) (docker.Credentials, error) {
|
||||
func(registry string) (oci.Credentials, error) {
|
||||
return getCredentialsByCredentialHelper(c.authFilePath, registry)
|
||||
})
|
||||
}
|
||||
|
@ -185,40 +184,40 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
|
|||
if err == nil {
|
||||
dockerConfigPath := filepath.Join(home, ".docker", "config.json")
|
||||
defaultCredentialLoaders = append(defaultCredentialLoaders,
|
||||
func(registry string) (docker.Credentials, error) {
|
||||
func(registry string) (oci.Credentials, error) {
|
||||
return getCredentialsByCredentialHelper(dockerConfigPath, registry)
|
||||
})
|
||||
}
|
||||
defaultCredentialLoaders = append(defaultCredentialLoaders,
|
||||
func(registry string) (docker.Credentials, error) {
|
||||
func(registry string) (oci.Credentials, error) {
|
||||
creds, err := dockerConfig.GetCredentials(sys, registry)
|
||||
if err != nil {
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
if creds.Username == "" || creds.Password == "" {
|
||||
return docker.Credentials{}, ErrCredentialsNotFound
|
||||
return oci.Credentials{}, ErrCredentialsNotFound
|
||||
}
|
||||
return docker.Credentials{
|
||||
return oci.Credentials{
|
||||
Username: creds.Username,
|
||||
Password: creds.Password,
|
||||
}, nil
|
||||
})
|
||||
defaultCredentialLoaders = append(defaultCredentialLoaders,
|
||||
func(registry string) (docker.Credentials, error) {
|
||||
func(registry string) (oci.Credentials, error) {
|
||||
// Fallback onto default docker config locations
|
||||
emptySys := &containersTypes.SystemContext{}
|
||||
creds, err := dockerConfig.GetCredentials(emptySys, registry)
|
||||
if err != nil {
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
return docker.Credentials{
|
||||
return oci.Credentials{
|
||||
Username: creds.Username,
|
||||
Password: creds.Password,
|
||||
}, nil
|
||||
})
|
||||
defaultCredentialLoaders = append(defaultCredentialLoaders,
|
||||
func(registry string) (docker.Credentials, error) { // empty credentials provider for unsecured registries
|
||||
return docker.Credentials{}, nil
|
||||
func(registry string) (oci.Credentials, error) { // empty credentials provider for unsecured registries
|
||||
return oci.Credentials{}, nil
|
||||
})
|
||||
|
||||
c.credentialLoaders = append(c.credentialLoaders, defaultCredentialLoaders...)
|
||||
|
@ -226,13 +225,13 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
|
|||
return c.getCredentials
|
||||
}
|
||||
|
||||
func (c *credentialsProvider) getCredentials(ctx context.Context, image string) (docker.Credentials, error) {
|
||||
func (c *credentialsProvider) getCredentials(ctx context.Context, image string) (oci.Credentials, error) {
|
||||
var err error
|
||||
result := docker.Credentials{}
|
||||
result := oci.Credentials{}
|
||||
|
||||
ref, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
return docker.Credentials{}, fmt.Errorf("cannot parse the image reference: %w", err)
|
||||
return oci.Credentials{}, fmt.Errorf("cannot parse the image reference: %w", err)
|
||||
}
|
||||
|
||||
registry := ref.Context().RegistryStr()
|
||||
|
@ -244,7 +243,7 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
|
|||
if errors.Is(err, ErrCredentialsNotFound) {
|
||||
continue
|
||||
}
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
|
||||
err = c.verifyCredentials(ctx, image, result)
|
||||
|
@ -252,14 +251,14 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
|
|||
return result, nil
|
||||
} else {
|
||||
if !errors.Is(err, ErrUnauthorized) {
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if c.promptForCredentials == nil {
|
||||
return docker.Credentials{}, ErrCredentialsNotFound
|
||||
return oci.Credentials{}, ErrCredentialsNotFound
|
||||
}
|
||||
|
||||
// this is [registry] / [repository]
|
||||
|
@ -271,7 +270,7 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
|
|||
// use repo here to print it out in prompt
|
||||
result, err = c.promptForCredentials(repository)
|
||||
if err != nil {
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
|
||||
err = c.verifyCredentials(ctx, image, result)
|
||||
|
@ -282,21 +281,21 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
|
|||
// This shouldn't be fatal error.
|
||||
if strings.Contains(err.Error(), "not implemented") {
|
||||
fmt.Fprintf(os.Stderr, "the cred-helper does not support write operation (consider changing the cred-helper it in auth.json)\n")
|
||||
return docker.Credentials{}, nil
|
||||
return oci.Credentials{}, nil
|
||||
}
|
||||
|
||||
if !errors.Is(err, errNoCredentialHelperConfigured) {
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
helpers := listCredentialHelpers()
|
||||
helper, err := c.promptForCredentialStore(helpers)
|
||||
if err != nil {
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
helper = strings.TrimPrefix(helper, "docker-credential-")
|
||||
err = setCredentialHelperToConfig(c.authFilePath, helper)
|
||||
if err != nil {
|
||||
return docker.Credentials{}, fmt.Errorf("faild to set the helper to the config: %w", err)
|
||||
return oci.Credentials{}, fmt.Errorf("faild to set the helper to the config: %w", err)
|
||||
}
|
||||
err = setCredentialsByCredentialHelper(c.authFilePath, registry, result.Username, result.Password)
|
||||
if err != nil {
|
||||
|
@ -304,11 +303,11 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
|
|||
// This shouldn't be fatal error.
|
||||
if strings.Contains(err.Error(), "not implemented") {
|
||||
fmt.Fprintf(os.Stderr, "the cred-helper does not support write operation (consider changing the cred-helper it in auth.json)\n")
|
||||
return docker.Credentials{}, nil
|
||||
return oci.Credentials{}, nil
|
||||
}
|
||||
|
||||
if !errors.Is(err, errNoCredentialHelperConfigured) {
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -317,7 +316,7 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
|
|||
if errors.Is(err, ErrUnauthorized) {
|
||||
continue
|
||||
}
|
||||
return docker.Credentials{}, err
|
||||
return oci.Credentials{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -374,8 +373,8 @@ func setCredentialHelperToConfig(confFilePath, helper string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getCredentialsByCredentialHelper(confFilePath, registry string) (docker.Credentials, error) {
|
||||
result := docker.Credentials{}
|
||||
func getCredentialsByCredentialHelper(confFilePath, registry string) (oci.Credentials, error) {
|
||||
result := oci.Credentials{}
|
||||
|
||||
helper, err := getCredentialHelperFromConfig(confFilePath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
|
@ -26,8 +26,8 @@ import (
|
|||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
|
||||
"knative.dev/func/pkg/docker"
|
||||
"knative.dev/func/pkg/docker/creds"
|
||||
"knative.dev/func/pkg/creds"
|
||||
"knative.dev/func/pkg/oci"
|
||||
. "knative.dev/func/pkg/testing"
|
||||
)
|
||||
|
||||
|
@ -162,7 +162,7 @@ func TestCheckAuth(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := docker.Credentials{
|
||||
c := oci.Credentials{
|
||||
Username: tt.args.username,
|
||||
Password: tt.args.password,
|
||||
}
|
||||
|
@ -199,7 +199,7 @@ func TestCheckAuth(t *testing.T) {
|
|||
func TestCheckAuthEmptyCreds(t *testing.T) {
|
||||
|
||||
localhost, _, _ := startServer(t, "", "")
|
||||
err := creds.CheckAuth(context.Background(), localhost+"/someorg/someimage:sometag", docker.Credentials{}, http.DefaultTransport)
|
||||
err := creds.CheckAuth(context.Background(), localhost+"/someorg/someimage:sometag", oci.Credentials{}, http.DefaultTransport)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -334,7 +334,7 @@ const (
|
|||
quayIoUserPwd = "goodPwd2"
|
||||
)
|
||||
|
||||
type Credentials = docker.Credentials
|
||||
type Credentials = oci.Credentials
|
||||
|
||||
func TestNewCredentialsProvider(t *testing.T) {
|
||||
helperWithQuayIO := newInMemoryHelper()
|
||||
|
@ -460,8 +460,8 @@ func TestNewCredentialsProvider(t *testing.T) {
|
|||
func TestNewCredentialsProviderEmptyCreds(t *testing.T) {
|
||||
resetHomeDir(t)
|
||||
|
||||
credentialsProvider := creds.NewCredentialsProvider(testConfigPath(t), creds.WithVerifyCredentials(func(ctx context.Context, image string, credentials docker.Credentials) error {
|
||||
if image == "localhost:5555/someorg/someimage:sometag" && credentials == (docker.Credentials{}) {
|
||||
credentialsProvider := creds.NewCredentialsProvider(testConfigPath(t), creds.WithVerifyCredentials(func(ctx context.Context, image string, credentials oci.Credentials) error {
|
||||
if image == "localhost:5555/someorg/someimage:sometag" && credentials == (oci.Credentials{}) {
|
||||
return nil
|
||||
}
|
||||
t.Fatal("unreachable")
|
||||
|
@ -471,7 +471,7 @@ func TestNewCredentialsProviderEmptyCreds(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if c != (docker.Credentials{}) {
|
||||
if c != (oci.Credentials{}) {
|
||||
t.Error("unexpected credentials")
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import (
|
|||
"strings"
|
||||
|
||||
fn "knative.dev/func/pkg/functions"
|
||||
"knative.dev/func/pkg/oci"
|
||||
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
|
@ -33,13 +34,6 @@ import (
|
|||
|
||||
type Opt func(*Pusher)
|
||||
|
||||
type Credentials struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type CredentialsProvider func(ctx context.Context, image string) (Credentials, error)
|
||||
|
||||
// PusherDockerClient is sub-interface of client.CommonAPIClient required by pusher.
|
||||
type PusherDockerClient interface {
|
||||
daemon.Client
|
||||
|
@ -52,12 +46,12 @@ type PusherDockerClientFactory func() (PusherDockerClient, error)
|
|||
// Pusher of images from local to remote registry.
|
||||
type Pusher struct {
|
||||
verbose bool // verbose logging.
|
||||
credentialsProvider CredentialsProvider
|
||||
credentialsProvider oci.CredentialsProvider
|
||||
transport http.RoundTripper
|
||||
dockerClientFactory PusherDockerClientFactory
|
||||
}
|
||||
|
||||
func WithCredentialsProvider(cp CredentialsProvider) Opt {
|
||||
func WithCredentialsProvider(cp oci.CredentialsProvider) Opt {
|
||||
return func(p *Pusher) {
|
||||
p.credentialsProvider = cp
|
||||
}
|
||||
|
@ -81,14 +75,10 @@ func WithVerbose(verbose bool) Opt {
|
|||
}
|
||||
}
|
||||
|
||||
func EmptyCredentialsProvider(ctx context.Context, registry string) (Credentials, error) {
|
||||
return Credentials{}, nil
|
||||
}
|
||||
|
||||
// NewPusher creates an instance of a docker-based image pusher.
|
||||
func NewPusher(opts ...Opt) *Pusher {
|
||||
result := &Pusher{
|
||||
credentialsProvider: EmptyCredentialsProvider,
|
||||
credentialsProvider: oci.EmptyCredentialsProvider,
|
||||
transport: http.DefaultTransport,
|
||||
dockerClientFactory: func() (PusherDockerClient, error) {
|
||||
c, _, err := NewClient(client.DefaultDockerHost)
|
||||
|
@ -176,7 +166,7 @@ func (n *Pusher) Push(ctx context.Context, f fn.Function) (string, error) {
|
|||
return d.String(), nil
|
||||
}
|
||||
|
||||
func (n *Pusher) pushImage(ctx context.Context, f fn.Function, credentials Credentials) (digest string, err error) {
|
||||
func (n *Pusher) pushImage(ctx context.Context, f fn.Function, credentials oci.Credentials) (digest string, err error) {
|
||||
|
||||
var output io.Writer
|
||||
|
||||
|
@ -211,7 +201,7 @@ func (n *Pusher) pushImage(ctx context.Context, f fn.Function, credentials Crede
|
|||
return "", err
|
||||
}
|
||||
|
||||
func (n *Pusher) daemonPush(ctx context.Context, f fn.Function, credentials Credentials, output io.Writer) (digest string, err error) {
|
||||
func (n *Pusher) daemonPush(ctx context.Context, f fn.Function, credentials oci.Credentials, output io.Writer) (digest string, err error) {
|
||||
cli, err := n.dockerClientFactory()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create docker api client: %w", err)
|
||||
|
@ -267,7 +257,7 @@ func ParseDigest(output string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (n *Pusher) push(ctx context.Context, f fn.Function, credentials Credentials, output io.Writer) (digest string, err error) {
|
||||
func (n *Pusher) push(ctx context.Context, f fn.Function, credentials oci.Credentials, output io.Writer) (digest string, err error) {
|
||||
auth := &authn.Basic{
|
||||
Username: credentials.Username,
|
||||
Password: credentials.Password,
|
||||
|
|
|
@ -38,6 +38,7 @@ import (
|
|||
|
||||
"knative.dev/func/pkg/docker"
|
||||
fn "knative.dev/func/pkg/functions"
|
||||
"knative.dev/func/pkg/oci"
|
||||
)
|
||||
|
||||
func TestGetRegistry(t *testing.T) {
|
||||
|
@ -77,8 +78,8 @@ const (
|
|||
imageRepoDigest = "sha256:00af51d125f3092e157a7f8a717029412dc9d266c017e89cecdfeccb4cc3d7a7"
|
||||
)
|
||||
|
||||
var testCredProvider = docker.CredentialsProvider(func(ctx context.Context, registry string) (docker.Credentials, error) {
|
||||
return docker.Credentials{
|
||||
var testCredProvider = oci.CredentialsProvider(func(ctx context.Context, registry string) (oci.Credentials, error) {
|
||||
return oci.Credentials{
|
||||
Username: testUser,
|
||||
Password: testPwd,
|
||||
}, nil
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1/google"
|
||||
|
||||
"knative.dev/func/pkg/creds"
|
||||
"knative.dev/func/pkg/oci"
|
||||
)
|
||||
|
||||
func GetGoogleCredentialLoader() []creds.CredentialsCallback {
|
||||
return []creds.CredentialsCallback{
|
||||
func(registry string) (oci.Credentials, error) {
|
||||
if registry != "gcr.io" {
|
||||
return oci.Credentials{}, nil // skip if not GCR
|
||||
}
|
||||
|
||||
res, err := name.NewRegistry(registry)
|
||||
if err != nil {
|
||||
return oci.Credentials{}, fmt.Errorf("parse registry: %w", err)
|
||||
}
|
||||
|
||||
authenticator, err := google.Keychain.Resolve(res)
|
||||
if err != nil {
|
||||
return oci.Credentials{}, fmt.Errorf("resolve google keychain: %w", err)
|
||||
}
|
||||
|
||||
authCfg, err := authenticator.Authorization()
|
||||
if err != nil {
|
||||
return oci.Credentials{}, fmt.Errorf("get authorization: %w", err)
|
||||
}
|
||||
|
||||
return oci.Credentials{
|
||||
Username: authCfg.Username,
|
||||
Password: authCfg.Password,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func GetECRCredentialLoader() []creds.CredentialsCallback {
|
||||
return []creds.CredentialsCallback{} // TODO: Implement ECR credentials loader
|
||||
}
|
||||
|
||||
func GetACRCredentialLoader() []creds.CredentialsCallback {
|
||||
return []creds.CredentialsCallback{
|
||||
func(registry string) (oci.Credentials, error) {
|
||||
if !strings.HasSuffix(registry, ".azurecr.io") {
|
||||
return oci.Credentials{}, nil
|
||||
}
|
||||
|
||||
f, err := os.Open(path.Join(os.Getenv("HOME"), ".azure", "accessTokens.json"))
|
||||
if err != nil {
|
||||
return oci.Credentials{}, fmt.Errorf("open Azure access tokens: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var tokens []struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
Resource string `json:"resource"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(f).Decode(&tokens); err != nil {
|
||||
return oci.Credentials{}, fmt.Errorf("decode Azure access tokens: %w", err)
|
||||
}
|
||||
|
||||
target := "https://" + registry
|
||||
for _, t := range tokens {
|
||||
if t.Resource == target {
|
||||
return oci.Credentials{
|
||||
Username: "00000000-0000-0000-0000-000000000000",
|
||||
Password: t.AccessToken,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return oci.Credentials{}, nil
|
||||
},
|
||||
}
|
||||
}
|
|
@ -14,9 +14,9 @@ import (
|
|||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
|
||||
"knative.dev/func/pkg/docker"
|
||||
"knative.dev/func/pkg/docker/creds"
|
||||
"knative.dev/func/pkg/creds"
|
||||
fn "knative.dev/func/pkg/functions"
|
||||
"knative.dev/func/pkg/oci"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -106,7 +106,7 @@ func GetOpenShiftDockerCredentialLoaders() []creds.CredentialsCallback {
|
|||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var credentials docker.Credentials
|
||||
var credentials oci.Credentials
|
||||
|
||||
if authInfo := rawConf.AuthInfos[cc.AuthInfo]; authInfo != nil {
|
||||
credentials.Username = "openshift"
|
||||
|
@ -114,11 +114,11 @@ func GetOpenShiftDockerCredentialLoaders() []creds.CredentialsCallback {
|
|||
}
|
||||
|
||||
return []creds.CredentialsCallback{
|
||||
func(registry string) (docker.Credentials, error) {
|
||||
func(registry string) (oci.Credentials, error) {
|
||||
if registry == openShiftRegistryHostPort {
|
||||
return credentials, nil
|
||||
}
|
||||
return docker.Credentials{}, creds.ErrCredentialsNotFound
|
||||
return oci.Credentials{}, creds.ErrCredentialsNotFound
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/google"
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -22,29 +21,63 @@ import (
|
|||
fn "knative.dev/func/pkg/functions"
|
||||
)
|
||||
|
||||
type Credentials struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type CredentialsProvider func(ctx context.Context, image string) (Credentials, error)
|
||||
|
||||
type Opt func(*Pusher)
|
||||
|
||||
// Pusher of OCI multi-arch layout directories.
|
||||
type Pusher struct {
|
||||
Anonymous bool
|
||||
Insecure bool
|
||||
Token string
|
||||
Username string
|
||||
Verbose bool
|
||||
Anonymous bool
|
||||
credentialsProvider CredentialsProvider
|
||||
|
||||
Insecure bool
|
||||
Token string
|
||||
Username string
|
||||
Verbose bool
|
||||
|
||||
updates chan v1.Update
|
||||
done chan bool
|
||||
}
|
||||
|
||||
func NewPusher(insecure, anon, verbose bool) *Pusher {
|
||||
return &Pusher{
|
||||
Insecure: insecure,
|
||||
Anonymous: anon,
|
||||
Verbose: verbose,
|
||||
updates: make(chan v1.Update, 10),
|
||||
done: make(chan bool, 1),
|
||||
func EmptyCredentialsProvider(ctx context.Context, registry string) (Credentials, error) {
|
||||
return Credentials{}, nil
|
||||
}
|
||||
|
||||
func WithCredentialsProvider(cp CredentialsProvider) Opt {
|
||||
return func(p *Pusher) {
|
||||
p.credentialsProvider = cp
|
||||
}
|
||||
}
|
||||
|
||||
func WithVerbose(verbose bool) Opt {
|
||||
return func(pusher *Pusher) {
|
||||
pusher.Verbose = verbose
|
||||
}
|
||||
}
|
||||
|
||||
func NewPusher(insecure, anon, verbose bool, opts ...Opt) *Pusher {
|
||||
result := &Pusher{
|
||||
credentialsProvider: EmptyCredentialsProvider,
|
||||
Insecure: insecure,
|
||||
Anonymous: anon,
|
||||
Verbose: verbose,
|
||||
updates: make(chan v1.Update, 10),
|
||||
done: make(chan bool, 1),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Pusher) Push(ctx context.Context, f fn.Function) (digest string, err error) {
|
||||
credentials, _ := p.credentialsProvider(ctx, f.Build.Image)
|
||||
|
||||
go p.handleUpdates(ctx)
|
||||
defer func() { p.done <- true }()
|
||||
buildDir, err := getLastBuildDir(f)
|
||||
|
@ -67,7 +100,7 @@ func (p *Pusher) Push(ctx context.Context, f fn.Function) (digest string, err er
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = p.writeIndex(ctx, ref, ii); err != nil {
|
||||
if err = p.writeIndex(ctx, ref, ii, credentials); err != nil {
|
||||
return
|
||||
}
|
||||
h, err := ii.Digest()
|
||||
|
@ -120,7 +153,7 @@ func getLastBuildDir(f fn.Function) (string, error) {
|
|||
}
|
||||
|
||||
// writeIndex to its defined registry.
|
||||
func (p *Pusher) writeIndex(ctx context.Context, ref name.Reference, ii v1.ImageIndex) error {
|
||||
func (p *Pusher) writeIndex(ctx context.Context, ref name.Reference, ii v1.ImageIndex, creds Credentials) error {
|
||||
oo := []remote.Option{
|
||||
remote.WithContext(ctx),
|
||||
remote.WithProgress(p.updates),
|
||||
|
@ -135,10 +168,13 @@ func (p *Pusher) writeIndex(ctx context.Context, ref name.Reference, ii v1.Image
|
|||
}
|
||||
|
||||
if !p.Anonymous {
|
||||
a, err := p.authOption(ctx)
|
||||
a, err := p.authOption(ctx, creds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a == nil {
|
||||
return errors.New("no authentication option provided")
|
||||
}
|
||||
oo = append(oo, a)
|
||||
}
|
||||
|
||||
|
@ -148,13 +184,14 @@ func (p *Pusher) writeIndex(ctx context.Context, ref name.Reference, ii v1.Image
|
|||
// authOption selects an appropriate authentication option.
|
||||
// If user provided = basic auth (secret is password)
|
||||
// If only secret provided = bearer token auth
|
||||
// If neither are provided = Returned is a cascading keychain auth mthod
|
||||
// If neither are provided = creds from credentials provider
|
||||
// which performs the following in order:
|
||||
// - Default Keychain (docker and podman config files)
|
||||
// - Google Keychain
|
||||
// - TODO: ECR Amazon
|
||||
// - TODO: ACR Azure
|
||||
func (p *Pusher) authOption(ctx context.Context) (remote.Option, error) {
|
||||
// - interactive prompt for username and password
|
||||
func (p *Pusher) authOption(ctx context.Context, creds Credentials) (remote.Option, error) {
|
||||
|
||||
// Basic Auth if provided
|
||||
username, _ := ctx.Value(fn.PushUsernameKey{}).(string)
|
||||
|
@ -168,12 +205,10 @@ func (p *Pusher) authOption(ctx context.Context) (remote.Option, error) {
|
|||
return remote.WithAuth(&authn.Basic{Username: username, Password: password}), nil
|
||||
}
|
||||
|
||||
// Default chain
|
||||
return remote.WithAuthFromKeychain(authn.NewMultiKeychain(
|
||||
authn.DefaultKeychain, // Podman and Docker config files
|
||||
google.Keychain, // Google
|
||||
// TODO: Integrate and test ECR and ACR credential helpers:
|
||||
// authn.NewKeychainFromHelper(ecr.ECRHelper{ClientFactory: api.DefaultClientFactory{}}),
|
||||
// authn.NewKeychainFromHelper(acr.ACRCredHelper{}),
|
||||
)), nil
|
||||
// Use provided credentials if available or prompt for them
|
||||
if creds.Username != "" && creds.Password != "" {
|
||||
return remote.WithAuth(&authn.Basic{Username: creds.Username, Password: creds.Password}), nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -37,9 +37,9 @@ import (
|
|||
"knative.dev/pkg/apis"
|
||||
|
||||
"knative.dev/func/pkg/builders/buildpacks"
|
||||
"knative.dev/func/pkg/docker"
|
||||
fn "knative.dev/func/pkg/functions"
|
||||
"knative.dev/func/pkg/k8s"
|
||||
"knative.dev/func/pkg/oci"
|
||||
"knative.dev/func/pkg/pipelines"
|
||||
"knative.dev/func/pkg/pipelines/tekton"
|
||||
"knative.dev/func/pkg/random"
|
||||
|
@ -102,8 +102,8 @@ func TestGitlab(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
credentialsProvider := func(ctx context.Context, image string) (docker.Credentials, error) {
|
||||
return docker.Credentials{
|
||||
credentialsProvider := func(ctx context.Context, image string) (oci.Credentials, error) {
|
||||
return oci.Credentials{
|
||||
Username: "",
|
||||
Password: "",
|
||||
}, nil
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"knative.dev/func/pkg/k8s"
|
||||
"knative.dev/func/pkg/knative"
|
||||
"knative.dev/func/pkg/oci"
|
||||
|
||||
"knative.dev/func/pkg/builders/buildpacks"
|
||||
pack "knative.dev/func/pkg/builders/buildpacks"
|
||||
|
@ -34,8 +35,8 @@ import (
|
|||
. "knative.dev/func/pkg/testing"
|
||||
)
|
||||
|
||||
var testCP = func(_ context.Context, _ string) (docker.Credentials, error) {
|
||||
return docker.Credentials{
|
||||
var testCP = func(_ context.Context, _ string) (oci.Credentials, error) {
|
||||
return oci.Credentials{
|
||||
Username: "",
|
||||
Password: "",
|
||||
}, nil
|
||||
|
|
|
@ -34,6 +34,7 @@ import (
|
|||
"knative.dev/func/pkg/k8s"
|
||||
fnlabels "knative.dev/func/pkg/k8s/labels"
|
||||
"knative.dev/func/pkg/knative"
|
||||
"knative.dev/func/pkg/oci"
|
||||
"knative.dev/pkg/apis"
|
||||
)
|
||||
|
||||
|
@ -54,11 +55,11 @@ type pacURLCallback = func() (string, error)
|
|||
type PipelinesProvider struct {
|
||||
verbose bool
|
||||
getPacURL pacURLCallback
|
||||
credentialsProvider docker.CredentialsProvider
|
||||
credentialsProvider oci.CredentialsProvider
|
||||
decorator PipelineDecorator
|
||||
}
|
||||
|
||||
func WithCredentialsProvider(credentialsProvider docker.CredentialsProvider) Opt {
|
||||
func WithCredentialsProvider(credentialsProvider oci.CredentialsProvider) Opt {
|
||||
return func(pp *PipelinesProvider) {
|
||||
pp.credentialsProvider = credentialsProvider
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue