addressing comments from stevvooe
Signed-off-by: Mike Brown <brownwm@us.ibm.com>master
							parent
							
								
									c1532332ad
								
							
						
					
					
						commit
						ec2aa05cdf
					
				|  | @ -10,7 +10,6 @@ import ( | ||||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // TODO (not assigned): consider making mockBlobService common for ocischema, schema2, and schema1
 |  | ||||||
| type mockBlobService struct { | type mockBlobService struct { | ||||||
| 	descriptors map[digest.Digest]distribution.Descriptor | 	descriptors map[digest.Digest]distribution.Descriptor | ||||||
| } | } | ||||||
|  | @ -50,64 +49,45 @@ func (bs *mockBlobService) Resume(ctx context.Context, id string) (distribution. | ||||||
| 
 | 
 | ||||||
| func TestBuilder(t *testing.T) { | func TestBuilder(t *testing.T) { | ||||||
| 	imgJSON := []byte(`{ | 	imgJSON := []byte(`{ | ||||||
|  |     "created": "2015-10-31T22:22:56.015925234Z", | ||||||
|  |     "author": "Alyssa P. Hacker <alyspdev@example.com>", | ||||||
|     "architecture": "amd64", |     "architecture": "amd64", | ||||||
|  |     "os": "linux", | ||||||
|     "config": { |     "config": { | ||||||
|         "AttachStderr": false, |         "User": "alice", | ||||||
|         "AttachStdin": false, |         "ExposedPorts": { | ||||||
|         "AttachStdout": false, |             "8080/tcp": {} | ||||||
|         "Cmd": [ |         }, | ||||||
|             "/bin/sh", |  | ||||||
|             "-c", |  | ||||||
|             "echo hi" |  | ||||||
|         ], |  | ||||||
|         "Domainname": "", |  | ||||||
|         "Entrypoint": null, |  | ||||||
|         "Env": [ |         "Env": [ | ||||||
|             "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", |             "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", | ||||||
|             "derived=true", |             "FOO=oci_is_a", | ||||||
|             "asdf=true" |             "BAR=well_written_spec" | ||||||
|  |         ], | ||||||
|  |         "Entrypoint": [ | ||||||
|  |             "/bin/my-app-binary" | ||||||
|         ], |         ], | ||||||
|         "Hostname": "23304fc829f9", |  | ||||||
|         "Image": "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246", |  | ||||||
|         "Labels": {}, |  | ||||||
|         "OnBuild": [], |  | ||||||
|         "OpenStdin": false, |  | ||||||
|         "StdinOnce": false, |  | ||||||
|         "Tty": false, |  | ||||||
|         "User": "", |  | ||||||
|         "Volumes": null, |  | ||||||
|         "WorkingDir": "" |  | ||||||
|     }, |  | ||||||
|     "container": "e91032eb0403a61bfe085ff5a5a48e3659e5a6deae9f4d678daa2ae399d5a001", |  | ||||||
|     "container_config": { |  | ||||||
|         "AttachStderr": false, |  | ||||||
|         "AttachStdin": false, |  | ||||||
|         "AttachStdout": false, |  | ||||||
|         "Cmd": [ |         "Cmd": [ | ||||||
|             "/bin/sh", |             "--foreground", | ||||||
|             "-c", |             "--config", | ||||||
|             "#(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]" |             "/etc/my-app.d/default.cfg" | ||||||
|         ], |         ], | ||||||
|         "Domainname": "", |         "Volumes": { | ||||||
|         "Entrypoint": null, |             "/var/job-result-data": {}, | ||||||
|         "Env": [ |             "/var/log/my-app-logs": {} | ||||||
|             "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", |         }, | ||||||
|             "derived=true", |         "WorkingDir": "/home/alice", | ||||||
|             "asdf=true" |         "Labels": { | ||||||
|         ], |             "com.example.project.git.url": "https://example.com/project.git", | ||||||
|         "Hostname": "23304fc829f9", |             "com.example.project.git.commit": "45a939b2999782a3f005621a8d0f29aa387e1d6b" | ||||||
|         "Image": "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246", |         } | ||||||
|         "Labels": {}, |     }, | ||||||
|         "OnBuild": [], |     "rootfs": { | ||||||
|         "OpenStdin": false, |       "diff_ids": [ | ||||||
|         "StdinOnce": false, |         "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1", | ||||||
|         "Tty": false, |         "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" | ||||||
|         "User": "", |       ], | ||||||
|         "Volumes": null, |       "type": "layers" | ||||||
|         "WorkingDir": "" |  | ||||||
|     }, |     }, | ||||||
|     "created": "2015-11-04T23:06:32.365666163Z", |  | ||||||
|     "docker_version": "1.9.0-dev", |  | ||||||
|     "history": [ |     "history": [ | ||||||
|       { |       { | ||||||
|         "created": "2015-10-31T22:22:54.690851953Z", |         "created": "2015-10-31T22:22:54.690851953Z", | ||||||
|  | @ -115,37 +95,10 @@ func TestBuilder(t *testing.T) { | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         "created": "2015-10-31T22:22:55.613815829Z", |         "created": "2015-10-31T22:22:55.613815829Z", | ||||||
|             "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]" |         "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]", | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             "created": "2015-11-04T23:06:30.934316144Z", |  | ||||||
|             "created_by": "/bin/sh -c #(nop) ENV derived=true", |  | ||||||
|             "empty_layer": true |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             "created": "2015-11-04T23:06:31.192097572Z", |  | ||||||
|             "created_by": "/bin/sh -c #(nop) ENV asdf=true", |  | ||||||
|             "empty_layer": true |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             "created": "2015-11-04T23:06:32.083868454Z", |  | ||||||
|             "created_by": "/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024" |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             "created": "2015-11-04T23:06:32.365666163Z", |  | ||||||
|             "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]", |  | ||||||
|         "empty_layer": true |         "empty_layer": true | ||||||
|       } |       } | ||||||
|     ], |     ] | ||||||
|     "os": "linux", |  | ||||||
|     "rootfs": { |  | ||||||
|         "diff_ids": [ |  | ||||||
|             "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1", |  | ||||||
|             "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef", |  | ||||||
|             "sha256:13f53e08df5a220ab6d13c58b2bf83a59cbdc2e04d0a3f041ddf4b0ba4112d49" |  | ||||||
|         ], |  | ||||||
|         "type": "layers" |  | ||||||
|     } |  | ||||||
| }`) | }`) | ||||||
| 	configDigest := digest.FromBytes(imgJSON) | 	configDigest := digest.FromBytes(imgJSON) | ||||||
| 
 | 
 | ||||||
|  | @ -154,6 +107,7 @@ func TestBuilder(t *testing.T) { | ||||||
| 			Digest:      digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"), | 			Digest:      digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"), | ||||||
| 			Size:        5312, | 			Size:        5312, | ||||||
| 			MediaType:   v1.MediaTypeImageLayerGzip, | 			MediaType:   v1.MediaTypeImageLayerGzip, | ||||||
|  | 			Annotations: map[string]string{"apple": "orange", "lettuce": "wrap"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Digest:    digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"), | 			Digest:    digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"), | ||||||
|  | @ -200,7 +154,7 @@ func TestBuilder(t *testing.T) { | ||||||
| 	if target.MediaType != v1.MediaTypeImageConfig { | 	if target.MediaType != v1.MediaTypeImageConfig { | ||||||
| 		t.Fatalf("unexpected media type in target: %s", target.MediaType) | 		t.Fatalf("unexpected media type in target: %s", target.MediaType) | ||||||
| 	} | 	} | ||||||
| 	if target.Size != 3153 { | 	if target.Size != 1582 { | ||||||
| 		t.Fatalf("unexpected size in target: %d", target.Size) | 		t.Fatalf("unexpected size in target: %d", target.Size) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ var ( | ||||||
| 	// SchemaVersion provides a pre-initialized version structure for this
 | 	// SchemaVersion provides a pre-initialized version structure for this
 | ||||||
| 	// packages version of the manifest.
 | 	// packages version of the manifest.
 | ||||||
| 	SchemaVersion = manifest.Versioned{ | 	SchemaVersion = manifest.Versioned{ | ||||||
| 		SchemaVersion: 2, // TODO: (mikebrow/stevvooe) this could be confusing cause oci version 1 is closer to docker 2 than 1
 | 		SchemaVersion: 2, // historical value here.. does not pertain to OCI or docker version
 | ||||||
| 		MediaType:     v1.MediaTypeImageManifest, | 		MediaType:     v1.MediaTypeImageManifest, | ||||||
| 	} | 	} | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -17,13 +17,19 @@ var expectedManifestSerialization = []byte(`{ | ||||||
|    "config": { |    "config": { | ||||||
|       "mediaType": "application/vnd.oci.image.config.v1+json", |       "mediaType": "application/vnd.oci.image.config.v1+json", | ||||||
|       "size": 985, |       "size": 985, | ||||||
|       "digest": "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b" |       "digest": "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", | ||||||
|  |       "annotations": { | ||||||
|  |          "apple": "orange" | ||||||
|  |       } | ||||||
|    }, |    }, | ||||||
|    "layers": [ |    "layers": [ | ||||||
|       { |       { | ||||||
|          "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", |          "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", | ||||||
|          "size": 153263, |          "size": 153263, | ||||||
|          "digest": "sha256:62d8908bee94c202b2d35224a221aaa2058318bfa9879fa541efaecba272331b" |          "digest": "sha256:62d8908bee94c202b2d35224a221aaa2058318bfa9879fa541efaecba272331b", | ||||||
|  |          "annotations": { | ||||||
|  |             "lettuce": "wrap" | ||||||
|  |          } | ||||||
|       } |       } | ||||||
|    ] |    ] | ||||||
| }`) | }`) | ||||||
|  | @ -35,12 +41,14 @@ func TestManifest(t *testing.T) { | ||||||
| 			Digest:      "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", | 			Digest:      "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", | ||||||
| 			Size:        985, | 			Size:        985, | ||||||
| 			MediaType:   v1.MediaTypeImageConfig, | 			MediaType:   v1.MediaTypeImageConfig, | ||||||
|  | 			Annotations: map[string]string{"apple": "orange"}, | ||||||
| 		}, | 		}, | ||||||
| 		Layers: []distribution.Descriptor{ | 		Layers: []distribution.Descriptor{ | ||||||
| 			{ | 			{ | ||||||
| 				Digest:      "sha256:62d8908bee94c202b2d35224a221aaa2058318bfa9879fa541efaecba272331b", | 				Digest:      "sha256:62d8908bee94c202b2d35224a221aaa2058318bfa9879fa541efaecba272331b", | ||||||
| 				Size:        153263, | 				Size:        153263, | ||||||
| 				MediaType:   v1.MediaTypeImageLayerGzip, | 				MediaType:   v1.MediaTypeImageLayerGzip, | ||||||
|  | 				Annotations: map[string]string{"lettuce": "wrap"}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  | @ -90,6 +98,9 @@ func TestManifest(t *testing.T) { | ||||||
| 	if target.Size != 985 { | 	if target.Size != 985 { | ||||||
| 		t.Fatalf("unexpected size in target: %d", target.Size) | 		t.Fatalf("unexpected size in target: %d", target.Size) | ||||||
| 	} | 	} | ||||||
|  | 	if target.Annotations["apple"] != "orange" { | ||||||
|  | 		t.Fatalf("unexpected annotation in target: %s", target.Annotations["apple"]) | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	references := deserialized.References() | 	references := deserialized.References() | ||||||
| 	if len(references) != 2 { | 	if len(references) != 2 { | ||||||
|  | @ -110,4 +121,7 @@ func TestManifest(t *testing.T) { | ||||||
| 	if references[1].Size != 153263 { | 	if references[1].Size != 153263 { | ||||||
| 		t.Fatalf("unexpected size in reference: %d", references[0].Size) | 		t.Fatalf("unexpected size in reference: %d", references[0].Size) | ||||||
| 	} | 	} | ||||||
|  | 	if references[1].Annotations["lettuce"] != "wrap" { | ||||||
|  | 		t.Fatalf("unexpected annotation in reference: %s", references[1].Annotations["lettuce"]) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -122,8 +122,7 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) | ||||||
| 
 | 
 | ||||||
| 	if imh.Tag != "" { | 	if imh.Tag != "" { | ||||||
| 		tags := imh.Repository.Tags(imh) | 		tags := imh.Repository.Tags(imh) | ||||||
| 		var desc distribution.Descriptor | 		desc, err := tags.Get(imh, imh.Tag) | ||||||
| 		desc, err = tags.Get(imh, imh.Tag) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			if _, ok := err.(distribution.ErrTagUnknown); ok { | 			if _, ok := err.(distribution.ErrTagUnknown); ok { | ||||||
| 				imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err)) | 				imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err)) | ||||||
|  | @ -144,8 +143,7 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) | ||||||
| 	if imh.Tag != "" { | 	if imh.Tag != "" { | ||||||
| 		options = append(options, distribution.WithTag(imh.Tag)) | 		options = append(options, distribution.WithTag(imh.Tag)) | ||||||
| 	} | 	} | ||||||
| 	var manifest distribution.Manifest | 	manifest, err := manifests.Get(imh, imh.Digest, options...) | ||||||
| 	manifest, err = manifests.Get(imh, imh.Digest, options...) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if _, ok := err.(distribution.ErrManifestUnknownRevision); ok { | 		if _, ok := err.(distribution.ErrManifestUnknownRevision); ok { | ||||||
| 			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err)) | 			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err)) | ||||||
|  | @ -266,7 +264,7 @@ func (imh *manifestHandler) convertSchema2Manifest(schema2Manifest *schema2.Dese | ||||||
| 
 | 
 | ||||||
| 	builder := schema1.NewConfigManifestBuilder(imh.Repository.Blobs(imh), imh.Context.App.trustKey, ref, configJSON) | 	builder := schema1.NewConfigManifestBuilder(imh.Repository.Blobs(imh), imh.Context.App.trustKey, ref, configJSON) | ||||||
| 	for _, d := range schema2Manifest.Layers { | 	for _, d := range schema2Manifest.Layers { | ||||||
| 		if err = builder.AppendReference(d); err != nil { | 		if err := builder.AppendReference(d); err != nil { | ||||||
| 			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err)) | 			imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err)) | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  | @ -339,7 +337,7 @@ func (imh *manifestHandler) PutManifest(w http.ResponseWriter, r *http.Request) | ||||||
| 		options = append(options, distribution.WithTag(imh.Tag)) | 		options = append(options, distribution.WithTag(imh.Tag)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err = imh.applyResourcePolicy(manifest); err != nil { | 	if err := imh.applyResourcePolicy(manifest); err != nil { | ||||||
| 		imh.Errors = append(imh.Errors, err) | 		imh.Errors = append(imh.Errors, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ package storage | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/url" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/docker/distribution" | 	"github.com/docker/distribution" | ||||||
| 	"github.com/docker/distribution/context" | 	"github.com/docker/distribution/context" | ||||||
|  | @ -80,23 +79,6 @@ func (ms *ocischemaManifestHandler) verifyManifest(ctx context.Context, mnfst oc | ||||||
| 		var err error | 		var err error | ||||||
| 
 | 
 | ||||||
| 		switch descriptor.MediaType { | 		switch descriptor.MediaType { | ||||||
| 		// TODO: mikebrow/steveoe verify we should treat oci nondistributable like foreign layers?
 |  | ||||||
| 		case v1.MediaTypeImageLayerNonDistributable, v1.MediaTypeImageLayerNonDistributableGzip: |  | ||||||
| 			// Clients download this layer from an external URL, so do not check for
 |  | ||||||
| 			// its presence.
 |  | ||||||
| 			if len(descriptor.URLs) == 0 { |  | ||||||
| 				err = errMissingURL |  | ||||||
| 			} |  | ||||||
| 			allow := ms.manifestURLs.allow |  | ||||||
| 			deny := ms.manifestURLs.deny |  | ||||||
| 			for _, u := range descriptor.URLs { |  | ||||||
| 				var pu *url.URL |  | ||||||
| 				pu, err = url.Parse(u) |  | ||||||
| 				if err != nil || (pu.Scheme != "http" && pu.Scheme != "https") || pu.Fragment != "" || (allow != nil && !allow.MatchString(u)) || (deny != nil && deny.MatchString(u)) { |  | ||||||
| 					err = errInvalidURL |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		case v1.MediaTypeImageManifest: | 		case v1.MediaTypeImageManifest: | ||||||
| 			var exists bool | 			var exists bool | ||||||
| 			exists, err = manifestService.Exists(ctx, descriptor.Digest) | 			exists, err = manifestService.Exists(ctx, descriptor.Digest) | ||||||
|  |  | ||||||
|  | @ -53,12 +53,6 @@ func TestVerifyOCIManifestNonDistributableLayer(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	cases := []testcase{ | 	cases := []testcase{ | ||||||
| 		{ | 		{ | ||||||
| 			nonDistributableLayer, |  | ||||||
| 			nil, |  | ||||||
| 			errMissingURL, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			// regular layers may have foreign urls (non-Distributable Layers)
 |  | ||||||
| 			layer, | 			layer, | ||||||
| 			[]string{"http://foo/bar"}, | 			[]string{"http://foo/bar"}, | ||||||
| 			nil, | 			nil, | ||||||
|  | @ -66,37 +60,37 @@ func TestVerifyOCIManifestNonDistributableLayer(t *testing.T) { | ||||||
| 		{ | 		{ | ||||||
| 			nonDistributableLayer, | 			nonDistributableLayer, | ||||||
| 			[]string{"file:///local/file"}, | 			[]string{"file:///local/file"}, | ||||||
| 			errInvalidURL, | 			nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			nonDistributableLayer, | 			nonDistributableLayer, | ||||||
| 			[]string{"http://foo/bar#baz"}, | 			[]string{"http://foo/bar#baz"}, | ||||||
| 			errInvalidURL, | 			nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			nonDistributableLayer, | 			nonDistributableLayer, | ||||||
| 			[]string{""}, | 			[]string{""}, | ||||||
| 			errInvalidURL, | 			nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			nonDistributableLayer, | 			nonDistributableLayer, | ||||||
| 			[]string{"https://foo/bar", ""}, | 			[]string{"https://foo/bar", ""}, | ||||||
| 			errInvalidURL, | 			nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			nonDistributableLayer, | 			nonDistributableLayer, | ||||||
| 			[]string{"", "https://foo/bar"}, | 			[]string{"", "https://foo/bar"}, | ||||||
| 			errInvalidURL, | 			nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			nonDistributableLayer, | 			nonDistributableLayer, | ||||||
| 			[]string{"http://nope/bar"}, | 			[]string{"http://nope/bar"}, | ||||||
| 			errInvalidURL, | 			nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			nonDistributableLayer, | 			nonDistributableLayer, | ||||||
| 			[]string{"http://foo/nope"}, | 			[]string{"http://foo/nope"}, | ||||||
| 			errInvalidURL, | 			nil, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			nonDistributableLayer, | 			nonDistributableLayer, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue