From e061593d16b6cf6bc9d639de7beb6c7a78ff86cb Mon Sep 17 00:00:00 2001 From: Zoran Regvart Date: Mon, 28 Oct 2024 15:29:44 +0100 Subject: [PATCH] refactor(oci): maintain backward compatibility This restores the `ClientOptResolver` function which now panics on invocation and previous signature of the `LoginOption` function. Signed-off-by: Zoran Regvart --- go.mod | 4 +++ go.sum | 8 +++++ pkg/registry/client.go | 70 +++++++++++++++++++++++++------------ pkg/registry/client_test.go | 32 +++++++++++++++++ 4 files changed, 92 insertions(+), 22 deletions(-) create mode 100644 pkg/registry/client_test.go diff --git a/go.mod b/go.mod index 653b571c2..22cf0c203 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/vcs v1.13.3 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 + github.com/containerd/containerd v1.7.23 github.com/cyphar/filepath-securejoin v0.3.4 github.com/distribution/distribution/v3 v3.0.0-rc.1 github.com/evanphx/json-patch v5.9.0+incompatible @@ -58,6 +59,9 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/containerd/errdefs v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect diff --git a/go.sum b/go.sum index e89418524..e7b2fc687 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,14 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ= +github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= diff --git a/pkg/registry/client.go b/pkg/registry/client.go index f36aa50f6..e0a9203f0 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -30,6 +30,7 @@ import ( "sync" "github.com/Masterminds/semver/v3" + "github.com/containerd/containerd/remotes" "github.com/opencontainers/image-spec/specs-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -54,6 +55,8 @@ storing semantic versions, Helm adopts the convention of changing plus (+) to an underscore (_) in chart version tags when pushing to a registry and back to a plus (+) when pulling from a registry.` +var errDeprecatedRemote = errors.New("providing github.com/containerd/containerd/remotes.Resolver via ClientOptResolver is no longer suported") + type ( // Client works with OCI-compliant registries Client struct { @@ -66,6 +69,7 @@ type ( credentialsStore credentials.Store httpClient *http.Client plainHTTP bool + err error // pass any errors from the ClientOption or LoginOption functions } // ClientOption allows specifying various settings configurable by the user for overriding the defaults @@ -80,6 +84,9 @@ func NewClient(options ...ClientOption) (*Client, error) { } for _, option := range options { option(client) + if client.err != nil { + return nil, client.popError() + } } if client.credentialsFile == "" { client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename) @@ -184,16 +191,29 @@ func ClientOptPlainHTTP() ClientOption { } } +func ClientOptResolver(_ remotes.Resolver) ClientOption { + return func(c *Client) { + c.err = errDeprecatedRemote + } +} + type ( // LoginOption allows specifying various settings on login - LoginOption func(host string, client *Client) error + LoginOption func(*loginOperation) + + loginOperation struct { + host string + client *Client + } ) // Login logs into a registry func (c *Client) Login(host string, options ...LoginOption) error { for _, option := range options { - if err := option(host, c); err != nil { - return fmt.Errorf("configuring login option: %w", err) + o := loginOperation{host, c} + option(&o) + if c.err != nil { + return c.popError() } } @@ -224,19 +244,23 @@ func (c *Client) Login(host string, options ...LoginOption) error { return nil } +func (c *Client) popError() error { + err := c.err + c.err = nil + return err +} + // LoginOptBasicAuth returns a function that sets the username/password settings on login func LoginOptBasicAuth(username string, password string) LoginOption { - return func(host string, client *Client) error { - client.authorizer.Credential = auth.StaticCredential(host, auth.Credential{Username: username, Password: password}) - return nil + return func(o *loginOperation) { + o.client.authorizer.Credential = auth.StaticCredential(o.host, auth.Credential{Username: username, Password: password}) } } // LoginOptBasicAuth returns a function that allows plaintext (HTTP) login func LoginOptPlainText(isPlainText bool) LoginOption { - return func(host string, client *Client) error { - client.plainHTTP = isPlainText - return nil + return func(o *loginOperation) { + o.client.plainHTTP = isPlainText } } @@ -268,33 +292,35 @@ func ensureTLSConfig(client *auth.Client) (*tls.Config, error) { // LoginOptInsecure returns a function that sets the insecure setting on login func LoginOptInsecure(insecure bool) LoginOption { - return func(_ string, client *Client) error { - tlsConfig, err := ensureTLSConfig(client.authorizer) + return func(o *loginOperation) { + tlsConfig, err := ensureTLSConfig(o.client.authorizer) if err != nil { - return err + o.client.err = err + return } tlsConfig.InsecureSkipVerify = insecure - return nil } } // LoginOptTLSClientConfig returns a function that sets the TLS settings on login. func LoginOptTLSClientConfig(certFile, keyFile, caFile string) LoginOption { - return func(_ string, client *Client) error { + return func(o *loginOperation) { if (certFile == "" || keyFile == "") && caFile == "" { - return nil + return } - tlsConfig, err := ensureTLSConfig(client.authorizer) + tlsConfig, err := ensureTLSConfig(o.client.authorizer) if err != nil { - return err + o.client.err = err + return } if certFile != "" && keyFile != "" { authCert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { - return err + o.client.err = err + return } tlsConfig.Certificates = []tls.Certificate{authCert} } @@ -303,15 +329,15 @@ func LoginOptTLSClientConfig(certFile, keyFile, caFile string) LoginOption { certPool := x509.NewCertPool() ca, err := os.ReadFile(caFile) if err != nil { - return err + o.client.err = err + return } if !certPool.AppendCertsFromPEM(ca) { - return fmt.Errorf("unable to parse CA file: %q", caFile) + o.client.err = fmt.Errorf("unable to parse CA file: %q", caFile) + return } tlsConfig.RootCAs = certPool } - - return nil } } diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go new file mode 100644 index 000000000..5989e271b --- /dev/null +++ b/pkg/registry/client_test.go @@ -0,0 +1,32 @@ +/* +Copyright The Helm Authors. + +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 registry + +import ( + "testing" + + "github.com/containerd/containerd/remotes" +) + +func TestNewClientResolverNotSupported(t *testing.T) { + var r remotes.Resolver + + client, err := NewClient(ClientOptResolver(r)) + if client != nil || err != errDeprecatedRemote { + t.Errorf("expected nil, %v, got: %v, %v", errDeprecatedRemote, client, err) + } +}