Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
| ... | ... |
@@ -13,6 +13,7 @@ import ( |
| 13 | 13 |
"github.com/docker/docker/api/types" |
| 14 | 14 |
registrytypes "github.com/docker/docker/api/types/registry" |
| 15 | 15 |
"github.com/docker/docker/api/types/swarm" |
| 16 |
+ "github.com/opencontainers/go-digest" |
|
| 16 | 17 |
"github.com/opencontainers/image-spec/specs-go/v1" |
| 17 | 18 |
"golang.org/x/net/context" |
| 18 | 19 |
) |
| ... | ... |
@@ -121,3 +122,92 @@ func TestServiceCreateCompatiblePlatforms(t *testing.T) {
|
| 121 | 121 |
t.Fatalf("expected `service_amd64`, got %s", r.ID)
|
| 122 | 122 |
} |
| 123 | 123 |
} |
| 124 |
+ |
|
| 125 |
+func TestServiceCreateDigestPinning(t *testing.T) {
|
|
| 126 |
+ dgst := "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96" |
|
| 127 |
+ dgstAlt := "sha256:37ffbf3f7497c07584dc9637ffbf3f7497c0758c0537ffbf3f7497c0c88e2bb7" |
|
| 128 |
+ serviceCreateImage := "" |
|
| 129 |
+ pinByDigestTests := []struct {
|
|
| 130 |
+ img string // input image provided by the user |
|
| 131 |
+ expected string // expected image after digest pinning |
|
| 132 |
+ }{
|
|
| 133 |
+ // default registry returns familiar string |
|
| 134 |
+ {"docker.io/library/alpine", "alpine:latest@" + dgst},
|
|
| 135 |
+ // provided tag is preserved and digest added |
|
| 136 |
+ {"alpine:edge", "alpine:edge@" + dgst},
|
|
| 137 |
+ // image with provided alternative digest remains unchanged |
|
| 138 |
+ {"alpine@" + dgstAlt, "alpine@" + dgstAlt},
|
|
| 139 |
+ // image with provided tag and alternative digest remains unchanged |
|
| 140 |
+ {"alpine:edge@" + dgstAlt, "alpine:edge@" + dgstAlt},
|
|
| 141 |
+ // image on alternative registry does not result in familiar string |
|
| 142 |
+ {"alternate.registry/library/alpine", "alternate.registry/library/alpine:latest@" + dgst},
|
|
| 143 |
+ // unresolvable image does not get a digest |
|
| 144 |
+ {"cannotresolve", "cannotresolve:latest"},
|
|
| 145 |
+ } |
|
| 146 |
+ |
|
| 147 |
+ client := &Client{
|
|
| 148 |
+ client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
|
| 149 |
+ if strings.HasPrefix(req.URL.Path, "/services/create") {
|
|
| 150 |
+ // reset and set image received by the service create endpoint |
|
| 151 |
+ serviceCreateImage = "" |
|
| 152 |
+ var service swarm.ServiceSpec |
|
| 153 |
+ if err := json.NewDecoder(req.Body).Decode(&service); err != nil {
|
|
| 154 |
+ return nil, fmt.Errorf("could not parse service create request")
|
|
| 155 |
+ } |
|
| 156 |
+ serviceCreateImage = service.TaskTemplate.ContainerSpec.Image |
|
| 157 |
+ |
|
| 158 |
+ b, err := json.Marshal(types.ServiceCreateResponse{
|
|
| 159 |
+ ID: "service_id", |
|
| 160 |
+ }) |
|
| 161 |
+ if err != nil {
|
|
| 162 |
+ return nil, err |
|
| 163 |
+ } |
|
| 164 |
+ return &http.Response{
|
|
| 165 |
+ StatusCode: http.StatusOK, |
|
| 166 |
+ Body: ioutil.NopCloser(bytes.NewReader(b)), |
|
| 167 |
+ }, nil |
|
| 168 |
+ } else if strings.HasPrefix(req.URL.Path, "/distribution/cannotresolve") {
|
|
| 169 |
+ // unresolvable image |
|
| 170 |
+ return nil, fmt.Errorf("cannot resolve image")
|
|
| 171 |
+ } else if strings.HasPrefix(req.URL.Path, "/distribution/") {
|
|
| 172 |
+ // resolvable images |
|
| 173 |
+ b, err := json.Marshal(registrytypes.DistributionInspect{
|
|
| 174 |
+ Descriptor: v1.Descriptor{
|
|
| 175 |
+ Digest: digest.Digest(dgst), |
|
| 176 |
+ }, |
|
| 177 |
+ }) |
|
| 178 |
+ if err != nil {
|
|
| 179 |
+ return nil, err |
|
| 180 |
+ } |
|
| 181 |
+ return &http.Response{
|
|
| 182 |
+ StatusCode: http.StatusOK, |
|
| 183 |
+ Body: ioutil.NopCloser(bytes.NewReader(b)), |
|
| 184 |
+ }, nil |
|
| 185 |
+ } |
|
| 186 |
+ return nil, fmt.Errorf("unexpected URL '%s'", req.URL.Path)
|
|
| 187 |
+ }), |
|
| 188 |
+ } |
|
| 189 |
+ |
|
| 190 |
+ // run pin by digest tests |
|
| 191 |
+ for _, p := range pinByDigestTests {
|
|
| 192 |
+ r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{
|
|
| 193 |
+ TaskTemplate: swarm.TaskSpec{
|
|
| 194 |
+ ContainerSpec: swarm.ContainerSpec{
|
|
| 195 |
+ Image: p.img, |
|
| 196 |
+ }, |
|
| 197 |
+ }, |
|
| 198 |
+ }, types.ServiceCreateOptions{QueryRegistry: true})
|
|
| 199 |
+ |
|
| 200 |
+ if err != nil {
|
|
| 201 |
+ t.Fatal(err) |
|
| 202 |
+ } |
|
| 203 |
+ |
|
| 204 |
+ if r.ID != "service_id" {
|
|
| 205 |
+ t.Fatalf("expected `service_id`, got %s", r.ID)
|
|
| 206 |
+ } |
|
| 207 |
+ |
|
| 208 |
+ if p.expected != serviceCreateImage {
|
|
| 209 |
+ t.Fatalf("expected image %s, got %s", p.expected, serviceCreateImage)
|
|
| 210 |
+ } |
|
| 211 |
+ } |
|
| 212 |
+} |