| package client |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "io" |
| "net/http" |
| "testing" |
| "time" |
| |
| cerrdefs "github.com/containerd/errdefs" |
| "github.com/moby/moby/api/types/jsonstream" |
| "github.com/moby/moby/api/types/registry" |
| "github.com/moby/moby/client/internal" |
| "gotest.tools/v3/assert" |
| is "gotest.tools/v3/assert/cmp" |
| ) |
| |
| func TestImagePullReferenceParseError(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.ImagePull(t.Context(), "", ImagePullOptions{}) |
| assert.Check(t, is.ErrorContains(err, "invalid reference format")) |
| } |
| |
| func TestImagePullAnyError(t *testing.T) { |
| client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) |
| assert.NilError(t, err) |
| _, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{}) |
| assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) |
| } |
| |
| func TestImagePullStatusUnauthorizedError(t *testing.T) { |
| client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error"))) |
| assert.NilError(t, err) |
| _, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{}) |
| assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized)) |
| } |
| |
| func TestImagePullWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) { |
| client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error"))) |
| assert.NilError(t, err) |
| _, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{ |
| PrivilegeFunc: func(_ context.Context) (string, error) { |
| return "", errors.New("error requesting privilege") |
| }, |
| }) |
| assert.Check(t, is.Error(err, "error requesting privilege")) |
| } |
| |
| func TestImagePullWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) { |
| client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error"))) |
| assert.NilError(t, err) |
| _, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{ |
| PrivilegeFunc: staticAuth("a-auth-header"), |
| }) |
| assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized)) |
| } |
| |
| func TestImagePullWithPrivilegedFuncNoError(t *testing.T) { |
| const expectedURL = "/images/create" |
| 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() |
| fromImage := query.Get("fromImage") |
| if fromImage != "docker.io/library/myimage" { |
| return nil, fmt.Errorf("fromimage not set in URL query properly. Expected '%s', got %s", "docker.io/library/myimage", fromImage) |
| } |
| tag := query.Get("tag") |
| if tag != "latest" { |
| return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", "latest", tag) |
| } |
| return mockResponse(http.StatusOK, nil, "hello world")(req) |
| })) |
| assert.NilError(t, err) |
| resp, err := client.ImagePull(t.Context(), "myimage", ImagePullOptions{ |
| 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 TestImagePullWithoutErrors(t *testing.T) { |
| const ( |
| expectedURL = "/images/create" |
| expectedOutput = "hello world" |
| ) |
| pullCases := []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 _, pullCase := range pullCases { |
| t.Run(pullCase.reference, func(t *testing.T) { |
| client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) { |
| if err := assertRequest(req, http.MethodPost, expectedURL); err != nil { |
| return nil, err |
| } |
| query := req.URL.Query() |
| fromImage := query.Get("fromImage") |
| if fromImage != pullCase.expectedImage { |
| return nil, fmt.Errorf("fromimage not set in URL query properly. Expected '%s', got %s", pullCase.expectedImage, fromImage) |
| } |
| tag := query.Get("tag") |
| if tag != pullCase.expectedTag { |
| return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", pullCase.expectedTag, tag) |
| } |
| return mockResponse(http.StatusOK, nil, expectedOutput)(req) |
| })) |
| assert.NilError(t, err) |
| resp, err := client.ImagePull(t.Context(), pullCase.reference, ImagePullOptions{ |
| All: pullCase.all, |
| }) |
| assert.NilError(t, err) |
| body, err := io.ReadAll(resp) |
| assert.NilError(t, err) |
| assert.Check(t, is.Equal(string(body), expectedOutput)) |
| }) |
| } |
| } |
| |
| func TestImagePullResponse(t *testing.T) { |
| r, w := io.Pipe() |
| response := internal.NewJSONMessageStream(r) |
| ctx, cancel := context.WithCancel(t.Context()) |
| messages := response.JSONMessages(ctx) |
| c := make(chan jsonstream.Message) |
| go func() { |
| for message, err := range messages { |
| if err != nil { |
| close(c) |
| break |
| } |
| c <- message |
| } |
| }() |
| |
| // Check we receive message sent to json stream |
| _, _ = w.Write([]byte(`{"id":"test"}`)) |
| ctxTO, toCancel := context.WithTimeout(t.Context(), 100*time.Millisecond) |
| defer toCancel() |
| select { |
| case message := <-c: |
| assert.Equal(t, message.ID, "test") |
| case <-ctxTO.Done(): |
| t.Fatal("expected message not received") |
| } |
| |
| // Check context cancelation |
| cancel() |
| ctxTO2, toCancel2 := context.WithTimeout(t.Context(), 100*time.Millisecond) |
| defer toCancel2() |
| select { |
| case _, ok := <-c: |
| assert.Check(t, !ok) |
| case <-ctxTO2.Done(): |
| t.Fatal("expected message not received") |
| } |
| |
| // Check that Close can be called twice without error |
| assert.NilError(t, response.Close()) |
| assert.NilError(t, response.Close()) |
| } |