oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/registry/remote/credentials/registry.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package credentials 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 23 "oras.land/oras-go/v2/registry/remote" 24 "oras.land/oras-go/v2/registry/remote/auth" 25 ) 26 27 // ErrClientTypeUnsupported is thrown by Login() when the registry's client type 28 // is not supported. 29 var ErrClientTypeUnsupported = errors.New("client type not supported") 30 31 // Login provides the login functionality with the given credentials. The target 32 // registry's client should be nil or of type *auth.Client. Login uses 33 // a client local to the function and will not modify the original client of 34 // the registry. 35 func Login(ctx context.Context, store Store, reg *remote.Registry, cred auth.Credential) error { 36 // create a clone of the original registry for login purpose 37 regClone := *reg 38 // we use the original client if applicable, otherwise use a default client 39 var authClient auth.Client 40 if reg.Client == nil { 41 authClient = *auth.DefaultClient 42 authClient.Cache = nil // no cache 43 } else if client, ok := reg.Client.(*auth.Client); ok { 44 authClient = *client 45 } else { 46 return ErrClientTypeUnsupported 47 } 48 regClone.Client = &authClient 49 // update credentials with the client 50 authClient.Credential = auth.StaticCredential(reg.Reference.Registry, cred) 51 // validate and store the credential 52 if err := regClone.Ping(ctx); err != nil { 53 return fmt.Errorf("failed to validate the credentials for %s: %w", regClone.Reference.Registry, err) 54 } 55 hostname := ServerAddressFromRegistry(regClone.Reference.Registry) 56 if err := store.Put(ctx, hostname, cred); err != nil { 57 return fmt.Errorf("failed to store the credentials for %s: %w", hostname, err) 58 } 59 return nil 60 } 61 62 // Logout provides the logout functionality given the registry name. 63 func Logout(ctx context.Context, store Store, registryName string) error { 64 registryName = ServerAddressFromRegistry(registryName) 65 if err := store.Delete(ctx, registryName); err != nil { 66 return fmt.Errorf("failed to delete the credential for %s: %w", registryName, err) 67 } 68 return nil 69 } 70 71 // Credential returns a Credential() function that can be used by auth.Client. 72 func Credential(store Store) auth.CredentialFunc { 73 return func(ctx context.Context, hostport string) (auth.Credential, error) { 74 hostport = ServerAddressFromHostname(hostport) 75 if hostport == "" { 76 return auth.EmptyCredential, nil 77 } 78 return store.Get(ctx, hostport) 79 } 80 } 81 82 // ServerAddressFromRegistry maps a registry to a server address, which is used as 83 // a key for credentials store. The Docker CLI expects that the credentials of 84 // the registry 'docker.io' will be added under the key "https://index.docker.io/v1/". 85 // See: https://github.com/moby/moby/blob/v24.0.2/registry/config.go#L25-L48 86 func ServerAddressFromRegistry(registry string) string { 87 if registry == "docker.io" { 88 return "https://index.docker.io/v1/" 89 } 90 return registry 91 } 92 93 // ServerAddressFromHostname maps a hostname to a server address, which is used as 94 // a key for credentials store. It is expected that the traffic targetting the 95 // host "registry-1.docker.io" will be redirected to "https://index.docker.io/v1/". 96 // See: https://github.com/moby/moby/blob/v24.0.2/registry/config.go#L25-L48 97 func ServerAddressFromHostname(hostname string) string { 98 if hostname == "registry-1.docker.io" { 99 return "https://index.docker.io/v1/" 100 } 101 return hostname 102 }