Skip to content

Commit

Permalink
Add cross-repository mounting section to spec/test (#204)
Browse files Browse the repository at this point in the history
* Add cross-repository mounting section to spec/test

* New test in 02_push_test.go
* Section added to spec.md detailing cross-repository mounting
* Section added to conformance/README.md on new configuration options
for cross-repository mounting

Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>

* Require cross-mounting

Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>

* Address PR comments

Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
  • Loading branch information
pmengelbert committed Dec 1, 2020
1 parent 703802f commit 55ebd32
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 20 deletions.
37 changes: 37 additions & 0 deletions conformance/02_push_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,43 @@ var test02Push = func() {
})
})

g.Context("Cross-Repository Blob Mount", func() {
g.Specify("POST request to mount another repository's blob should return 201 or 202", func() {
SkipIfDisabled(push)
req := client.NewRequest(reggie.POST, "/v2/<name>/blobs/uploads/",
reggie.WithName(crossmountNamespace)).
SetQueryParam("mount", testBlobADigest).
SetQueryParam("from", client.Config.DefaultName)
resp, err := client.Do(req)
Expect(err).To(BeNil())
Expect(resp.StatusCode()).To(SatisfyAny(
Equal(http.StatusCreated),
Equal(http.StatusAccepted),
))
Expect(resp.GetRelativeLocation()).To(ContainSubstring(crossmountNamespace))

lastResponse = resp
})

g.Specify("GET request to test digest within cross-mount namespace should return 200", func() {
SkipIfDisabled(push)
RunOnlyIf(lastResponse.StatusCode() == http.StatusCreated)

req := client.NewRequest(reggie.GET, lastResponse.GetRelativeLocation())
resp, err := client.Do(req)
Expect(err).To(BeNil())
Expect(resp.StatusCode()).To(Equal(http.StatusOK))
})

g.Specify("Cross-mounting of nonexistent blob should yield session id", func() {
SkipIfDisabled(push)
RunOnlyIf(lastResponse.StatusCode() == http.StatusAccepted)

loc := lastResponse.GetRelativeLocation()
Expect(loc).To(ContainSubstring("/blobs/uploads/"))
})
})

