Skip to content

Commit 439d039

Browse files
authored
[ACR] Improvements to README, TSG and samples (Azure#25746)
### Packages impacted by this PR - `@azure/container-registry` ### Issues associated with this PR - Azure#25448 ### Describe the problem that is addressed by this PR - Add documentation about resumable upload 404 issue. - Add select samples for blob operations to README to be consistent with the other languages. - Increment version in preparation for GA and republish samples.
1 parent 3edd409 commit 439d039

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+269
-1048
lines changed

sdk/containerregistry/container-registry/CHANGELOG.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
# Release History
22

3-
## 1.1.0-beta.4 (Unreleased)
3+
## 1.1.0 (2023-05-09)
44

55
### Features Added
6+
- Added `ContainerRegistryContentClient` for uploading and downloading OCI blobs and manifests.
67

78
### Breaking Changes
89

9-
### Bugs Fixed
10-
11-
### Other Changes
10+
Since `1.1.0-beta.3`:
11+
- The manifest property on GetManifestResult is now `Record<string, unknown>` instead of OciImageManifest. This property is now populated regardless of the media type of the manifest, instead of only being populated when the manifest was an OCI image manifest.
12+
- Added an `isOciImageManifest` type guard to check if a manifest is an OCI image manifest, providing strong typing.
13+
- Renamed properties on `OciImageManifest`, `OciDescriptor`, and `OciAnnotations` to match the specification exactly:
14+
- Renamed `OciAnnotations.createdOn` to `created`.
15+
- Renamed `OciDescriptor.sizeInBytes` to `size`.
16+
- Renamed `OciImageManifest.configuration` to `config`.
17+
- Removed `GetOciImageManifestResult` and the corresponding type guard, `isGetOciImageManifestResult`.
1218

1319
## 1.1.0-beta.3 (2023-04-11)
1420

sdk/containerregistry/container-registry/README.md

Lines changed: 173 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ For more information please see [Container Registry Concepts](https://docs.micro
104104

105105
## Examples
106106

107-
### Listing repositories
107+
### Registry operations
108+
109+
#### Listing repositories
108110

109111
Iterate through the collection of repositories in the registry.
110112

@@ -135,7 +137,7 @@ main().catch((err) => {
135137
});
136138
```
137139

138-
### List tags with anonymous access
140+
#### List tags with anonymous access
139141

140142
```javascript
141143
const {
@@ -170,7 +172,7 @@ main().catch((err) => {
170172
});
171173
```
172174

173-
### Set artifact properties
175+
#### Set artifact properties
174176

175177
```javascript
176178
const {
@@ -198,7 +200,7 @@ main().catch((err) => {
198200
});
199201
```
200202

201-
### Delete images
203+
#### Delete images
202204

203205
```javascript
204206
const {
@@ -247,6 +249,173 @@ main().catch((err) => {
247249
});
248250
```
249251

252+
### Blob and manifest operations
253+
254+
#### Upload images
255+
256+
```javascript
257+
const { ContainerRegistryContentClient } = require("@azure/container-registry");
258+
const { DefaultAzureCredential } = require("@azure/identity");
259+
require("dotenv").config();
260+
261+
async function main() {
262+
// endpoint should be in the form of "https://myregistryname.azurecr.io"
263+
// where "myregistryname" is the actual name of your registry
264+
const endpoint = process.env.CONTAINER_REGISTRY_ENDPOINT || "<endpoint>";
265+
const repository = process.env.CONTAINER_REGISTRY_REPOSITORY || "library/hello-world";
266+
const client = new ContainerRegistryContentClient(
267+
endpoint,
268+
repository,
269+
new DefaultAzureCredential()
270+
);
271+
272+
const config = Buffer.from("Sample config");
273+
const { digest: configDigest, sizeInBytes: configSize } = await client.uploadBlob(config);
274+
275+
const layer = Buffer.from("Sample layer");
276+
const { digest: layerDigest, sizeInBytes: layerSize } = await client.uploadBlob(layer);
277+
278+
const manifest = {
279+
schemaVersion: 2,
280+
config: {
281+
digest: configDigest,
282+
size: configSize,
283+
mediaType: "application/vnd.oci.image.config.v1+json",
284+
},
285+
layers: [
286+
{
287+
digest: layerDigest,
288+
size: layerSize,
289+
mediaType: "application/vnd.oci.image.layer.v1.tar",
290+
},
291+
],
292+
};
293+
294+
await client.setManifest(manifest, { tag: "demo" });
295+
}
296+
297+
main().catch((err) => {
298+
console.error("The sample encountered an error:", err);
299+
});
300+
```
301+
302+
#### Download images
303+
304+
```javascript
305+
const { ContainerRegistryContentClient, isOciImageManifest } = require("@azure/container-registry");
306+
const { DefaultAzureCredential } = require("@azure/identity");
307+
const dotenv = require("dotenv");
308+
const fs = require("fs");
309+
dotenv.config();
310+
311+
function trimSha(digest) {
312+
const index = digest.indexOf(":");
313+
return index === -1 ? digest : digest.substring(index);
314+
}
315+
316+
async function main() {
317+
// endpoint should be in the form of "https://myregistryname.azurecr.io"
318+
// where "myregistryname" is the actual name of your registry
319+
const endpoint = process.env.CONTAINER_REGISTRY_ENDPOINT || "<endpoint>";
320+
const repository = process.env.CONTAINER_REGISTRY_REPOSITORY || "library/hello-world";
321+
const client = new ContainerRegistryContentClient(
322+
endpoint,
323+
repository,
324+
new DefaultAzureCredential()
325+
);
326+
327+
// Download the manifest to obtain the list of files in the image based on the tag
328+
const result = await client.getManifest("demo");
329+
330+
const manifest = result.manifest;
331+
if (!isOciImageManifest(manifest)) {
332+
throw new Error("Expected an OCI image manifest");
333+
}
334+
335+
// Manifests of all media types have a buffer containing their content; this can be written to a file.
336+
fs.writeFileSync("manifest.json", result.content);
337+
338+
const configResult = await client.downloadBlob(manifest.config.digest);
339+
const configFile = fs.createWriteStream("config.json");
340+
configResult.content.pipe(configFile);
341+
342+
// Download and write out the layers
343+
for (const layer of manifest.layers) {
344+
const fileName = trimSha(layer.digest);
345+
const layerStream = fs.createWriteStream(fileName);
346+
const downloadLayerResult = await client.downloadBlob(layer.digest);
347+
downloadLayerResult.content.pipe(layerStream);
348+
}
349+
}
350+
351+
main().catch((err) => {
352+
console.error("The sample encountered an error:", err);
353+
});
354+
355+
```
356+
357+
#### Delete manifest
358+
359+
```javascript
360+
const { ContainerRegistryContentClient } = require("@azure/container-registry");
361+
const { DefaultAzureCredential } = require("@azure/identity");
362+
require("dotenv").config();
363+
364+
async function main() {
365+
// Get the service endpoint from the environment
366+
const endpoint = process.env.CONTAINER_REGISTRY_ENDPOINT || "<endpoint>";
367+
const repository = process.env.CONTAINER_REGISTRY_REPOSITORY || "library/hello-world";
368+
// Create a new ContainerRegistryClient
369+
const client = new ContainerRegistryContentClient(
370+
endpoint,
371+
repository,
372+
new DefaultAzureCredential()
373+
);
374+
375+
const downloadResult = await client.getManifest("latest");
376+
await client.deleteManifest(downloadResult.digest);
377+
}
378+
379+
main().catch((err) => {
380+
console.error("The sample encountered an error:", err);
381+
});
382+
```
383+
384+
#### Delete blob
385+
386+
```javascript
387+
const { ContainerRegistryContentClient, isOciImageManifest } = require("@azure/container-registry");
388+
const { DefaultAzureCredential } = require("@azure/identity");
389+
require("dotenv").config();
390+
391+
async function main() {
392+
// Get the service endpoint from the environment
393+
const endpoint = process.env.CONTAINER_REGISTRY_ENDPOINT || "<endpoint>";
394+
const repository = process.env.CONTAINER_REGISTRY_REPOSITORY || "library/hello-world";
395+
// Create a new ContainerRegistryClient
396+
const client = new ContainerRegistryContentClient(
397+
endpoint,
398+
repository,
399+
new DefaultAzureCredential()
400+
);
401+
402+
const downloadResult = await client.getManifest("latest");
403+
404+
if (!isOciImageManifest(downloadResult.manifest)) {
405+
throw new Error("Expected an OCI image manifest");
406+
}
407+
408+
for (const layer of downloadResult.manifest.layers) {
409+
await client.deleteBlob(layer.digest);
410+
}
411+
}
412+
413+
main().catch((err) => {
414+
console.error("The sample encountered an error:", err);
415+
});
416+
417+
```
418+
250419
## Troubleshooting
251420

252421
For infomation about troubleshooting, refer to the [troubleshooting guide].

sdk/containerregistry/container-registry/TROUBLESHOOTING.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,33 @@ RestError: {"errors":[{"code":"DENIED","message":"client with IP '<your IP addre
6161
s/acr/firewall to grant access."}]}
6262
```
6363

64+
## Service errors
65+
66+
When working with `ContainerRegistryContentClient` and `ContainerRegistryAsyncContentClient` you may get a `RestError` with
67+
message containing additional information and [Docker error code](https://docs.docker.com/registry/spec/api/#errors-2).
68+
69+
### Getting BLOB_UPLOAD_INVALID
70+
71+
In rare cases, transient error (such as connection reset) can happen during blob upload which may lead a `RestError` being thrown with a message similar to
72+
`{"errors":[{"code":"BLOB_UPLOAD_INVALID","message":"blob upload invalid"}]}`, resulting in a failed upload. In this case upload should to be restarted from the beginning.
73+
74+
The following code snippet shows how to access detailed error information:
75+
```ts
76+
const config = Buffer.from(`{"hello":"world"}`);
77+
78+
try {
79+
const uploadResult = await contentClient.uploadBlob(config);
80+
console.log(`Uploaded blob: digest - ${uploadResult.digest}, size - ${uploadResult.sizeInBytes}`);
81+
} catch (e) {
82+
// isRestError is exported by @azure/core-rest-pipeline
83+
if(isRestError(e) && e.statusCode === 404 && (e.details as any).errors.some((error: any) => error.code === "BLOB_UPLOAD_INVALID")) {
84+
// Retry upload
85+
} else {
86+
throw e;
87+
}
88+
}
89+
```
90+
6491
<!-- Links -->
6592

6693
[`resterror`]: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-rest-pipeline/src/restError.ts

sdk/containerregistry/container-registry/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@azure/container-registry",
3-
"version": "1.1.0-beta.4",
3+
"version": "1.1.0",
44
"description": "An isomorphic client library for the Azure Container Registry service.",
55
"sdk-type": "client",
66
"main": "dist/index.js",

sdk/containerregistry/container-registry/samples/v1-beta/javascript/README.md

Lines changed: 0 additions & 85 deletions
This file was deleted.

0 commit comments

Comments
 (0)