Skip to content
This repository was archived by the owner on Nov 10, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions registry/blob.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package registry

import (
"fmt"
"io"
"net/http"
"net/url"
Expand All @@ -21,6 +22,58 @@ func (registry *Registry) DownloadBlob(repository string, digest digest.Digest)
return resp.Body, nil
}

// Sending Monolithic chunked upload - following docker API specification for Chunked uploads : https://docs.docker.com/registry/spec/api/#listing-repositories
// See UploadBlob for more info about getBody
func (registry *Registry) UploadBlobToArtifactory(repository string, digest digest.Digest, content io.Reader, getBody func() (io.ReadCloser, error)) error {
uploadUrl, err := registry.initiateUpload(repository)
if err != nil {
return err
}
q := uploadUrl.Query()
q.Set("digest", digest.String())
uploadUrl.RawQuery = q.Encode()

registry.Logf("registry.blob.uploadToArtifactory url=%s repository=%s digest=%s", uploadUrl, repository, digest)

uploadStep1, err := http.NewRequest("PATCH", uploadUrl.String(), content)
if err != nil {
return err
}
uploadStep1.Header.Set("Content-Type", "application/octet-stream")
if getBody != nil {
uploadStep1.GetBody = getBody
}
resp1, err := registry.Client.Do(uploadStep1)
if resp1 != nil {
defer resp1.Body.Close()
}
// TODO: retry upload more than 0 bytes were successfully transferred
// (HEAD upload UUID, adn check the Range header)
if err != nil {
if resp1 == nil {
return fmt.Errorf("error while uploading blob to %s, digest: %s: %s", repository, digest, err)

} else {
return fmt.Errorf("error while uploading blob to %s: %v %v: digest: %s: %s", repository, resp1.StatusCode, resp1.Status, digest, err)
}
}
if resp1.StatusCode != 202 {
return fmt.Errorf("unexpected PATCH response while uploading blob to %s: %v %v: digest: %s", repository, resp1.StatusCode, resp1.Status, digest)
}

uploadStep2, err := http.NewRequest("PUT", uploadUrl.String(), nil)
if err != nil {
return err
}
uploadStep2.Header.Set("Content-Type", "application/octet-stream")
if getBody != nil {
uploadStep2.GetBody = getBody
}

_, err = registry.Client.Do(uploadStep2)
return err
}

// UploadBlob can be used to upload an FS layer or an image config file into the given repository.
// It uploads the bytes read from content. Digest must match with the hash of those bytes.
// In case of token authentication the HTTP request must be retried after a 401 Unauthorized response
Expand Down
15 changes: 13 additions & 2 deletions registry/blob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ func TestRegistry_UploadBlob(t *testing.T) {

foreachWritableTestcase(t, func(t *testing.T, tc *TestCase) {
content := bytes.NewBuffer(blobData)
err := tc.Registry(t).UploadBlob(tc.Repository, digest, content, nil)
var err error
if !tc.Artifactory {
err = tc.Registry(t).UploadBlob(tc.Repository, digest, content, nil)
} else {
err = tc.Registry(t).UploadBlobToArtifactory(tc.Repository, digest, content, nil)
}

if err != nil {
t.Error("UploadBlob() failed:", err)
}
Expand Down Expand Up @@ -44,7 +50,12 @@ func TestRegistry_UploadBlobFromFile(t *testing.T) {
t.Fatal(err)
}

err = tc.Registry(t).UploadBlob(tc.Repository, digest, blobReader, body)
if !tc.Artifactory {
err = tc.Registry(t).UploadBlob(tc.Repository, digest, blobReader, body)
} else {
err = tc.Registry(t).UploadBlobToArtifactory(tc.Repository, digest, blobReader, body)
}

if err != nil {
t.Error("UploadBlob() failed:", err)
}
Expand Down
31 changes: 27 additions & 4 deletions registry/testing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"

"github.com/docker/distribution"

"github.com/nokia/docker-registry-client/registry"
"github.com/opencontainers/go-digest"
)
Expand All @@ -25,10 +26,11 @@ type Expected struct {

// TestCase represents a test case (normally read from a test data file).
type TestCase struct {
Url string `json:"url"`
Repository string `json:"repository"`
Reference string `json:"reference"`
Writeable bool `json:"writeable,omitempty"`
Url string `json:"url"`
Repository string `json:"repository"`
Reference string `json:"reference"`
Writeable bool `json:"writeable,omitempty"`
Artifactory bool

registry.Options
registry *registry.Registry
Expand Down Expand Up @@ -102,6 +104,27 @@ func testCases(t *testing.T) []*TestCase {
}
_testCases = append(_testCases, tc)
}

//add testcase for Artifactory if credentials are given in environment variables
artifactoryUsername := os.Getenv("DRC_TEST_ARTIFACTORY_USERNAME")
artifactoryPassword := os.Getenv("DRC_TEST_ARTIFACTORY_PASSWORD")
artifactoryUrl := os.Getenv("DRC_TEST_ARTIFACTORY_REGISTRY")
artifactoryRepo := os.Getenv("DRC_TEST_ARTIFACTORY_REPO")
if artifactoryUsername != "" && artifactoryPassword != "" && artifactoryUrl != "" && artifactoryRepo != "" {
tc := &TestCase{
Url: artifactoryUrl,
Repository: artifactoryRepo,
Reference: "latest",
Writeable: true,
Artifactory: true,
Options: registry.Options{
Username: username,
Password: password,
DoInitialPing: false,
},
}
_testCases = append(_testCases, tc)
}
return _testCases
}

Expand Down