package client import ( "context" "errors" "fmt" "io" "net/http" "testing" cerrdefs "github.com/containerd/errdefs" "github.com/moby/moby/api/types/registry" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) func TestImagePushReferenceError(t *testing.T) { client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) { return nil, nil })) assert.NilError(t, err) // An empty reference is an invalid reference _, err = client.ImagePush(t.Context(), "", ImagePushOptions{}) assert.Check(t, is.ErrorContains(err, "invalid reference format")) // A canonical reference cannot be pushed _, err = client.ImagePush(t.Context(), "repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ImagePushOptions{}) assert.Check(t, is.Error(err, "cannot push a digest reference")) } func TestImagePushAnyError(t *testing.T) { client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) assert.NilError(t, err) _, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) } func TestImagePushStatusUnauthorizedError(t *testing.T) { client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error"))) assert.NilError(t, err) _, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized)) } func TestImagePushWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) { client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error"))) assert.NilError(t, err) privilegeFunc := func(_ context.Context) (string, error) { return "", errors.New("error requesting privilege") } _, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{ PrivilegeFunc: privilegeFunc, }) assert.Check(t, is.Error(err, "error requesting privilege")) } func TestImagePushWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) { client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error"))) assert.NilError(t, err) privilegeFunc := func(_ context.Context) (string, error) { return "a-auth-header", nil } _, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{ PrivilegeFunc: privilegeFunc, }) assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized)) } func TestImagePushWithPrivilegedFuncNoError(t *testing.T) { const expectedURL = "/images/docker.io/myname/myimage/push" const invalidAuth = "NotValid" const validAuth = "IAmValid" client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) { if err := assertRequest(req, http.MethodPost, expectedURL); err != nil { return nil, err } auth := req.Header.Get(registry.AuthHeader) if auth == invalidAuth { return mockResponse(http.StatusUnauthorized, nil, "Invalid credentials")(req) } if auth != validAuth { return nil, fmt.Errorf("invalid auth header: expected %s, got %s", "IAmValid", auth) } query := req.URL.Query() tag := query.Get("tag") if tag != "tag" { return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", "tag", tag) } return mockResponse(http.StatusOK, nil, "hello world")(req) })) assert.NilError(t, err) resp, err := client.ImagePush(t.Context(), "myname/myimage:tag", ImagePushOptions{ RegistryAuth: invalidAuth, PrivilegeFunc: staticAuth(validAuth), }) assert.NilError(t, err) body, err := io.ReadAll(resp) assert.NilError(t, err) assert.Check(t, is.Equal(string(body), "hello world")) } func TestImagePushWithoutErrors(t *testing.T) { const ( expectedURLFormat = "/images/%s/push" expectedOutput = "hello world" ) testCases := []struct { all bool reference string expectedImage string expectedTag string }{ { all: false, reference: "myimage", expectedImage: "docker.io/library/myimage", expectedTag: "latest", }, { all: false, reference: "myimage:tag", expectedImage: "docker.io/library/myimage", expectedTag: "tag", }, { all: true, reference: "myimage", expectedImage: "docker.io/library/myimage", expectedTag: "", }, { all: true, reference: "myimage:anything", expectedImage: "docker.io/library/myimage", expectedTag: "", }, { reference: "myname/myimage", expectedImage: "docker.io/myname/myimage", expectedTag: "latest", }, { reference: "docker.io/myname/myimage", expectedImage: "docker.io/myname/myimage", expectedTag: "latest", }, { reference: "index.docker.io/myname/myimage:tag", expectedImage: "docker.io/myname/myimage", expectedTag: "tag", }, { reference: "localhost/myname/myimage", expectedImage: "localhost/myname/myimage", expectedTag: "latest", }, { reference: "registry.example.com:5000/myimage:tag", expectedImage: "registry.example.com:5000/myimage", expectedTag: "tag", }, } for _, tc := range testCases { t.Run(fmt.Sprintf("%s,all-tags=%t", tc.reference, tc.all), func(t *testing.T) { client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) { expectedURL := fmt.Sprintf(expectedURLFormat, tc.expectedImage) if err := assertRequest(req, http.MethodPost, expectedURL); err != nil { return nil, err } query := req.URL.Query() tag := query.Get("tag") if tag != tc.expectedTag { return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", tc.expectedTag, tag) } return mockResponse(http.StatusOK, nil, expectedOutput)(req) })) assert.NilError(t, err) resp, err := client.ImagePush(t.Context(), tc.reference, ImagePushOptions{ All: tc.all, }) assert.NilError(t, err) body, err := io.ReadAll(resp) assert.NilError(t, err) assert.Check(t, is.Equal(string(body), expectedOutput)) }) } }