github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/docker/registry/internal/gcr.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package internal 5 6 import ( 7 "fmt" 8 "net/http" 9 "strings" 10 11 "github.com/juju/errors" 12 13 "github.com/juju/juju/docker" 14 ) 15 16 type googleContainerRegistry struct { 17 *baseClient 18 } 19 20 func newGoogleContainerRegistry(repoDetails docker.ImageRepoDetails, transport http.RoundTripper) (RegistryInternal, error) { 21 c, err := newBase(repoDetails, transport, normalizeRepoDetailsCommon) 22 if err != nil { 23 return nil, errors.Trace(err) 24 } 25 return &googleContainerRegistry{c}, nil 26 } 27 28 func (c *googleContainerRegistry) String() string { 29 return "gcr.io" 30 } 31 32 // Match checks if the repository details matches current provider format. 33 func (c *googleContainerRegistry) Match() bool { 34 return strings.Contains(c.repoDetails.ServerAddress, "gcr.io") 35 } 36 37 const ( 38 googleContainerRegistryUserNameJSONKey = "_json_key" 39 ) 40 41 var invalidGoogleContainerRegistryUserNameError = errors.NewNotValid(nil, 42 fmt.Sprintf("google container registry username has to be %q", 43 googleContainerRegistryUserNameJSONKey, 44 ), 45 ) 46 47 func validateGoogleContainerRegistryCredential(auth docker.BasicAuthConfig) (err error) { 48 if auth.Username == "" && auth.Auth.Empty() { 49 return errors.NewNotValid(nil, "username or auth token is required") 50 } 51 username := auth.Username 52 if !auth.Auth.Empty() { 53 username, _, err = unpackAuthToken(auth.Auth.Value) 54 if err != nil { 55 return errors.Annotate(err, "getting username from the google container registry auth token") 56 } 57 } 58 if username != googleContainerRegistryUserNameJSONKey { 59 return invalidGoogleContainerRegistryUserNameError 60 } 61 return nil 62 } 63 64 func googleContainerRegistryTransport(transport http.RoundTripper, repoDetails *docker.ImageRepoDetails, 65 ) (http.RoundTripper, error) { 66 if !repoDetails.TokenAuthConfig.Empty() { 67 return nil, errors.NewNotValid(nil, "google container registry only supports username and password or auth token") 68 } 69 if repoDetails.BasicAuthConfig.Empty() { 70 // Anonymous login. 71 return newTokenTransport(transport, "", "", "", "", false), nil 72 } 73 if err := validateGoogleContainerRegistryCredential(repoDetails.BasicAuthConfig); err != nil { 74 return nil, errors.Annotatef(err, "validating the google container registry credential") 75 } 76 return newTokenTransport( 77 transport, 78 repoDetails.Username, repoDetails.Password, repoDetails.Auth.Content(), "", false, 79 ), nil 80 } 81 82 func (c *googleContainerRegistry) WrapTransport(...TransportWrapper) (err error) { 83 if c.client.Transport, err = mergeTransportWrappers( 84 c.client.Transport, c.repoDetails, 85 googleContainerRegistryTransport, wrapErrorTransport, 86 ); err != nil { 87 return errors.Trace(err) 88 } 89 return nil 90 } 91 92 // Ping pings the github endpoint. 93 func (c googleContainerRegistry) Ping() error { 94 if !c.repoDetails.IsPrivate() { 95 // gcr.io root path requires authentication. 96 // So skip ping for public repositories. 97 return nil 98 } 99 url := c.url("/") 100 if !strings.HasSuffix(url, "/") { 101 // gcr v2 root endpoint requires the trailing slash(otherwise 404 returns). 102 url += "/" 103 } 104 logger.Debugf("gcr ping %q", url) 105 resp, err := c.client.Get(url) 106 if resp != nil { 107 defer resp.Body.Close() 108 } 109 return errors.Trace(unwrapNetError(err)) 110 }