Skip to content

Commit 015626d

Browse files
authored
Merge pull request #208 from bloodorangeio/head-requests
Add test and spec section for HEAD requests
2 parents 43d840e + 064bef5 commit 015626d

File tree

6 files changed

+97
-19
lines changed

6 files changed

+97
-19
lines changed

conformance/01_pull_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,27 @@ var test01Pull = func() {
8181
})
8282

8383
g.Context("Pull blobs", func() {
84+
g.Specify("HEAD request to nonexistent blob should result in 404 response", func() {
85+
SkipIfDisabled(pull)
86+
req := client.NewRequest(reggie.HEAD, "/v2/<name>/blobs/<digest>",
87+
reggie.WithDigest(dummyDigest))
88+
resp, err := client.Do(req)
89+
Expect(err).To(BeNil())
90+
Expect(resp.StatusCode()).To(Equal(http.StatusNotFound))
91+
})
92+
93+
g.Specify("HEAD request to existing blob should yield 200", func() {
94+
SkipIfDisabled(pull)
95+
req := client.NewRequest(reggie.HEAD, "/v2/<name>/blobs/<digest>",
96+
reggie.WithDigest(configBlobDigest))
97+
resp, err := client.Do(req)
98+
Expect(err).To(BeNil())
99+
Expect(resp.StatusCode()).To(Equal(http.StatusOK))
100+
if h := resp.Header().Get("Docker-Content-Digest"); h != "" {
101+
Expect(h).To(Equal(configBlobDigest))
102+
}
103+
})
104+
84105
g.Specify("GET nonexistent blob should result in 404 response", func() {
85106
SkipIfDisabled(pull)
86107
req := client.NewRequest(reggie.GET, "/v2/<name>/blobs/<digest>",
@@ -100,6 +121,40 @@ var test01Pull = func() {
100121
})
101122

102123
g.Context("Pull manifests", func() {
124+
g.Specify("HEAD request to nonexistent manifest should return 404", func() {
125+
SkipIfDisabled(pull)
126+
req := client.NewRequest(reggie.HEAD, "/v2/<name>/manifests/<reference>",
127+
reggie.WithReference(nonexistentManifest))
128+
resp, err := client.Do(req)
129+
Expect(err).To(BeNil())
130+
Expect(resp.StatusCode()).To(Equal(http.StatusNotFound))
131+
})
132+
133+
g.Specify("HEAD request to manifest path (digest) should yield 200 response", func() {
134+
SkipIfDisabled(pull)
135+
req := client.NewRequest(reggie.HEAD, "/v2/<name>/manifests/<digest>", reggie.WithDigest(manifestDigest)).
136+
SetHeader("Accept", "application/vnd.oci.image.manifest.v1+json")
137+
resp, err := client.Do(req)
138+
Expect(err).To(BeNil())
139+
Expect(resp.StatusCode()).To(Equal(http.StatusOK))
140+
if h := resp.Header().Get("Docker-Content-Digest"); h != "" {
141+
Expect(h).To(Equal(manifestDigest))
142+
}
143+
})
144+
145+
g.Specify("HEAD request to manifest path (tag) should yield 200 response", func() {
146+
SkipIfDisabled(pull)
147+
Expect(tag).ToNot(BeEmpty())
148+
req := client.NewRequest(reggie.GET, "/v2/<name>/manifests/<reference>", reggie.WithReference(tag)).
149+
SetHeader("Accept", "application/vnd.oci.image.manifest.v1+json")
150+
resp, err := client.Do(req)
151+
Expect(err).To(BeNil())
152+
Expect(resp.StatusCode()).To(Equal(http.StatusOK))
153+
if h := resp.Header().Get("Docker-Content-Digest"); h != "" {
154+
Expect(h).To(Equal(manifestDigest))
155+
}
156+
})
157+
103158
g.Specify("GET nonexistent manifest should return 404", func() {
104159
SkipIfDisabled(pull)
105160
req := client.NewRequest(reggie.GET, "/v2/<name>/manifests/<reference>",

conformance/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.13
44

55
require (
66
github.com/bloodorangeio/reggie v0.4.0
7+
github.com/google/uuid v1.1.2
78
github.com/onsi/ginkgo v1.11.0
89
github.com/onsi/gomega v1.8.1
910
github.com/opencontainers/go-digest v1.0.0-rc1

conformance/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ github.com/go-resty/resty/v2 v2.1.0 h1:Z6IefCpUMfnvItVJaJXWv/pMiiD11So35QgwEELsl
66
github.com/go-resty/resty/v2 v2.1.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
77
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
88
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
9+
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
10+
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
911
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
1012
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
1113
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=

conformance/reporter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ func (reporter *HTMLReporter) SpecSuiteWillBegin(config config.GinkgoConfigType,
581581
envVarTagList,
582582
envVarHideSkippedWorkflows,
583583
envVarAuthScope,
584+
envVarCrossmountNamespace,
584585
}
585586
for _, v := range varsToCheck {
586587
var replacement string

conformance/setup.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strconv"
1111

1212
"github.com/bloodorangeio/reggie"
13+
"github.com/google/uuid"
1314
g "github.com/onsi/ginkgo"
1415
godigest "github.com/opencontainers/go-digest"
1516
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -139,6 +140,9 @@ func init() {
139140
password := os.Getenv(envVarPassword)
140141
authScope := os.Getenv(envVarAuthScope)
141142
crossmountNamespace = os.Getenv(envVarCrossmountNamespace)
143+
if len(crossmountNamespace) == 0 {
144+
crossmountNamespace = fmt.Sprintf("conformance-%s", uuid.New())
145+
}
142146

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

spec.md

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ Typically, the first step in pulling an artifact is to retrieve the manifest. Ho
128128

129129
##### Pulling manifests
130130

131-
To pull a manifest, perform a `GET` request to a url in the following form:
131+
To pull a manifest, perform a `GET` request to a URL in the following form:
132132
`/v2/<name>/manifests/<reference>` <sup>[end-3](#endpoints)</sup>
133133

134134
`<name>` refers to the namespace of the repository. `<reference>` MUST be either (a) the digest of the manifest or (b) a tag name.
@@ -137,26 +137,41 @@ The `<reference>` MUST NOT be in any other format. Throughout this document, `<n
137137
`[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*`
138138

139139
A GET request to an existing manifest URL MUST provide the expected manifest, with a response code that MUST be `200 OK`.
140+
A successful response SHOULD contain the digest of the uploaded blob in the header `Docker-Content-Digest`.
140141

141-
The `OCI-Content-Digest` header (or, as a fallback, `Docker-Content-Digest` header), if present on the response, returns the canonical
142+
The `Docker-Content-Digest` header, if present on the response, returns the canonical
142143
digest of the uploaded blob which MAY differ from the provided digest. If the digest does differ, it MAY be the case that
143144
the hashing algorithms used do not match. See [Content Digests](./detail.md) for information on how to detect the hashing
144145
algorithm in use. Most clients MAY ignore the value, but if it is used, the client MUST verify the value against the uploaded
145146
blob data.
146147

147148
If the manifest is not found in the registry, the response code MUST be `404 Not Found`.
148149

149-
##### Pulling Blobs
150+
##### Pulling blobs
150151

151-
To pull a blob, perform a `GET` request to a url in the following form:
152+
To pull a blob, perform a `GET` request to a URL in the following form:
152153
`/v2/<name>/blobs/<digest>` <sup>[end-2](#endpoints)</sup>
153154

154155
`<name>` is the namespace of the repository, and `<digest>` is the blob's digest.
155156

156157
A GET request to an existing blob URL MUST provide the expected blob, with a response code that MUST be `200 OK`.
158+
A successful response SHOULD contain the digest of the uploaded blob in the header `Docker-Content-Digest`. If present,
159+
the value of this header MUST be a digest matching that of the response body.
157160

158161
If the blob is not found in the registry, the response code MUST be `404 Not Found`.
159162

163+
##### Checking if content exists in the registry
164+
165+
In order to verify that a repository contains a given manifest or blob, make a `HEAD` request to a URL in the following form:
166+
167+
`/v2/<name>/manifests/<reference>` <sup>[end-3](#endpoints)</sup> (for manifests), or
168+
169+
`/v2/<name>/blobs/<digest>` <sup>[end-2](#endpoints)</sup> (for blobs).
170+
171+
A HEAD request to an existing blob or manifest URL MUST return `200 OK`. A successful response SHOULD contain the digest
172+
of the uploaded blob in the header `Docker-Content-Digest`.
173+
174+
If the blob or manifest is not found in the registry, the response code MUST be `404 Not Found`.
160175

161176
#### Push
162177

@@ -452,21 +467,21 @@ of this specification.
452467

453468
#### Endpoints
454469

455-
| ID | Method | API endpoint | Accepted Successful Response Codes | Accepted Failure Response Codes |
456-
| ------ | -------- | ----------------------------------------------------------------- | ---------------------------------- | ------------------------------- |
457-
| end-1 | `GET` | `/v2/` | `200` | `404`/`401` |
458-
| end-2 | `GET` | `/v2/<name>/blobs/<digest>` | `200` | `404` |
459-
| end-3 | `GET` | `/v2/<name>/manifests/<reference>` | `200` | `404` |
460-
| end-4a | `POST` | `/v2/<name>/blobs/uploads/` | `202` | `404` |
461-
| end-4b | `POST` | `/v2/<name>/blobs/uploads/?digest=<digest>` | `201`/`202` | `404`/`400` |
462-
| end-5 | `PATCH` | `/v2/<name>/blobs/uploads/<reference>` | `202` | `404`/`416` |
463-
| end-6 | `PUT` | `/v2/<name>/blobs/uploads/<reference>?digest=<digest>` | `201` | `404`/`400` |
464-
| end-7 | `PUT` | `/v2/<name>/manifests/<reference>` | `201` | `404` |
465-
| end-8a | `GET` | `/v2/<name>/tags/list` | `200` | `404` |
466-
| end-8b | `GET` | `/v2/<name>/tags/list?n=<integer>&last=<integer>` | `200` | `404` |
467-
| end-9 | `DELETE` | `/v2/<name>/manifests/<reference>` | `202` | `404`/`400`/`405` |
468-
| end-10 | `DELETE` | `/v2/<name>/blobs/<digest>` | `202` | `404`/`405` |
469-
| end-11 | `POST` | `/v2/<name>/blobs/uploads/?mount=<digest>&from=<other_namespace>` | `201` | `404` |
470+
| ID | Method | API endpoint | Accepted Successful Response Codes | Accepted Failure Response Codes |
471+
| ------ | -------------- | ----------------------------------------------------------------- | ---------------------------------- | ------------------------------- |
472+
| end-1 | `GET` | `/v2/` | `200` | `404`/`401` |
473+
| end-2 | `GET` / `HEAD` | `/v2/<name>/blobs/<digest>` | `200` | `404` |
474+
| end-3 | `GET` / `HEAD` | `/v2/<name>/manifests/<reference>` | `200` | `404` |
475+
| end-4a | `POST` | `/v2/<name>/blobs/uploads/` | `202` | `404` |
476+
| end-4b | `POST` | `/v2/<name>/blobs/uploads/?digest=<digest>` | `201`/`202` | `404`/`400` |
477+
| end-5 | `PATCH` | `/v2/<name>/blobs/uploads/<reference>` | `202` | `404`/`416` |
478+
| end-6 | `PUT` | `/v2/<name>/blobs/uploads/<reference>?digest=<digest>` | `201` | `404`/`400` |
479+
| end-7 | `PUT` | `/v2/<name>/manifests/<reference>` | `201` | `404` |
480+
| end-8a | `GET` | `/v2/<name>/tags/list` | `200` | `404` |
481+
| end-8b | `GET` | `/v2/<name>/tags/list?n=<integer>&last=<integer>` | `200` | `404` |
482+
| end-9 | `DELETE` | `/v2/<name>/manifests/<reference>` | `202` | `404`/`400`/`405` |
483+
| end-10 | `DELETE` | `/v2/<name>/blobs/<digest>` | `202` | `404`/`405` |
484+
| end-11 | `POST` | `/v2/<name>/blobs/uploads/?mount=<digest>&from=<other_namespace>` | `201` | `404` |
470485

471486
#### Error Codes
472487

0 commit comments

Comments
 (0)