/* * Copyright 2026 Oleg Borodin * * This work is published and licensed under a Creative Commons * Attribution-NonCommercial-NoDerivatives 4.0 International License. * * Distribution of this work is permitted, but commercial use and * modifications are strictly prohibited. */ package client import ( "context" "encoding/json" "fmt" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/crane" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) const ( indexMediaType = "application/vnd.oci.image.index.v1+json" manifestMediaType = "application/vnd.oci.image.manifest.v1+json" ) func (cli *Client) ImageManifest(ctx context.Context, imagepath string) (any, error) { var err error var res any imagepath, username, password, err := repackReference(imagepath) if err != nil { return res, err } options := make([]crane.Option, 0) options = append(options, crane.WithContext(ctx)) ref, err := name.ParseReference(imagepath) if err != nil { return res, err } repo := ref.Context() if err != nil { return res, err } // Manifest if username != "" && password != "" { defaultTransport := NewRoundTripper(cli.skipTLSVerify) scopes := []string{repo.Scope(transport.PullScope)} regName := repo.RegistryStr() reg, err := name.NewRegistry(regName) if err != nil { return res, err } basicAuth := &authn.Basic{ Username: username, Password: password, } authTransport, err := transport.NewWithContext(ctx, reg, basicAuth, defaultTransport, scopes) if err != nil { return res, err } options = append(options, crane.WithTransport(authTransport)) } else { transport := NewRoundTripper(cli.skipTLSVerify) options = append(options, crane.WithTransport(transport)) } manifestBytes, err := crane.Manifest(imagepath, options...) if err != nil { return res, err } var descr ocispec.Descriptor err = json.Unmarshal(manifestBytes, &descr) if err != nil { return res, err } switch descr.MediaType { case indexMediaType: var index ocispec.Index err = json.Unmarshal(manifestBytes, &index) if err != nil { return res, err } res = index //case manifestMediaType: default: var manifest ocispec.Manifest err = json.Unmarshal(manifestBytes, &manifest) if err != nil { return res, err } res = &manifest fmt.Printf("Unknown or empty media type: %s", descr.MediaType) } return res, err } func (cli *Client) ImageTags(ctx context.Context, imagepath string) ([]string, error) { var err error res := make([]string, 0) imagepath, username, password, err := repackReference(imagepath) if err != nil { return res, err } options := make([]crane.Option, 0) options = append(options, crane.WithContext(ctx)) ref, err := name.ParseReference(imagepath) if err != nil { return res, err } repo := ref.Context() if err != nil { return res, err } // Tags remoteOptions := make([]remote.Option, 0) remoteOptions = append(remoteOptions, remote.WithContext(ctx)) if username != "" && password != "" { defaultTransport := NewRoundTripper(cli.skipTLSVerify) scopes := []string{repo.Scope(transport.PullScope)} regName := repo.RegistryStr() reg, err := name.NewRegistry(regName) if err != nil { return res, err } basicAuth := &authn.Basic{ Username: username, Password: password, } authTransport, err := transport.NewWithContext(ctx, reg, basicAuth, defaultTransport, scopes) if err != nil { return res, err } remoteOptions = append(remoteOptions, remote.WithTransport(authTransport)) } else { transport := NewRoundTripper(cli.skipTLSVerify) options = append(options, crane.WithTransport(transport)) } tags, err := remote.List(repo, remoteOptions...) if err != nil { return res, err } res = tags return res, err } func (cli *Client) ImageConfig(ctx context.Context, imagepath string) (*ocispec.Image, error) { var err error res := &ocispec.Image{} imagepath, username, password, err := repackReference(imagepath) if err != nil { return res, err } options := make([]crane.Option, 0) options = append(options, crane.WithContext(ctx)) ref, err := name.ParseReference(imagepath) if err != nil { return res, err } repo := ref.Context() if err != nil { return res, err } // Config if username != "" && password != "" { defaultTransport := NewRoundTripper(cli.skipTLSVerify) scopes := []string{repo.Scope(transport.PullScope)} regName := repo.RegistryStr() reg, err := name.NewRegistry(regName) if err != nil { return res, err } basicAuth := &authn.Basic{ Username: username, Password: password, } authTransport, err := transport.NewWithContext(ctx, reg, basicAuth, defaultTransport, scopes) if err != nil { return res, err } options = append(options, crane.WithTransport(authTransport)) } else { transport := NewRoundTripper(cli.skipTLSVerify) options = append(options, crane.WithTransport(transport)) } configBytes, err := crane.Config(imagepath, options...) if err != nil { return res, err } var config ocispec.Image err = json.Unmarshal(configBytes, &config) if err != nil { return res, err } res = &config return res, err } func (cli *Client) CatalogImages(ctx context.Context, source string) ([]string, error) { var err error res := make([]string, 0) source, _, _, err = repackSource(source) if err != nil { return res, err } options := make([]crane.Option, 0) options = append(options, crane.WithContext(ctx)) // Config transport := NewRoundTripper(cli.skipTLSVerify) options = append(options, crane.WithTransport(transport)) res, err = crane.Catalog(source, options...) if err != nil { return res, err } return res, err }