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  }