g.Context("Manifest Upload", func() {
g.Specify("GET nonexistent manifest should return 404", func() {
SkipIfDisabled(push)
Expand Down
10 changes: 10 additions & 0 deletions conformance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Next, set environment variables with your registry details:
# Registry details
export OCI_ROOT_URL="https://r.myreg.io"
export OCI_NAMESPACE="myorg/myrepo"
export OCI_CROSSMOUNT_NAMESPACE="myorg/other"
export OCI_USERNAME="myuser"
export OCI_PASSWORD="mypass"
Expand Down Expand Up @@ -105,6 +106,15 @@ environment:
OCI_SKIP_EMPTY_LAYER_PUSH_TEST=1
```

The test suite will need access to a second namespace. This namespace is used to check support for cross-repository mounting
of blobs, and may need to be configured on the server-side in advance. It is specified by setting the following in
the environment:

```
# The destination repository for cross-repository mounting:
OCI_CROSSMOUNT_NAMESPACE="myorg/other"
```

##### Content Discovery

The Content Discovery tests validate that the contents of a registry can be discovered.
Expand Down
15 changes: 9 additions & 6 deletions conformance/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ type (
)

const (
pull = 1 << iota
push
contentDiscovery
contentManagement

BLOB_UNKNOWN = iota
BLOB_UPLOAD_INVALID
BLOB_UPLOAD_UNKNOWN
Expand Down Expand Up @@ -57,6 +62,7 @@ const (
envVarHideSkippedWorkflows = "OCI_HIDE_SKIPPED_WORKFLOWS"
envVarAuthScope = "OCI_AUTH_SCOPE"
envVarDeleteManifestBeforeBlobs = "OCI_DELETE_MANIFEST_BEFORE_BLOBS"
envVarCrossmountNamespace = "OCI_CROSSMOUNT_NAMESPACE"

emptyLayerTestTag = "emptylayer"
testTagName = "tagtest0"
Expand All @@ -66,11 +72,6 @@ const (
titleContentDiscovery = "Content Discovery"
titleContentManagement = "Content Management"

pull = 1 << iota
push
contentDiscovery
contentManagement

// layerBase64String is a base64 encoding of a simple tarball, obtained like this:
// $ echo 'you bothered to find out what was in here. Congratulations!' > test.txt
// $ tar czvf test.tar.gz test.txt
Expand Down Expand Up @@ -103,6 +104,7 @@ var (
client *reggie.Client
configBlobContent []byte
configBlobContentLength string
crossmountNamespace string
dummyDigest string
errorCodes []string
invalidManifestContent []byte
Expand All @@ -111,8 +113,8 @@ var (
layerBlobContentLength string
manifestContent []byte
manifestDigest string
emptyLayerManifestContent []byte
emptyLayerManifestDigest string
emptyLayerManifestContent []byte
nonexistentManifest string
reportJUnitFilename string
reportHTMLFilename string
Expand All @@ -136,6 +138,7 @@ func init() {
username := os.Getenv(envVarUsername)
password := os.Getenv(envVarPassword)
authScope := os.Getenv(envVarAuthScope)
crossmountNamespace = os.Getenv(envVarCrossmountNamespace)

debug, _ := strconv.ParseBool(os.Getenv(envVarDebug))

Expand Down
52 changes: 38 additions & 14 deletions spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,29 @@ Location: <blob-location>
Here, `<blob-location>` is a pullable blob URL.


##### Mounting a blob from another repository
If a necessary blob exists already in another repository, it can be mounted into a different repository via a `POST`
request in the following format:

`/v2/<name>/blobs/uploads/?mount=<digest>&from=<other_namespace>` <sup>[end-11](#endpoints)</sup>.

In this case, `<name>` is the namespace to which the blob will be mounted. `<digest>` is the digest of the blob to mount,
and `<other_namespace>` is the namespace from which the blob should be mounted.

The response to a successful mount MUST be `201 Created`, and MUST contain the following header:
```
Location: <blob-location>
```

The digest contained in the `Location` header MAY be different from that of the blob that was mounted. As such, a client
SHOULD use the digest found in the path from this header and SHOULD NOT use the digest of the blob that was mounted.

The response to an unsuccessful mount MUST be `202 Accepted`, and be handled in the same way as a `POST` request to
`/v2/<name>/blobs/uploads/`<sup>[end-4a](#endpoints)</sup>. That is, it MUST contain the following header, in the following format:
```
Location: /v2/<name>/blobs/uploads/<session-id>
```

##### Pushing Manifests

To push a manifest, perform a `PUT` request to a path in the following format, and with the following headers
Expand Down Expand Up @@ -414,20 +437,21 @@ of this specification.

#### Endpoints

| ID | Method | API endpoint | Accepted Successful Response Codes | Accepted Failure Response Codes |
| ------ | -------- | ------------------------------------------------------ | ---------------------------------- | ------------------------------- |
| end-1 | `GET` | `/v2/` | `200` | `404`/`401` |
| end-2 | `GET` | `/v2/<name>/blobs/<digest>` | `200` | `404` |
| end-3 | `GET` | `/v2/<name>/manifests/<reference>` | `200` | `404` |
| end-4a | `POST` | `/v2/<name>/blobs/uploads/` | `202` | `404` |
| end-4b | `POST` | `/v2/<name>/blobs/uploads/?digest=<digest>` | `201`/`202` | `404`/`400` |
| end-5 | `PATCH` | `/v2/<name>/blobs/uploads/<reference>` | `202` | `404`/`416` |
| end-6 | `PUT` | `/v2/<name>/blobs/uploads/<reference>?digest=<digest>` | `201` | `404`/`400` |
| end-7 | `PUT` | `/v2/<name>/manifests/<reference>` | `201` | `404` |
| end-8a | `GET` | `/v2/<name>/tags/list` | `200` | `404` |
| end-8b | `GET` | `/v2/<name>/tags/list?n=<integer>&last=<integer>` | `200` | `404` |
| end-9 | `DELETE` | `/v2/<name>/manifests/<reference>` | `202` | `404`/`400`/`405` |
| end-10 | `DELETE` | `/v2/<name>/blobs/<digest>` | `202` | `404`/`405` |
| ID | Method | API endpoint | Accepted Successful Response Codes | Accepted Failure Response Codes |
| ------ | -------- | ----------------------------------------------------------------- | ---------------------------------- | ------------------------------- |
| end-1 | `GET` | `/v2/` | `200` | `404`/`401` |
| end-2 | `GET` | `/v2/<name>/blobs/<digest>` | `200` | `404` |
| end-3 | `GET` | `/v2/<name>/manifests/<reference>` | `200` | `404` |
| end-4a | `POST` | `/v2/<name>/blobs/uploads/` | `202` | `404` |
| end-4b | `POST` | `/v2/<name>/blobs/uploads/?digest=<digest>` | `201`/`202` | `404`/`400` |
| end-5 | `PATCH` | `/v2/<name>/blobs/uploads/<reference>` | `202` | `404`/`416` |
| end-6 | `PUT` | `/v2/<name>/blobs/uploads/<reference>?digest=<digest>` | `201` | `404`/`400` |
| end-7 | `PUT` | `/v2/<name>/manifests/<reference>` | `201` | `404` |
| end-8a | `GET` | `/v2/<name>/tags/list` | `200` | `404` |
| end-8b | `GET` | `/v2/<name>/tags/list?n=<integer>&last=<integer>` | `200` | `404` |
| end-9 | `DELETE` | `/v2/<name>/manifests/<reference>` | `202` | `404`/`400`/`405` |
| end-10 | `DELETE` | `/v2/<name>/blobs/<digest>` | `202` | `404`/`405` |
| end-11 | `POST` | `/v2/<name>/blobs/uploads/?mount=<digest>&from=<other_namespace>` | `201` | `404` |

#### Error Codes

Expand Down

0 comments on commit 55ebd32

Please sign in to comment.