diff --git a/cmd/hauler/cli/download/download.go b/cmd/hauler/cli/download/download.go index 7b39f49..5c2cdcd 100644 --- a/cmd/hauler/cli/download/download.go +++ b/cmd/hauler/cli/download/download.go @@ -3,8 +3,6 @@ package download import ( "context" "encoding/json" - "fmt" - "path" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" @@ -12,7 +10,6 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/pkg/content" "oras.land/oras-go/pkg/oras" - "oras.land/oras-go/pkg/target" "github.com/rancherfederal/hauler/internal/mapper" "github.com/rancherfederal/hauler/pkg/consts" @@ -59,46 +56,17 @@ func Cmd(ctx context.Context, o *Opts, reference string) error { return err } - var ms target.Target - // TODO: These need to be factored out into each of the contents own logic - switch manifest.Config.MediaType { - case consts.DockerConfigJSON, consts.OCIManifestSchema1: - l.Debugf("identified [image] (%s) content", manifest.Config.MediaType) - - outputFile := o.OutputFile - if outputFile == "" { - outputFile = fmt.Sprintf("%s:%s.tar", path.Base(ref.Context().RepositoryStr()), ref.Identifier()) - } - - s := mapper.NewMapperFileStore(o.DestinationDir, mapper.Images()) - defer s.Close() - ms = s - - case consts.FileLocalConfigMediaType: - l.Debugf("identified [file] (%s) content", manifest.Config.MediaType) - - s := mapper.NewMapperFileStore(o.DestinationDir, nil) - defer s.Close() - ms = s - - case consts.ChartLayerMediaType, consts.ChartConfigMediaType: - l.Debugf("identified [chart] (%s) content", manifest.Config.MediaType) - - s := mapper.NewMapperFileStore(o.DestinationDir, mapper.Chart()) - defer s.Close() - ms = s - - default: - return fmt.Errorf("unrecognized content type: %s", manifest.Config.MediaType) + mapperStore, err := mapper.FromManifest(manifest, o.DestinationDir) + if err != nil { + return err } - pushedDesc, err := oras.Copy(ctx, rs, ref.Name(), ms, "", + pushedDesc, err := oras.Copy(ctx, rs, ref.Name(), mapperStore, "", oras.WithAdditionalCachedMediaTypes(consts.DockerManifestSchema2)) if err != nil { return err } l.Infof("downloaded [%s] with digest [%s]", pushedDesc.MediaType, pushedDesc.Digest.String()) - return nil } diff --git a/cmd/hauler/cli/store/copy.go b/cmd/hauler/cli/store/copy.go index 4dcacb5..be9da91 100644 --- a/cmd/hauler/cli/store/copy.go +++ b/cmd/hauler/cli/store/copy.go @@ -22,11 +22,11 @@ func (o *CopyOpts) AddFlags(cmd *cobra.Command) { func CopyCmd(ctx context.Context, o *CopyOpts, s *store.Store, targetRef string) error { l := log.FromContext(ctx) - _ = l components := strings.SplitN(targetRef, "://", 2) switch components[0] { case "dir": + l.Debugf("identified directory target reference") fs := content.NewFile(components[1]) defer fs.Close() @@ -35,6 +35,7 @@ func CopyCmd(ctx context.Context, o *CopyOpts, s *store.Store, targetRef string) } case "registry": + l.Debugf("identified registry target reference") r, err := content.NewRegistry(content.RegistryOptions{}) if err != nil { return err @@ -54,7 +55,6 @@ func CopyCmd(ctx context.Context, o *CopyOpts, s *store.Store, targetRef string) default: return errors.Errorf("determining target protocol from: [%s]", targetRef) - } return nil } diff --git a/cmd/hauler/cli/store/extract.go b/cmd/hauler/cli/store/extract.go index 41d22dd..bc285cc 100644 --- a/cmd/hauler/cli/store/extract.go +++ b/cmd/hauler/cli/store/extract.go @@ -2,10 +2,15 @@ package store import ( "context" + "encoding/json" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/layout" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" - "github.com/rancherfederal/hauler/cmd/hauler/cli/download" + "github.com/rancherfederal/hauler/internal/mapper" + "github.com/rancherfederal/hauler/pkg/log" "github.com/rancherfederal/hauler/pkg/store" ) @@ -20,14 +25,52 @@ func (o *ExtractOpts) AddArgs(cmd *cobra.Command) { } func ExtractCmd(ctx context.Context, o *ExtractOpts, s *store.Store, reference string) error { - eref, err := store.RelocateReference(reference, "") + l := log.FromContext(ctx) + + ref, err := name.ParseReference(reference, name.WithDefaultRegistry(""), name.WithDefaultTag("latest")) if err != nil { return err } - gopts := &download.Opts{ - DestinationDir: o.DestinationDir, + p, err := layout.FromPath("store") + if err != nil { + return err } - return download.Cmd(ctx, gopts, eref.Name()) + ii, _ := p.ImageIndex() + im, _ := ii.IndexManifest() + var manifest ocispec.Manifest + for _, m := range im.Manifests { + if r, ok := m.Annotations[ocispec.AnnotationRefName]; !ok || r != ref.Name() { + continue + } + + desc, err := p.Image(m.Digest) + if err != nil { + return err + } + l.Infof(m.Annotations[ocispec.AnnotationRefName]) + + manifestData, err := desc.RawManifest() + if err != nil { + return err + } + + if err := json.Unmarshal(manifestData, &manifest); err != nil { + return err + } + } + + mapperStore, err := mapper.FromManifest(manifest, o.DestinationDir) + if err != nil { + return err + } + + desc, err := s.Get(ctx, mapperStore, ref.Name()) + if err != nil { + return err + } + + l.Infof("downloaded [%s] with digest [%s]", desc.MediaType, desc.Digest.String()) + return nil } diff --git a/internal/mapper/mappers.go b/internal/mapper/mappers.go index 7cc72d4..d365e7a 100644 --- a/internal/mapper/mappers.go +++ b/internal/mapper/mappers.go @@ -4,12 +4,34 @@ import ( "fmt" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras-go/pkg/target" "github.com/rancherfederal/hauler/pkg/consts" ) type Fn func(desc ocispec.Descriptor) (string, error) +// FromManifest will return the appropriate content store given a reference and source type adequate for storing the results on disk +func FromManifest(manifest ocispec.Manifest, root string) (target.Target, error) { + switch manifest.Config.MediaType { + case consts.DockerConfigJSON, consts.OCIManifestSchema1: + s := NewMapperFileStore(root, Images()) + defer s.Close() + return s, nil + + case consts.FileLocalConfigMediaType: + s := NewMapperFileStore(root, nil) + defer s.Close() + return s, nil + + case consts.ChartLayerMediaType, consts.ChartConfigMediaType: + s := NewMapperFileStore(root, Chart()) + defer s.Close() + return s, nil + } + return nil, fmt.Errorf("could not identify mapper from manifest with media type [%s]", manifest.MediaType) +} + func Images() map[string]Fn { m := make(map[string]Fn) diff --git a/pkg/store/store.go b/pkg/store/store.go index a3b1678..069fc1e 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -124,13 +124,10 @@ func (s *Store) List(ctx context.Context) ([]ocispec.Descriptor, error) { return descs, nil } -func (s *Store) Get(ctx context.Context, to target.Target, reference string) error { - _, err := oras.Copy(ctx, s.store, reference, to, "", +// Get given a reference, +func (s *Store) Get(ctx context.Context, to target.Target, reference string) (ocispec.Descriptor, error) { + return oras.Copy(ctx, s.store, reference, to, "", oras.WithAdditionalCachedMediaTypes(consts.DockerManifestSchema2)) - if err != nil { - return err - } - return nil } // Copy performs bulk copy operations on the stores oci layout to a provided target.Target