github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/docker/registry/internal/base_client_test.go (about)

     1  // Copyright 2021 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package internal_test
     5  
     6  import (
     7  	"encoding/base64"
     8  	"io"
     9  	"net/http"
    10  	"strings"
    11  
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	"go.uber.org/mock/gomock"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/docker"
    18  	"github.com/juju/juju/docker/registry"
    19  	"github.com/juju/juju/docker/registry/internal"
    20  	"github.com/juju/juju/docker/registry/mocks"
    21  )
    22  
    23  type baseSuite struct {
    24  	testing.IsolationSuite
    25  
    26  	mockRoundTripper *mocks.MockRoundTripper
    27  	imageRepoDetails docker.ImageRepoDetails
    28  	isPrivate        bool
    29  }
    30  
    31  var _ = gc.Suite(&baseSuite{})
    32  
    33  func (s *baseSuite) getAuthToken(username, password string) string {
    34  	return base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
    35  }
    36  
    37  func (s *baseSuite) getRegistry(c *gc.C) (*internal.BaseClient, *gomock.Controller) {
    38  	ctrl := gomock.NewController(c)
    39  
    40  	s.imageRepoDetails = docker.ImageRepoDetails{
    41  		Repository:    "example.com/jujuqa",
    42  		ServerAddress: "example.com",
    43  	}
    44  	authToken := s.getAuthToken("username", "pwd")
    45  	if s.isPrivate {
    46  		s.imageRepoDetails.BasicAuthConfig = docker.BasicAuthConfig{
    47  			Auth: docker.NewToken(authToken),
    48  		}
    49  	}
    50  
    51  	s.mockRoundTripper = mocks.NewMockRoundTripper(ctrl)
    52  	gomock.InOrder(
    53  		// registry.Ping() 1st try failed - bearer token was missing.
    54  		s.mockRoundTripper.EXPECT().RoundTrip(gomock.Any()).DoAndReturn(
    55  			func(req *http.Request) (*http.Response, error) {
    56  				c.Assert(req.Header, jc.DeepEquals, http.Header{})
    57  				c.Assert(req.Method, gc.Equals, `GET`)
    58  				c.Assert(req.URL.String(), gc.Equals, `https://example.com/v2`)
    59  				return &http.Response{
    60  					Request:    req,
    61  					StatusCode: http.StatusUnauthorized,
    62  					Body:       io.NopCloser(nil),
    63  					Header: http.Header{
    64  						http.CanonicalHeaderKey("WWW-Authenticate"): []string{
    65  							`Bearer realm="https://auth.example.com/token",service="registry.example.com",scope="repository:jujuqa/jujud-operator:pull"`,
    66  						},
    67  					},
    68  				}, nil
    69  			},
    70  		),
    71  		// Refresh OAuth Token
    72  		s.mockRoundTripper.EXPECT().RoundTrip(gomock.Any()).DoAndReturn(
    73  			func(req *http.Request) (*http.Response, error) {
    74  				if s.isPrivate {
    75  					c.Assert(req.Header, jc.DeepEquals, http.Header{"Authorization": []string{"Basic " + authToken}})
    76  				}
    77  				c.Assert(req.Method, gc.Equals, `GET`)
    78  				c.Assert(req.URL.String(), gc.Equals, `https://auth.example.com/token?scope=repository%3Ajujuqa%2Fjujud-operator%3Apull&service=registry.example.com`)
    79  				return &http.Response{
    80  					Request:    req,
    81  					StatusCode: http.StatusOK,
    82  					Body:       io.NopCloser(strings.NewReader(`{"token": "jwt-token", "access_token": "jwt-token","expires_in": 300}`)),
    83  				}, nil
    84  			},
    85  		),
    86  		s.mockRoundTripper.EXPECT().RoundTrip(gomock.Any()).DoAndReturn(
    87  			func(req *http.Request) (*http.Response, error) {
    88  				c.Assert(req.Header, jc.DeepEquals, http.Header{"Authorization": []string{"Bearer " + `jwt-token`}})
    89  				c.Assert(req.Method, gc.Equals, `GET`)
    90  				c.Assert(req.URL.String(), gc.Equals, `https://example.com/v2`)
    91  				return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(nil)}, nil
    92  			},
    93  		),
    94  	)
    95  	s.PatchValue(&registry.DefaultTransport, s.mockRoundTripper)
    96  
    97  	reg, err := registry.New(s.imageRepoDetails)
    98  	c.Assert(err, jc.ErrorIsNil)
    99  	client, ok := reg.(*internal.BaseClient)
   100  	c.Assert(ok, jc.IsTrue)
   101  	err = reg.Ping()
   102  	c.Assert(err, jc.ErrorIsNil)
   103  	return client, ctrl
   104  }
   105  
   106  func (s *baseSuite) TestPingPublicRepository(c *gc.C) {
   107  	s.isPrivate = false
   108  	_, ctrl := s.getRegistry(c)
   109  	ctrl.Finish()
   110  }
   111  
   112  func (s *baseSuite) TestPingPrivateRepository(c *gc.C) {
   113  	s.isPrivate = true
   114  	_, ctrl := s.getRegistry(c)
   115  	ctrl.Finish()
   116  }
   117  
   118  func (s *baseSuite) TestInvalidAuth(c *gc.C) {
   119  	s.imageRepoDetails = docker.ImageRepoDetails{
   120  		Repository:    "example.com/jujuqa",
   121  		ServerAddress: "example.com",
   122  	}
   123  	s.imageRepoDetails.TokenAuthConfig = docker.TokenAuthConfig{
   124  		RegistryToken: &docker.Token{Value: `xxxxx==`},
   125  	}
   126  
   127  	_, err := registry.New(s.imageRepoDetails)
   128  	c.Assert(err, gc.ErrorMatches, `only {"username", "password"} or {"auth"} authorization is supported for registry "example.com"`)
   129  }