oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/example_copy_test.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package oras_test 17 18 import ( 19 "bytes" 20 "context" 21 "encoding/json" 22 "fmt" 23 "net/http" 24 "net/http/httptest" 25 "net/url" 26 "os" 27 "strconv" 28 "strings" 29 "testing" 30 31 "github.com/opencontainers/go-digest" 32 specs "github.com/opencontainers/image-spec/specs-go" 33 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 34 "oras.land/oras-go/v2" 35 "oras.land/oras-go/v2/content/memory" 36 "oras.land/oras-go/v2/content/oci" 37 "oras.land/oras-go/v2/internal/spec" 38 "oras.land/oras-go/v2/registry/remote" 39 ) 40 41 var exampleMemoryStore oras.Target 42 var remoteHost string 43 var ( 44 exampleManifest, _ = json.Marshal(spec.Artifact{ 45 MediaType: spec.MediaTypeArtifactManifest, 46 ArtifactType: "example/content"}) 47 exampleManifestDescriptor = ocispec.Descriptor{ 48 MediaType: spec.MediaTypeArtifactManifest, 49 Digest: digest.Digest(digest.FromBytes(exampleManifest)), 50 Size: int64(len(exampleManifest))} 51 exampleSignatureManifest, _ = json.Marshal(spec.Artifact{ 52 MediaType: spec.MediaTypeArtifactManifest, 53 ArtifactType: "example/signature", 54 Subject: &exampleManifestDescriptor}) 55 exampleSignatureManifestDescriptor = ocispec.Descriptor{ 56 MediaType: spec.MediaTypeArtifactManifest, 57 Digest: digest.FromBytes(exampleSignatureManifest), 58 Size: int64(len(exampleSignatureManifest))} 59 ) 60 61 func pushBlob(ctx context.Context, mediaType string, blob []byte, target oras.Target) (desc ocispec.Descriptor, err error) { 62 desc = ocispec.Descriptor{ // Generate descriptor based on the media type and blob content 63 MediaType: mediaType, 64 Digest: digest.FromBytes(blob), // Calculate digest 65 Size: int64(len(blob)), // Include blob size 66 } 67 return desc, target.Push(ctx, desc, bytes.NewReader(blob)) // Push the blob to the registry target 68 } 69 70 func generateManifestContent(config ocispec.Descriptor, layers ...ocispec.Descriptor) ([]byte, error) { 71 content := ocispec.Manifest{ 72 Config: config, // Set config blob 73 Layers: layers, // Set layer blobs 74 Versioned: specs.Versioned{SchemaVersion: 2}, 75 } 76 return json.Marshal(content) // Get json content 77 } 78 79 func TestMain(m *testing.M) { 80 const exampleTag = "latest" 81 const exampleUploadUUid = "0bc84d80-837c-41d9-824e-1907463c53b3" 82 83 // Setup example local target 84 exampleMemoryStore = memory.New() 85 layerBlob := []byte("Hello layer") 86 ctx := context.Background() 87 layerDesc, err := pushBlob(ctx, ocispec.MediaTypeImageLayer, layerBlob, exampleMemoryStore) // push layer blob 88 if err != nil { 89 panic(err) 90 } 91 configBlob := []byte("Hello config") 92 configDesc, err := pushBlob(ctx, ocispec.MediaTypeImageConfig, configBlob, exampleMemoryStore) // push config blob 93 if err != nil { 94 panic(err) 95 } 96 manifestBlob, err := generateManifestContent(configDesc, layerDesc) // generate a image manifest 97 if err != nil { 98 panic(err) 99 } 100 manifestDesc, err := pushBlob(ctx, ocispec.MediaTypeImageManifest, manifestBlob, exampleMemoryStore) // push manifest blob 101 if err != nil { 102 panic(err) 103 } 104 err = exampleMemoryStore.Tag(ctx, manifestDesc, exampleTag) 105 if err != nil { 106 panic(err) 107 } 108 109 // Setup example remote target 110 httpsServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 111 p := r.URL.Path 112 m := r.Method 113 switch { 114 case strings.Contains(p, "/blobs/uploads/") && m == "POST": 115 w.Header().Set("Content-Type", ocispec.MediaTypeImageManifest) 116 w.Header().Set("Location", p+exampleUploadUUid) 117 w.WriteHeader(http.StatusAccepted) 118 case strings.Contains(p, "/blobs/uploads/"+exampleUploadUUid) && m == "GET": 119 w.WriteHeader(http.StatusCreated) 120 case strings.Contains(p, "/manifests/"+string(exampleSignatureManifestDescriptor.Digest)): 121 w.Header().Set("Content-Type", spec.MediaTypeArtifactManifest) 122 w.Header().Set("Docker-Content-Digest", string(exampleSignatureManifestDescriptor.Digest)) 123 w.Header().Set("Content-Length", strconv.Itoa(len(exampleSignatureManifest))) 124 w.Write(exampleSignatureManifest) 125 case strings.Contains(p, "/manifests/latest") && m == "PUT": 126 w.WriteHeader(http.StatusCreated) 127 case strings.Contains(p, "/manifests/"+string(exampleManifestDescriptor.Digest)), 128 strings.Contains(p, "/manifests/latest") && m == "HEAD": 129 w.Header().Set("Content-Type", spec.MediaTypeArtifactManifest) 130 w.Header().Set("Docker-Content-Digest", string(exampleManifestDescriptor.Digest)) 131 w.Header().Set("Content-Length", strconv.Itoa(len(exampleManifest))) 132 if m == "GET" { 133 w.Write(exampleManifest) 134 } 135 case strings.Contains(p, "/v2/source/referrers/"): 136 var referrers []ocispec.Descriptor 137 if p == "/v2/source/referrers/"+exampleManifestDescriptor.Digest.String() { 138 referrers = []ocispec.Descriptor{exampleSignatureManifestDescriptor} 139 } 140 result := ocispec.Index{ 141 Versioned: specs.Versioned{ 142 SchemaVersion: 2, // historical value. does not pertain to OCI or docker version 143 }, 144 MediaType: ocispec.MediaTypeImageIndex, 145 Manifests: referrers, 146 } 147 w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex) 148 if err := json.NewEncoder(w).Encode(result); err != nil { 149 panic(err) 150 } 151 case strings.Contains(p, "/manifests/") && (m == "HEAD" || m == "GET"): 152 w.Header().Set("Content-Type", ocispec.MediaTypeImageManifest) 153 w.Header().Set("Docker-Content-Digest", string(manifestDesc.Digest)) 154 w.Header().Set("Content-Length", strconv.Itoa(len([]byte(manifestBlob)))) 155 w.Write([]byte(manifestBlob)) 156 case strings.Contains(p, "/blobs/") && (m == "GET" || m == "HEAD"): 157 arr := strings.Split(p, "/") 158 digest := arr[len(arr)-1] 159 var desc ocispec.Descriptor 160 var content []byte 161 switch digest { 162 case layerDesc.Digest.String(): 163 desc = layerDesc 164 content = layerBlob 165 case configDesc.Digest.String(): 166 desc = configDesc 167 content = configBlob 168 case manifestDesc.Digest.String(): 169 desc = manifestDesc 170 content = manifestBlob 171 } 172 w.Header().Set("Content-Type", desc.MediaType) 173 w.Header().Set("Docker-Content-Digest", digest) 174 w.Header().Set("Content-Length", strconv.Itoa(len([]byte(content)))) 175 w.Write([]byte(content)) 176 case strings.Contains(p, "/manifests/") && m == "PUT": 177 w.WriteHeader(http.StatusCreated) 178 } 179 180 })) 181 defer httpsServer.Close() 182 u, err := url.Parse(httpsServer.URL) 183 if err != nil { 184 panic(err) 185 } 186 remoteHost = u.Host 187 http.DefaultTransport = httpsServer.Client().Transport 188 189 os.Exit(m.Run()) 190 } 191 192 func ExampleCopy_remoteToRemote() { 193 reg, err := remote.NewRegistry(remoteHost) 194 if err != nil { 195 panic(err) // Handle error 196 } 197 ctx := context.Background() 198 src, err := reg.Repository(ctx, "source") 199 if err != nil { 200 panic(err) // Handle error 201 } 202 dst, err := reg.Repository(ctx, "target") 203 if err != nil { 204 panic(err) // Handle error 205 } 206 207 tagName := "latest" 208 desc, err := oras.Copy(ctx, src, tagName, dst, tagName, oras.DefaultCopyOptions) 209 if err != nil { 210 panic(err) // Handle error 211 } 212 fmt.Println(desc.Digest) 213 214 // Output: 215 // sha256:7cbb44b44e8ede5a89cf193db3f5f2fd019d89697e6b87e8ed2589e60649b0d1 216 } 217 218 func ExampleCopy_remoteToRemoteWithMount() { 219 reg, err := remote.NewRegistry(remoteHost) 220 if err != nil { 221 panic(err) // Handle error 222 } 223 ctx := context.Background() 224 src, err := reg.Repository(ctx, "source") 225 if err != nil { 226 panic(err) // Handle error 227 } 228 dst, err := reg.Repository(ctx, "target") 229 if err != nil { 230 panic(err) // Handle error 231 } 232 233 tagName := "latest" 234 235 opts := oras.CopyOptions{} 236 // optionally be notified that a mount occurred. 237 opts.OnMounted = func(ctx context.Context, desc ocispec.Descriptor) error { 238 // log.Println("Mounted", desc.Digest) 239 return nil 240 } 241 242 // Enable cross-repository blob mounting 243 opts.MountFrom = func(ctx context.Context, desc ocispec.Descriptor) ([]string, error) { 244 // the slice of source repositores may also come from a database of known locations of blobs 245 return []string{"source/repository/name"}, nil 246 } 247 248 desc, err := oras.Copy(ctx, src, tagName, dst, tagName, opts) 249 if err != nil { 250 panic(err) // Handle error 251 } 252 fmt.Println("Final", desc.Digest) 253 254 // Output: 255 // Final sha256:7cbb44b44e8ede5a89cf193db3f5f2fd019d89697e6b87e8ed2589e60649b0d1 256 } 257 258 func ExampleCopy_remoteToLocal() { 259 reg, err := remote.NewRegistry(remoteHost) 260 if err != nil { 261 panic(err) // Handle error 262 } 263 264 ctx := context.Background() 265 src, err := reg.Repository(ctx, "source") 266 if err != nil { 267 panic(err) // Handle error 268 } 269 dst := memory.New() 270 271 tagName := "latest" 272 desc, err := oras.Copy(ctx, src, tagName, dst, tagName, oras.DefaultCopyOptions) 273 if err != nil { 274 panic(err) // Handle error 275 } 276 fmt.Println(desc.Digest) 277 278 // Output: 279 // sha256:7cbb44b44e8ede5a89cf193db3f5f2fd019d89697e6b87e8ed2589e60649b0d1 280 } 281 282 func ExampleCopy_localToLocal() { 283 src := exampleMemoryStore 284 dst := memory.New() 285 286 tagName := "latest" 287 ctx := context.Background() 288 desc, err := oras.Copy(ctx, src, tagName, dst, tagName, oras.DefaultCopyOptions) 289 if err != nil { 290 panic(err) // Handle error 291 } 292 fmt.Println(desc.Digest) 293 294 // Output: 295 // sha256:7cbb44b44e8ede5a89cf193db3f5f2fd019d89697e6b87e8ed2589e60649b0d1 296 } 297 298 func ExampleCopy_localToOciFile() { 299 src := exampleMemoryStore 300 tempDir, err := os.MkdirTemp("", "oras_oci_example_*") 301 if err != nil { 302 panic(err) // Handle error 303 } 304 defer os.RemoveAll(tempDir) 305 dst, err := oci.New(tempDir) 306 if err != nil { 307 panic(err) // Handle error 308 } 309 310 tagName := "latest" 311 ctx := context.Background() 312 desc, err := oras.Copy(ctx, src, tagName, dst, tagName, oras.DefaultCopyOptions) 313 if err != nil { 314 panic(err) // Handle error 315 } 316 fmt.Println(desc.Digest) 317 318 // Output: 319 // sha256:7cbb44b44e8ede5a89cf193db3f5f2fd019d89697e6b87e8ed2589e60649b0d1 320 } 321 322 func ExampleCopy_localToRemote() { 323 src := exampleMemoryStore 324 reg, err := remote.NewRegistry(remoteHost) 325 if err != nil { 326 panic(err) // Handle error 327 } 328 ctx := context.Background() 329 dst, err := reg.Repository(ctx, "target") 330 if err != nil { 331 panic(err) // Handle error 332 } 333 334 tagName := "latest" 335 desc, err := oras.Copy(ctx, src, tagName, dst, tagName, oras.DefaultCopyOptions) 336 if err != nil { 337 panic(err) // Handle error 338 } 339 fmt.Println(desc.Digest) 340 341 // Output: 342 // sha256:7cbb44b44e8ede5a89cf193db3f5f2fd019d89697e6b87e8ed2589e60649b0d1 343 } 344 345 // ExampleCopyArtifactManifestRemoteToLocal gives an example of copying 346 // an artifact manifest from a remote repository into memory. 347 func Example_copyArtifactManifestRemoteToLocal() { 348 src, err := remote.NewRepository(fmt.Sprintf("%s/source", remoteHost)) 349 if err != nil { 350 panic(err) 351 } 352 dst := memory.New() 353 ctx := context.Background() 354 355 exampleDigest := "sha256:70c29a81e235dda5c2cebb8ec06eafd3cca346cbd91f15ac74cefd98681c5b3d" 356 descriptor, err := src.Resolve(ctx, exampleDigest) 357 if err != nil { 358 panic(err) 359 } 360 err = oras.CopyGraph(ctx, src, dst, descriptor, oras.DefaultCopyGraphOptions) 361 if err != nil { 362 panic(err) 363 } 364 365 // verify that the artifact manifest described by the descriptor exists in dst 366 contentExists, err := dst.Exists(ctx, descriptor) 367 if err != nil { 368 panic(err) 369 } 370 fmt.Println(contentExists) 371 372 // Output: 373 // true 374 } 375 376 // ExampleExtendedCopyArtifactAndReferrersRemoteToLocal gives an example of 377 // copying an artifact along with its referrers from a remote repository into 378 // memory. 379 func Example_extendedCopyArtifactAndReferrersRemoteToLocal() { 380 src, err := remote.NewRepository(fmt.Sprintf("%s/source", remoteHost)) 381 if err != nil { 382 panic(err) 383 } 384 dst := memory.New() 385 ctx := context.Background() 386 387 tagName := "latest" 388 // ExtendedCopy will copy the artifact tagged by "latest" along with all of its 389 // referrers from src to dst. 390 desc, err := oras.ExtendedCopy(ctx, src, tagName, dst, tagName, oras.DefaultExtendedCopyOptions) 391 if err != nil { 392 panic(err) 393 } 394 395 fmt.Println(desc.Digest) 396 // Output: 397 // sha256:f396bc4d300934a39ca28ab0d5ac8a3573336d7d63c654d783a68cd1e2057662 398 }