|
| 1 | +# Tutorial: Get started with oras-go v2 |
| 2 | + |
| 3 | +This tutorial introduces the basics of managing OCI artifacts with the [oras-go v2](https://pkg.go.dev/oras.land/oras-go/v2) package. |
| 4 | + |
| 5 | +You'll get the most out of this tutorial if you have a basic familiarity with Go and its tooling. If this is your first exposure to Go, please see [Tutorial: Get started with Go](https://golang.org/doc/tutorial/getting-started) for a quick introduction. |
| 6 | + |
| 7 | +The tutorial includes the following sections: |
| 8 | + |
| 9 | +1. Create a folder for your code. |
| 10 | +2. Connect to a remote repository. |
| 11 | +3. Show tags in the repository. |
| 12 | +4. Push a layer to the repository. |
| 13 | +5. Push a manifest to the repository. |
| 14 | +6. Fetch the manifest from the repository. |
| 15 | +7. Parse the fetched manifest content and get the layers. |
| 16 | +8. Copy the artifact from the repository. |
| 17 | + |
| 18 | +The complete code is provided at the end of this tutorial. |
| 19 | + |
| 20 | +## Create a folder for your code |
| 21 | + |
| 22 | +Open a command prompt and cd to your working directory. |
| 23 | + |
| 24 | +Create a directory for your code. |
| 25 | +```shell |
| 26 | +mkdir oras-go-v2-quickstart |
| 27 | +cd oras-go-v2-quickstart |
| 28 | +``` |
| 29 | +Create a module in which you can manage dependencies. |
| 30 | + |
| 31 | +```console |
| 32 | +$ go mod init quickstart/oras-go-v2 |
| 33 | +go: creating new go.mod: quickstart/oras-go-v2 |
| 34 | +``` |
| 35 | + |
| 36 | +Import the `oras-go v2` package. |
| 37 | +```console |
| 38 | +$ go get oras.land/oras-go/v2 |
| 39 | +go: added github.com/opencontainers/go-digest v1.0.0 |
| 40 | +go: added github.com/opencontainers/image-spec v1.1.0 |
| 41 | +go: added golang.org/x/sync v0.6.0 |
| 42 | +go: added oras.land/oras-go/v2 v2.5.0 |
| 43 | +``` |
| 44 | + |
| 45 | +In your text editor, create a file `main.go` in which to write your code. |
| 46 | + |
| 47 | +## Connect to a remote repository with token authentication |
| 48 | + |
| 49 | +Paste the following into `main.go` and save the file. This code demonstrates how to use [NewRepository](https://pkg.go.dev/oras.land/oras-go/v2/registry/remote#NewRepository) from the [registry/remote](https://pkg.go.dev/oras.land/oras-go/v2/registry/remote) package to connect to a remote repository. Token authentication (using a username and password, see reference [here](https://distribution.github.io/distribution/spec/auth/token/)) is handled by the [registry/remote/auth](https://pkg.go.dev/oras.land/oras-go/v2/registry/remote/auth) package. Other authentication methods, such as refresh token authentication, are also supported. |
| 50 | + |
| 51 | +```go |
| 52 | +package main |
| 53 | + |
| 54 | +import ( |
| 55 | + "oras.land/oras-go/v2/registry/remote" |
| 56 | + "oras.land/oras-go/v2/registry/remote/auth" |
| 57 | + "oras.land/oras-go/v2/registry/remote/retry" |
| 58 | +) |
| 59 | + |
| 60 | +func main() { |
| 61 | + // 1. Connect to a remote repository with token authentication |
| 62 | + ref := "example.registry.com/myrepo" |
| 63 | + repo, err := remote.NewRepository(ref) |
| 64 | + if err != nil { |
| 65 | + panic(err) |
| 66 | + } |
| 67 | + // Note: The below code can be omitted if authentication is not required. |
| 68 | + repo.Client = &auth.Client{ |
| 69 | + Client: retry.DefaultClient, |
| 70 | + Cache: auth.NewCache(), |
| 71 | + Credential: auth.StaticCredential(repo.Reference.Registry, auth.Credential{ |
| 72 | + Username: "username", |
| 73 | + Password: "password", |
| 74 | + }), |
| 75 | + } |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +## Show tags in the repository |
| 80 | +Add these two lines to the `import` block of `main.go`. |
| 81 | + |
| 82 | +```go |
| 83 | +"context" |
| 84 | +"fmt" |
| 85 | +``` |
| 86 | + |
| 87 | +The following code snippet uses the [(*Repository) Tags](https://pkg.go.dev/oras.land/oras-go/v2/registry/remote#Repository.Tags) method to list the tags in the repository. Paste the code into `main.go` after the last section. |
| 88 | + |
| 89 | +```go |
| 90 | +// 2. Show the tags in the repository |
| 91 | +ctx := context.Background() |
| 92 | +err = repo.Tags(ctx, "", func(tags []string) error { |
| 93 | + for _, tag := range tags { |
| 94 | + fmt.Println(tag) |
| 95 | + } |
| 96 | + return nil |
| 97 | +}) |
| 98 | +if err != nil { |
| 99 | + panic(err) |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +### Run the code |
| 104 | + |
| 105 | +Run `go mod tidy` to clean up dependencies. |
| 106 | + |
| 107 | +```shell |
| 108 | +go mod tidy |
| 109 | +``` |
| 110 | + |
| 111 | +Run the code. |
| 112 | +```shell |
| 113 | +go run . |
| 114 | +``` |
| 115 | + |
| 116 | +You should see the tags in the repository. |
| 117 | + |
| 118 | +## Push a layer to the repository |
| 119 | + |
| 120 | +All referenced layers must exist in the repository before a manifest can be pushed, so we need to push manifest layers before we can push a manifest. |
| 121 | + |
| 122 | +Add these two lines to the `import` block of `main.go`. |
| 123 | + |
| 124 | +```go |
| 125 | +ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| 126 | +"oras.land/oras-go/v2" |
| 127 | +``` |
| 128 | + |
| 129 | +The following code snippet demonstrates how to push a manifest layer with [PushBytes](https://pkg.go.dev/oras.land/oras-go/v2#PushBytes). Paste the code into `main.go` after the last section. |
| 130 | + |
| 131 | +```go |
| 132 | +// 3. push a layer to the repository |
| 133 | +layer := []byte("example manifest layer") |
| 134 | +layerDescriptor, err := oras.PushBytes(ctx, repo, ocispec.MediaTypeImageLayer, layer) |
| 135 | +if err != nil { |
| 136 | + panic(err) |
| 137 | +} |
| 138 | +fmt.Println("Pushed manifest layer:", layerDescriptor.Digest) |
| 139 | +``` |
| 140 | + |
| 141 | +## Push a manifest to the repository with the tag "quickstart" |
| 142 | + |
| 143 | +The following code snippet demonstrates how to pack a manifest and push it to the repository with the tag "quickstart" using the [PackManifest](https://pkg.go.dev/oras.land/oras-go/v2#PackManifest) and the [(*Repository) Tag](https://pkg.go.dev/oras.land/oras-go/v2/registry/remote#Repository.Tag) methods. Paste the code into `main.go` after the last section. |
| 144 | + |
| 145 | +```go |
| 146 | +// 4. Push a manifest to the repository with the tag "quickstart" |
| 147 | +packOpts := oras.PackManifestOptions{ |
| 148 | + Layers: []ocispec.Descriptor{layerDescriptor}, |
| 149 | +} |
| 150 | +artifactType := "application/vnd.example+type" |
| 151 | +desc, err := oras.PackManifest(ctx, repo, oras.PackManifestVersion1_1, artifactType, packOpts) |
| 152 | +if err != nil { |
| 153 | + panic(err) |
| 154 | +} |
| 155 | +tag := "quickstart" |
| 156 | +err = repo.Tag(ctx, desc, tag) |
| 157 | +if err != nil { |
| 158 | + panic(err) |
| 159 | +} |
| 160 | +fmt.Println("Pushed and tagged manifest") |
| 161 | +``` |
| 162 | + |
| 163 | +## Fetch the manifest from the repository by tag |
| 164 | + |
| 165 | +The following code snippet demonstrates how to fetch a manifest from the repository by its tag with [FetchBytes](https://pkg.go.dev/oras.land/oras-go/v2#FetchBytes). Paste the code into `main.go` after the last section. |
| 166 | + |
| 167 | +```go |
| 168 | +// 5. Fetch the manifest from the repository by tag |
| 169 | +_, fetchedManifestContent, err := oras.FetchBytes(ctx, repo, tag, oras.DefaultFetchBytesOptions) |
| 170 | +if err != nil { |
| 171 | + panic(err) |
| 172 | +} |
| 173 | +fmt.Println(string(fetchedManifestContent)) |
| 174 | +``` |
| 175 | + |
| 176 | +## Parse the fetched manifest content and get the layers |
| 177 | + |
| 178 | +Add these two lines to the `import` block of `main.go`. |
| 179 | +```go |
| 180 | +"encoding/json" |
| 181 | +"oras.land/oras-go/v2/content" |
| 182 | +``` |
| 183 | + |
| 184 | +The following code snippet demonstrates how to parse the fetched manifest content and get the layers. [FetchAll](https://pkg.go.dev/oras.land/oras-go/v2/content#FetchAll) from the [content](https://pkg.go.dev/oras.land/oras-go/v2/content) package is used to read and fetch the content identified by a descriptor. Paste the code into `main.go` after the last section. |
| 185 | + |
| 186 | +```go |
| 187 | +// 6. Parse the fetched manifest content and get the layers |
| 188 | +var manifest ocispec.Manifest |
| 189 | +if err := json.Unmarshal(fetchedManifestContent, &manifest); err != nil { |
| 190 | + panic(err) |
| 191 | +} |
| 192 | +for _, layer := range manifest.Layers { |
| 193 | + layerContent, err := content.FetchAll(ctx, repo, layer) |
| 194 | + if err != nil { |
| 195 | + panic(err) |
| 196 | + } |
| 197 | + fmt.Println(string(layerContent)) |
| 198 | +} |
| 199 | +``` |
| 200 | + |
| 201 | +## Copy the artifact to local OCI layout directory from the repository |
| 202 | + |
| 203 | +Add these two lines to the `import` block of `main.go`. |
| 204 | +```go |
| 205 | +"os" |
| 206 | +"oras.land/oras-go/v2/content/oci" |
| 207 | +``` |
| 208 | + |
| 209 | +The following code snippet demonstrates how to copy an artifact from the repository by its tag and save it to the current directory in the [OCI layout](https://github.com/opencontainers/image-spec/blob/main/image-layout.md) format. The copy operation is performed using [Copy](https://pkg.go.dev/oras.land/oras-go/v2#Copy). Paste the code into `main.go` after the last section. |
| 210 | + |
| 211 | +```go |
| 212 | +// 7. Copy the artifact to local OCI layout directory with a tag "quickstartOCI" |
| 213 | +ociDir, err := os.MkdirTemp(".", "oras_oci_example_*") |
| 214 | +if err != nil { |
| 215 | + panic(err) |
| 216 | +} |
| 217 | +ociTarget, err := oci.New(ociDir) |
| 218 | +if err != nil { |
| 219 | + panic(err) |
| 220 | +} |
| 221 | +_, err = oras.Copy(ctx, repo, tag, ociTarget, "quickstartOCI", oras.DefaultCopyOptions) |
| 222 | +if err != nil { |
| 223 | + panic(err) |
| 224 | +} |
| 225 | +fmt.Println("Copied the artifact") |
| 226 | +``` |
| 227 | + |
| 228 | +## Run the code |
| 229 | + |
| 230 | +Run the code. |
| 231 | +```shell |
| 232 | +go run . |
| 233 | +``` |
| 234 | + |
| 235 | +You should see a similar output on the terminal as below and an OCI layout folder in the current directory. |
| 236 | +``` |
| 237 | +tag1 |
| 238 | +tag2 |
| 239 | +tag3 |
| 240 | +Pushed manifest layer: sha256:4f19474743ecb04b60156ea41b73e06fdf6a5b758e007b788aaa92595dcd3a49 |
| 241 | +Pushed and tagged manifest |
| 242 | +{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","artifactType":"application/vnd.example+type","config":{"mediaType":"application/vnd.oci.empty.v1+json","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2,"data":"e30="},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:4f19474743ecb04b60156ea41b73e06fdf6a5b758e007b788aaa92595dcd3a49","size":22}],"annotations":{"org.opencontainers.image.created":"2025-04-18T03:15:26Z"}} |
| 243 | +example manifest layer |
| 244 | +Copied the artifact |
| 245 | +``` |
| 246 | + |
| 247 | +## Conclusion |
| 248 | + |
| 249 | +Congratulations! You’ve completed this tutorial. |
| 250 | + |
| 251 | +Suggested next steps: |
| 252 | +* Check out more [examples](https://pkg.go.dev/oras.land/oras-go/v2#pkg-overview) in the documentation. |
| 253 | +* Learn about how `oras-go` v2 [models artifacts](https://github.com/oras-project/oras-go/blob/main/docs/Modeling-Artifacts.md). |
| 254 | +* Learn about [Targets and Content Stores](https://github.com/oras-project/oras-go/blob/main/docs/Targets.md) in `oras-go` v2. |
| 255 | + |
| 256 | +## Completed Code |
| 257 | + |
| 258 | +This section contains the completed code from this tutorial. |
| 259 | + |
| 260 | +```go |
| 261 | +package main |
| 262 | + |
| 263 | +import ( |
| 264 | + "context" |
| 265 | + "encoding/json" |
| 266 | + "fmt" |
| 267 | + "os" |
| 268 | + |
| 269 | + ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| 270 | + "oras.land/oras-go/v2" |
| 271 | + "oras.land/oras-go/v2/content" |
| 272 | + "oras.land/oras-go/v2/content/oci" |
| 273 | + "oras.land/oras-go/v2/registry" |
| 274 | + "oras.land/oras-go/v2/registry/remote" |
| 275 | + "oras.land/oras-go/v2/registry/remote/auth" |
| 276 | + "oras.land/oras-go/v2/registry/remote/retry" |
| 277 | +) |
| 278 | + |
| 279 | +func main() { |
| 280 | + // 1. Connect to a remote repository with token authentication |
| 281 | + ref := "example.registry.com/myrepo" |
| 282 | + repo, err := remote.NewRepository(ref) |
| 283 | + if err != nil { |
| 284 | + panic(err) |
| 285 | + } |
| 286 | + // Note: The below code can be omitted if authentication is not required. |
| 287 | + repo.Client = &auth.Client{ |
| 288 | + Client: retry.DefaultClient, |
| 289 | + Cache: auth.NewCache(), |
| 290 | + Credential: auth.StaticCredential(repo.Reference.Registry, auth.Credential{ |
| 291 | + Username: "username", |
| 292 | + Password: "password", |
| 293 | + }), |
| 294 | + } |
| 295 | + |
| 296 | + // 2. Show the tags in the repository |
| 297 | + ctx := context.Background() |
| 298 | + err = repo.Tags(ctx, "", func(tags []string) error { |
| 299 | + for _, tag := range tags { |
| 300 | + fmt.Println(tag) |
| 301 | + } |
| 302 | + return nil |
| 303 | + }) |
| 304 | + if err != nil { |
| 305 | + panic(err) |
| 306 | + } |
| 307 | + |
| 308 | + // 3. push a layer to the repository |
| 309 | + layer := []byte("example manifest layer") |
| 310 | + layerDescriptor, err := oras.PushBytes(ctx, repo, ocispec.MediaTypeImageLayer, layer) |
| 311 | + if err != nil { |
| 312 | + panic(err) |
| 313 | + } |
| 314 | + fmt.Println("Pushed manifest layer:", layerDescriptor.Digest) |
| 315 | + |
| 316 | + // 4. Push a manifest to the repository with the tag "quickstart" |
| 317 | + packOpts := oras.PackManifestOptions{ |
| 318 | + Layers: []ocispec.Descriptor{layerDescriptor}, |
| 319 | + } |
| 320 | + artifactType := "application/vnd.example+type" |
| 321 | + desc, err := oras.PackManifest(ctx, repo, oras.PackManifestVersion1_1, artifactType, packOpts) |
| 322 | + if err != nil { |
| 323 | + panic(err) |
| 324 | + } |
| 325 | + tag := "quickstart" |
| 326 | + err = repo.Tag(ctx, desc, tag) |
| 327 | + if err != nil { |
| 328 | + panic(err) |
| 329 | + } |
| 330 | + fmt.Println("Pushed and tagged manifest") |
| 331 | + |
| 332 | + // 5. Fetch the manifest from the repository by tag |
| 333 | + _, fetchedManifestContent, err := oras.FetchBytes(ctx, repo, tag, oras.DefaultFetchBytesOptions) |
| 334 | + if err != nil { |
| 335 | + panic(err) |
| 336 | + } |
| 337 | + fmt.Println(string(fetchedManifestContent)) |
| 338 | + |
| 339 | + // 6. Parse the fetched manifest content and get the layers |
| 340 | + var manifest ocispec.Manifest |
| 341 | + if err := json.Unmarshal(fetchedManifestContent, &manifest); err != nil { |
| 342 | + panic(err) |
| 343 | + } |
| 344 | + for _, layer := range manifest.Layers { |
| 345 | + layerContent, err := content.FetchAll(ctx, repo, layer) |
| 346 | + if err != nil { |
| 347 | + panic(err) |
| 348 | + } |
| 349 | + fmt.Println(string(layerContent)) |
| 350 | + } |
| 351 | + |
| 352 | + // 7. Copy the artifact to local OCI layout directory with a tag "quickstartOCI" |
| 353 | + ociDir, err := os.MkdirTemp(".", "oras_oci_example_*") |
| 354 | + if err != nil { |
| 355 | + panic(err) |
| 356 | + } |
| 357 | + ociTarget, err := oci.New(ociDir) |
| 358 | + if err != nil { |
| 359 | + panic(err) |
| 360 | + } |
| 361 | + _, err = oras.Copy(ctx, repo, tag, ociTarget, "quickstartOCI", oras.DefaultCopyOptions) |
| 362 | + if err != nil { |
| 363 | + panic(err) |
| 364 | + } |
| 365 | + fmt.Println("Copied the artifact") |
| 366 | +} |
| 367 | +``` |
0 commit comments