diff --git a/uriget/example_test.go b/uriget/example_test.go index f4ad784..99c4dff 100644 --- a/uriget/example_test.go +++ b/uriget/example_test.go @@ -52,7 +52,7 @@ func ExampleWithHttpClient() { // Output: failed to make get request: Get "https://example.com": no proxy } func ExampleGetFile_oci() { - testUrl := "oci://ghcr.io/score-spec/score-compose:0.18.0" + testUrl := "oci://ghcr.io/score-spec/score-compose-community-provisioners:v0.1.0#00-service.provisioners.yaml" buff, err := GetFile(context.Background(), testUrl) if err != nil { fmt.Println("failed to pull OCI image:", err) @@ -63,26 +63,38 @@ func ExampleGetFile_oci() { // true } -func ExampleGetFile_ociNoTag() { - testUrl := "oci://ghcr.io/score-spec/score-compose" - buff, err := GetFile(context.Background(), testUrl) +func ExampleGetFile_oci_git() { + ociTestUrl := "oci://ghcr.io/score-spec/score-compose-community-provisioners:v0.1.0#00-service.provisioners.yaml" + ociBuff, err := GetFile(context.Background(), ociTestUrl) if err != nil { fmt.Println("failed to pull OCI image:", err) return } - fmt.Println(len(buff) > 0) + gitTestUrl := "git-https://github.com/score-spec/community-provisioners.git/score-compose/00-service.provisioners.yaml" + gitBuff, err := GetFile(context.Background(), gitTestUrl) + if err != nil { + fmt.Println("failed to pull file in git:", err) + return + } + fmt.Println(len(ociBuff) == len(gitBuff)) // Output: // true } -func ExampleGetFile_ociWithDigest() { - testUrl := "oci://ghcr.io/score-spec/score-compose@sha256:f3d8d5485a751cbdc91e073df1b6fbcde83f85a86ee3bc7d53e05b00452baedd" - buff, err := GetFile(context.Background(), testUrl) +func ExampleGetFile_oci_https() { + ociTestUrl := "oci://ghcr.io/score-spec/score-compose-community-provisioners:v0.1.0#00-service.provisioners.yaml" + ociBuff, err := GetFile(context.Background(), ociTestUrl) if err != nil { fmt.Println("failed to pull OCI image:", err) return } - fmt.Println(len(buff) > 0) + httpsTestUrl := "https://github.com/score-spec/community-provisioners/raw/v0.1.0/score-compose/00-service.provisioners.yaml" + httpsbuff, err := GetFile(context.Background(), httpsTestUrl) + if err != nil { + fmt.Println("failed to pull file by HTTPS:", err) + return + } + fmt.Println(len(ociBuff) == len(httpsbuff)) // Output: // true } diff --git a/uriget/uriget.go b/uriget/uriget.go index 2d3a772..d498571 100644 --- a/uriget/uriget.go +++ b/uriget/uriget.go @@ -6,6 +6,7 @@ package uriget import ( "context" + "encoding/json" "fmt" "io" "log" @@ -17,8 +18,7 @@ import ( "strings" "time" - "oras.land/oras-go/v2" - "oras.land/oras-go/v2/content/oci" + v1 "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" ) @@ -244,25 +244,52 @@ func (o *options) getGit(ctx context.Context, u *url.URL) ([]byte, error) { func (o *options) getOci(ctx context.Context, u *url.URL) ([]byte, error) { ref, err := registry.ParseReference(u.Host + u.Path) if err != nil { - return nil, fmt.Errorf("can't parse artifact URL into a valid reference: %w", err) + return nil, fmt.Errorf("invalid artifact URL: %w", err) } - store, err := oci.New(o.tempDir) - if err != nil { - return nil, fmt.Errorf("failed to create OCI layout store: %w", err) + if ref.Reference == "" { + ref.Reference = "latest" } + specifiedFile := strings.TrimPrefix(u.Fragment, "#") remoteRepo, err := remote.NewRepository(ref.String()) if err != nil { - return nil, fmt.Errorf("failed to connect to remote repository: %w", err) + return nil, fmt.Errorf("connection to remote repository failed: %w", err) + } + _, rc, err := remoteRepo.Manifests().FetchReference(ctx, ref.Reference) + if err != nil { + return nil, fmt.Errorf("manifest fetch failed: %w", err) + } + defer rc.Close() + var manifest v1.Manifest + if err = json.NewDecoder(rc).Decode(&manifest); err != nil { + return nil, fmt.Errorf("manifest decode failed: %w", err) + } + var selectedLayer *v1.Descriptor + yamlFileCount := 0 + for _, layer := range manifest.Layers { + title := layer.Annotations[v1.AnnotationTitle] + if strings.HasSuffix(title, ".yaml") { + yamlFileCount++ + if specifiedFile == "" && yamlFileCount > 1 { + return nil, fmt.Errorf("manifest contains %d .yaml files; specify a specific file in the URL fragment", yamlFileCount) + } + if specifiedFile == "" || title == specifiedFile { + selectedLayer = &layer + break + } + } } - tag := "latest" - if ref.Reference != "" { - tag = ref.Reference + if selectedLayer == nil { + return nil, fmt.Errorf("no matching .yaml file found in layers") } - manifestDescriptor, err := oras.Copy(ctx, remoteRepo, tag, store, tag, oras.DefaultCopyOptions) + _, rc, err = remoteRepo.Blobs().FetchReference(ctx, selectedLayer.Digest.String()) if err != nil { - return nil, fmt.Errorf("failed to pull OCI image: %w", err) + return nil, fmt.Errorf("blob fetch failed: %w", err) } - - o.logger.Printf("Pulled OCI image: %s with manifest descriptor: %v", u.String(), manifestDescriptor.Digest) - return []byte(manifestDescriptor.Digest), nil + defer rc.Close() + buff, err := readLimited(rc, o.limit) + if err != nil { + return nil, fmt.Errorf("blob read failed: %w", err) + } + o.logger.Printf("Read %d bytes from %s", len(buff), specifiedFile) + return buff, nil }