Skip to content

Commit 7c2d300

Browse files
authored
Changes the config layer of the feature manifest to a empty descriptor (#815)
* Change the config layer of the feature manifest to a empty descriptor This commit changes the config layer of the feature manifest to a empty descriptor according to the OCI image manifest specification to to increase the available feature registry. Some registries that implement the OCI image manifest specification, such as AWS ECR, do not support pushing empty content (size 0). However, the manifest may contain layers with no content. Therefore, the OCI Image Manifest specification specifies an empty descriptor of non-zero size to represent a layer with no content. Currently, the devcontainer feature manifest specifies the config layer as an empty content (size 0) layer. If the devcontainer feature manifest conforms to this specification, it can also available registries that cannot publish empty content (size 0). Specification: https://github.com/opencontainers/image-spec/blob/8f3820ccf8f65db8744e626df17fe8a64462aece/manifest.md#guidelines-for-artifact-usage Note: If the config is set to an empty descriptor, the manifest media type must be defined in the artifactType. * Revert "Change the config layer of the feature manifest to a empty descriptor" This reverts commit 1b290f0. * Changing the content of the config layer to an empty object The content of the configuration layer of the feature manifest is specified as empty content (size 0). This is because there is no content to reference in the config layer content of the feature manifest. However, some registries that implement the OCI image manifest specification, such as AWS ECR, do not support pushing empty content (size 0). Therefore, this commit changes the content of the config layer of the feature manifestto indicate that empty objects have no content to reference. * Revert the test of push against the GitHub registry and fix/add the comments * Change the feature that test depends on
1 parent 5216f9a commit 7c2d300

File tree

5 files changed

+26
-21
lines changed

5 files changed

+26
-21
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ output
1515
src/test/container-features/configs/temp_lifecycle-hooks-alternative-order
1616
test-secrets-temp.json
1717
src/test/container-*/**/src/**/README.md
18+
!src/test/container-features/assets/*.tgz

src/spec-configuration/containerCollectionsOCIPush.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ export async function pushOCIFeatureOrTemplate(params: CommonParams, ociRef: OCI
4444
{
4545
name: 'configLayer',
4646
digest: manifest.manifestObj.config.digest,
47-
contents: Buffer.alloc(0),
4847
size: manifest.manifestObj.config.size,
48+
contents: Buffer.from('{}'),
4949
},
5050
{
5151
name: 'tgzLayer',
@@ -119,7 +119,7 @@ export async function pushCollectionMetadata(params: CommonParams, collectionRef
119119
name: 'configLayer',
120120
digest: manifest.manifestObj.config.digest,
121121
size: manifest.manifestObj.config.size,
122-
contents: Buffer.alloc(0),
122+
contents: Buffer.from('{}'),
123123
},
124124
{
125125
name: 'collectionLayer',
@@ -382,16 +382,16 @@ async function postUploadSessionId(params: CommonParams, ociRef: OCIRef | OCICol
382382
export async function calculateManifestAndContentDigest(output: Log, ociRef: OCIRef | OCICollectionRef, dataLayer: OCILayer, annotations: { [key: string]: string } | undefined): Promise<ManifestContainer> {
383383
// A canonical manifest digest is the sha256 hash of the JSON representation of the manifest, without the signature content.
384384
// See: https://docs.docker.com/registry/spec/api/#content-digests
385-
// Below is an example of a serialized manifest that should resolve to '9726054859c13377c4c3c3c73d15065de59d0c25d61d5652576c0125f2ea8ed3'
386-
// {"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.devcontainers","digest":"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","size":0},"layers":[{"mediaType":"application/vnd.devcontainers.layer.v1+tar","digest":"sha256:b2006e7647191f7b47222ae48df049c6e21a4c5a04acfad0c4ef614d819de4c5","size":15872,"annotations":{"org.opencontainers.image.title":"go.tgz"}}]}
385+
// Below is an example of a serialized manifest that should resolve to 'dd328c25cc7382aaf4e9ee10104425d9a2561b47fe238407f6c0f77b3f8409fc'
386+
// {"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.devcontainers","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/vnd.devcontainers.layer.v1+tar","digest":"sha256:0bb92d2da46d760c599d0a41ed88d52521209408b529761417090b62ee16dfd1","size":3584,"annotations":{"org.opencontainers.image.title":"devcontainer-feature-color.tgz"}}],"annotations":{"dev.containers.metadata":"{\"id\":\"color\",\"version\":\"1.0.0\",\"name\":\"A feature to remind you of your favorite color\",\"options\":{\"favorite\":{\"type\":\"string\",\"enum\":[\"red\",\"gold\",\"green\"],\"default\":\"red\",\"description\":\"Choose your favorite color.\"}}}","com.github.package.type":"devcontainer_feature"}}
387387

388388
let manifest: OCIManifest = {
389389
schemaVersion: 2,
390390
mediaType: 'application/vnd.oci.image.manifest.v1+json',
391391
config: {
392392
mediaType: 'application/vnd.devcontainers',
393-
digest: 'sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', // A zero byte digest for the devcontainer mediaType.
394-
size: 0
393+
digest: 'sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a', // A empty json byte digest for the devcontainer mediaType.
394+
size: 2
395395
},
396396
layers: [
397397
dataLayer
Binary file not shown.
-15.5 KB
Binary file not shown.

src/test/container-features/containerFeaturesOCIPush.test.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -305,26 +305,26 @@ registry`;
305305
});
306306

307307
// NOTE:
308-
// Test depends on https://github.com/codspace/features/pkgs/container/features%2Fgo/29819216?tag=1
308+
// Test depends on https://github.com/orgs/codspace/packages/container/non-empty-config-layer%2Fcolor/225254837?tag=1.0.0
309309
describe('Test OCI Push Helper Functions', function () {
310310
this.timeout('10s');
311311
it('Generates the correct tgz manifest layer', async () => {
312312

313-
const dataBytes = fs.readFileSync(`${testAssetsDir}/go.tgz`);
313+
const dataBytes = fs.readFileSync(`${testAssetsDir}/devcontainer-feature-color.tgz`);
314314

315-
const featureRef = getRef(output, 'ghcr.io/devcontainers/features/go');
315+
const featureRef = getRef(output, 'ghcr.io/codspace/non-empty-config-layer/color');
316316
if (!featureRef) {
317317
assert.fail();
318318
}
319319

320320
// Calculate the tgz layer and digest
321-
const res = await calculateDataLayer(output, dataBytes, 'go.tgz', DEVCONTAINER_TAR_LAYER_MEDIATYPE);
321+
const res = await calculateDataLayer(output, dataBytes, 'devcontainer-feature-color.tgz', DEVCONTAINER_TAR_LAYER_MEDIATYPE);
322322
const expected = {
323-
digest: 'sha256:b2006e7647191f7b47222ae48df049c6e21a4c5a04acfad0c4ef614d819de4c5',
323+
digest: 'sha256:0bb92d2da46d760c599d0a41ed88d52521209408b529761417090b62ee16dfd1',
324324
mediaType: 'application/vnd.devcontainers.layer.v1+tar',
325-
size: 15872,
325+
size: 3584,
326326
annotations: {
327-
'org.opencontainers.image.title': 'go.tgz'
327+
'org.opencontainers.image.title': 'devcontainer-feature-color.tgz'
328328
}
329329
};
330330

@@ -334,35 +334,39 @@ describe('Test OCI Push Helper Functions', function () {
334334
assert.deepEqual(res, expected);
335335

336336
// Generate entire manifest to be able to calculate content digest
337-
const manifestContainer = await calculateManifestAndContentDigest(output, featureRef, res, undefined);
337+
const annotations = {
338+
'dev.containers.metadata': '{\"id\":\"color\",\"version\":\"1.0.0\",\"name\":\"A feature to remind you of your favorite color\",\"options\":{\"favorite\":{\"type\":\"string\",\"enum\":[\"red\",\"gold\",\"green\"],\"default\":\"red\",\"description\":\"Choose your favorite color.\"}}}',
339+
'com.github.package.type': 'devcontainer_feature'
340+
};
341+
const manifestContainer = await calculateManifestAndContentDigest(output, featureRef, res, annotations);
338342
if (!manifestContainer) {
339343
assert.fail();
340344
}
341345
const { contentDigest, manifestBuffer } = manifestContainer;
342346

343347
// 'Expected' is taken from intermediate value in oras reference implementation, before hash calculation
344-
assert.strictEqual('{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.devcontainers","digest":"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","size":0},"layers":[{"mediaType":"application/vnd.devcontainers.layer.v1+tar","digest":"sha256:b2006e7647191f7b47222ae48df049c6e21a4c5a04acfad0c4ef614d819de4c5","size":15872,"annotations":{"org.opencontainers.image.title":"go.tgz"}}]}', manifestBuffer.toString());
348+
assert.strictEqual('{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.devcontainers","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/vnd.devcontainers.layer.v1+tar","digest":"sha256:0bb92d2da46d760c599d0a41ed88d52521209408b529761417090b62ee16dfd1","size":3584,"annotations":{"org.opencontainers.image.title":"devcontainer-feature-color.tgz"}}],"annotations":{"dev.containers.metadata":"{\\"id\\":\\"color\\",\\"version\\":\\"1.0.0\\",\\"name\\":\\"A feature to remind you of your favorite color\\",\\"options\\":{\\"favorite\\":{\\"type\\":\\"string\\",\\"enum\\":[\\"red\\",\\"gold\\",\\"green\\"],\\"default\\":\\"red\\",\\"description\\":\\"Choose your favorite color.\\"}}}","com.github.package.type":"devcontainer_feature"}}', manifestBuffer.toString());
345349

346350
// This is the canonical digest of the manifest
347-
assert.strictEqual('sha256:9726054859c13377c4c3c3c73d15065de59d0c25d61d5652576c0125f2ea8ed3', contentDigest);
351+
assert.strictEqual('sha256:dd328c25cc7382aaf4e9ee10104425d9a2561b47fe238407f6c0f77b3f8409fc', contentDigest);
348352
});
349353

350354
it('Can fetch an artifact from a digest reference', async () => {
351-
const manifest = await fetchOCIFeatureManifestIfExistsFromUserIdentifier({ output, env: process.env }, 'ghcr.io/codspace/features/go', 'sha256:9726054859c13377c4c3c3c73d15065de59d0c25d61d5652576c0125f2ea8ed3');
352-
assert.strictEqual(manifest?.manifestObj.layers[0].annotations['org.opencontainers.image.title'], 'go.tgz');
355+
const manifest = await fetchOCIFeatureManifestIfExistsFromUserIdentifier({ output, env: process.env }, 'ghcr.io/codspace/non-empty-config-layer/color', 'sha256:dd328c25cc7382aaf4e9ee10104425d9a2561b47fe238407f6c0f77b3f8409fc');
356+
assert.strictEqual(manifest?.manifestObj.layers[0].annotations['org.opencontainers.image.title'], 'devcontainer-feature-color.tgz');
353357
});
354358

355359
it('Can check whether a blob exists', async () => {
356-
const ociFeatureRef = getRef(output, 'ghcr.io/codspace/features/go:1');
360+
const ociFeatureRef = getRef(output, 'ghcr.io/codspace/non-empty-config-layer/color:1.0.0');
357361
if (!ociFeatureRef) {
358362
assert.fail('getRef() for the Feature should not be undefined');
359363
}
360364

361365

362-
const tarLayerBlobExists = await checkIfBlobExists({ output, env: process.env }, ociFeatureRef, 'sha256:b2006e7647191f7b47222ae48df049c6e21a4c5a04acfad0c4ef614d819de4c5');
366+
const tarLayerBlobExists = await checkIfBlobExists({ output, env: process.env }, ociFeatureRef, 'sha256:0bb92d2da46d760c599d0a41ed88d52521209408b529761417090b62ee16dfd1');
363367
assert.isTrue(tarLayerBlobExists);
364368

365-
const configLayerBlobExists = await checkIfBlobExists({ output, env: process.env }, ociFeatureRef, 'sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
369+
const configLayerBlobExists = await checkIfBlobExists({ output, env: process.env }, ociFeatureRef, 'sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a');
366370
assert.isTrue(configLayerBlobExists);
367371

368372
const randomStringDoesNotExist = await checkIfBlobExists({ output, env: process.env }, ociFeatureRef, 'sha256:41af286dc0b172ed2f1ca934fd2278de4a1192302ffa07087cea2682e7d372e3');

0 commit comments

Comments
 (0)