primary: refactor store and transport to use oci-layouts and add fileserver feature

minors:
* add optional 'extraImages' to ThickCharts
* refactor File content into generic getter interfaces
* refactor artifact.Config into an actual usable interface (by File content)
* refactor 'copy' cli command to use oras mappers
* refactor 'serve' cli command to server registry and/or fileserver
This commit is contained in:
Josh Wolf
2021-12-01 14:53:06 -07:00
parent 32d24b2b26
commit d87d8a2041
58 changed files with 2569 additions and 1460 deletions
+2 -3
View File
@@ -23,8 +23,7 @@ airgap-scp.sh
# generated
dist/
./bundle/
tmp/
bin/
pkg.yaml
haul/
/store/
/registry/
+1 -1
View File
@@ -1,4 +1,4 @@
# Hauler: Airgap Assistant
# Hauler: Airgap Swiss Army Knife
> ⚠️ This project is still in active development and _not_ GA. While a lot of the core features are ready, we're still adding a _ton_, and we may make breaking api and feature changes version to version.
+15 -18
View File
@@ -8,7 +8,7 @@ import (
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/pkg/cache"
cache2 "github.com/rancherfederal/hauler/internal/cache"
"github.com/rancherfederal/hauler/pkg/log"
"github.com/rancherfederal/hauler/pkg/store"
)
@@ -19,10 +19,13 @@ type rootOpts struct {
storeDir string
}
const defaultStoreLocation = "haul"
var ro = &rootOpts{}
const (
DefaultStoreName = "store"
DefaultCacheDir = "hauler"
)
func New() *cobra.Command {
cmd := &cobra.Command{
Use: "hauler",
@@ -41,11 +44,12 @@ func New() *cobra.Command {
pf := cmd.PersistentFlags()
pf.StringVarP(&ro.logLevel, "log-level", "l", "info", "")
pf.StringVar(&ro.cacheDir, "cache", "", "Location of where to store cache data (defaults to $XDG_CACHE_DIR/hauler)")
pf.StringVarP(&ro.storeDir, "store", "s", "", "Location to create store at (defaults to $PWD/store)")
pf.StringVarP(&ro.storeDir, "store", "s", DefaultStoreName, "Location to create store at")
// Add subcommands
addDownload(cmd)
addStore(cmd)
addServe(cmd)
addVersion(cmd)
return cmd
@@ -55,16 +59,6 @@ func (o *rootOpts) getStore(ctx context.Context) (*store.Store, error) {
l := log.FromContext(ctx)
dir := o.storeDir
if dir == "" {
l.Debugf("no store path specified, defaulting to $PWD/store")
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
dir = filepath.Join(pwd, defaultStoreLocation)
}
abs, err := filepath.Abs(dir)
if err != nil {
return nil, err
@@ -86,11 +80,14 @@ func (o *rootOpts) getStore(ctx context.Context) (*store.Store, error) {
return nil, err
}
s := store.NewStore(ctx, abs, store.WithCache(c))
s, err := store.NewStore(abs, store.WithCache(c))
if err != nil {
return nil, err
}
return s, nil
}
func (o *rootOpts) getCache(ctx context.Context) (cache.Cache, error) {
func (o *rootOpts) getCache(ctx context.Context) (cache2.Cache, error) {
dir := o.cacheDir
if dir == "" {
@@ -100,7 +97,7 @@ func (o *rootOpts) getCache(ctx context.Context) (cache.Cache, error) {
return nil, err
}
abs, _ := filepath.Abs(filepath.Join(cachedir, "hauler"))
abs, _ := filepath.Abs(filepath.Join(cachedir, DefaultCacheDir))
if err := os.MkdirAll(abs, os.ModePerm); err != nil {
return nil, err
}
@@ -108,6 +105,6 @@ func (o *rootOpts) getCache(ctx context.Context) (cache.Cache, error) {
dir = abs
}
c := cache.NewFilesystem(dir)
c := cache2.NewFilesystem(dir)
return c, nil
}
+60 -43
View File
@@ -6,17 +6,19 @@ import (
"fmt"
"path"
"github.com/containerd/containerd/remotes/docker"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"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/pkg/artifact/types"
"github.com/rancherfederal/hauler/internal/mapper"
"github.com/rancherfederal/hauler/pkg/consts"
"github.com/rancherfederal/hauler/pkg/content/chart"
"github.com/rancherfederal/hauler/pkg/content/file"
"github.com/rancherfederal/hauler/pkg/content/image"
"github.com/rancherfederal/hauler/pkg/log"
)
@@ -35,8 +37,13 @@ func (o *Opts) AddArgs(cmd *cobra.Command) {
func Cmd(ctx context.Context, o *Opts, reference string) error {
l := log.FromContext(ctx)
cs := content.NewFileStore(o.DestinationDir)
defer cs.Close()
fs := content.NewFile(o.DestinationDir)
defer fs.Close()
rs, err := content.NewRegistry(content.RegistryOptions{})
if err != nil {
return err
}
ref, err := name.ParseReference(reference)
if err != nil {
@@ -58,66 +65,76 @@ 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 types.DockerConfigJSON, types.OCIManifestSchema1:
case consts.DockerConfigJSON, consts.OCIManifestSchema1:
l.Debugf("identified [image] (%s) content", manifest.Config.MediaType)
img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return err
}
outputFile := o.OutputFile
if outputFile == "" {
outputFile = fmt.Sprintf("%s:%s.tar", path.Base(ref.Context().RepositoryStr()), ref.Identifier())
}
if err := tarball.WriteToFile(outputFile, ref, img); err != nil {
return err
}
is := mapper.NewStore(o.DestinationDir, image.Mapper())
defer is.Close()
l.Infof("downloaded image [%s] to [%s]", ref.Name(), outputFile)
ms = is
case types.FileConfigMediaType:
// l.Infof("downloaded image [%s] to [%s]", ref.Name(), outputFile)
case consts.FileConfigMediaType:
l.Debugf("identified [file] (%s) content", manifest.Config.MediaType)
fs := content.NewFileStore(o.DestinationDir)
fs := mapper.NewStore(o.DestinationDir, file.Mapper())
defer fs.Close()
resolver := docker.NewResolver(docker.ResolverOptions{})
_, descs, err := oras.Pull(ctx, resolver, ref.Name(), fs)
if err != nil {
return err
}
ms = fs
ldescs := len(descs)
for i, desc := range descs {
// NOTE: This is safe without a map key check b/c we're not allowing unnamed content from oras.Pull
l.Infof("downloaded (%d/%d) files to [%s]", i+1, ldescs, desc.Annotations[ocispec.AnnotationTitle])
}
// _, err := oras.Copy(ctx, rs, ref.Name(), fs, "",
// oras.WithLayerDescriptors(func(descriptors []ocispec.Descriptor) {
// for _, desc := range descriptors {
// if _, ok := desc.Annotations[ocispec.AnnotationTitle]; !ok {
// continue
// }
// descs = append(descs, desc)
// }
// }))
// if err != nil {
// return err
// }
//
// ldescs := len(descs)
// for i, desc := range descs {
// // NOTE: This is safe without a map key check b/c we're not allowing unnamed content from oras.Pull
// l.Infof("downloaded (%d/%d) files to [%s]", i+1, ldescs, desc.Annotations[ocispec.AnnotationTitle])
// }
case types.ChartLayerMediaType, types.ChartConfigMediaType:
case consts.ChartLayerMediaType, consts.ChartConfigMediaType:
l.Debugf("identified [chart] (%s) content", manifest.Config.MediaType)
fs := content.NewFileStore(o.DestinationDir)
cs := mapper.NewStore(o.DestinationDir, chart.Mapper())
defer cs.Close()
resolver := docker.NewResolver(docker.ResolverOptions{})
_, descs, err := oras.Pull(ctx, resolver, ref.Name(), fs)
if err != nil {
return err
}
cn := path.Base(ref.Name())
for _, d := range descs {
if n, ok := d.Annotations[ocispec.AnnotationTitle]; ok {
cn = n
}
}
l.Infof("downloaded chart [%s] to [%s]", ref.String(), cn)
ms = cs
// desc, err := oras.Copy(ctx, rs, ref.Name(), fs, "")
// if err != nil {
// return err
// }
//
// l.Infof("downloaded chart [%s] to [%s]", ref.String(), desc.Annotations[ocispec.AnnotationTitle])
default:
return fmt.Errorf("unrecognized content type: %s", manifest.Config.MediaType)
}
pushedDesc, err := oras.Copy(ctx, rs, ref.Name(), ms, "",
oras.WithAdditionalCachedMediaTypes(consts.DockerManifestSchema2))
if err != nil {
return err
}
l.Infof("downloaded [%s] with digest [%s]", pushedDesc.MediaType, pushedDesc.Digest.String())
return nil
}
+55
View File
@@ -0,0 +1,55 @@
package cli
import (
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/cmd/hauler/cli/serve"
)
func addServe(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "serve",
Short: "Run one or more of hauler's embedded servers types",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
cmd.AddCommand(
addServeFiles(),
addServeRegistry(),
)
parent.AddCommand(cmd)
}
func addServeFiles() *cobra.Command {
o := &serve.FilesOpts{}
cmd := &cobra.Command{
Use: "files",
Short: "Start a fileserver",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
return serve.FilesCmd(ctx, o)
},
}
o.AddFlags(cmd)
return cmd
}
func addServeRegistry() *cobra.Command {
o := &serve.RegistryOpts{}
cmd := &cobra.Command{
Use: "registry",
Short: "Start a registry",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
return serve.RegistryCmd(ctx, o)
},
}
o.AddFlags(cmd)
return cmd
}
+33
View File
@@ -0,0 +1,33 @@
package serve
import (
"context"
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/pkg/server"
)
type FilesOpts struct {
Root string
Port int
}
func (o *FilesOpts) AddFlags(cmd *cobra.Command) {
f := cmd.Flags()
f.StringVarP(&o.Root, "root", "r", ".", "Path to root of the directory to serve")
f.IntVarP(&o.Port, "port", "p", 8080, "Port to listen on")
}
func FilesCmd(ctx context.Context, o *FilesOpts) error {
s, err := server.NewFile(ctx, o.Root)
if err != nil {
return err
}
if err := s.ListenAndServe(); err != nil {
return err
}
return nil
}
+83
View File
@@ -0,0 +1,83 @@
package serve
import (
"context"
"fmt"
"net/http"
"os"
"github.com/distribution/distribution/v3/configuration"
dcontext "github.com/distribution/distribution/v3/context"
"github.com/distribution/distribution/v3/version"
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/pkg/server"
)
type RegistryOpts struct {
Root string
Port int
ConfigFile string
}
func (o *RegistryOpts) AddFlags(cmd *cobra.Command) {
f := cmd.Flags()
f.StringVarP(&o.Root, "root", "r", ".", "Path to root of the directory to serve")
f.IntVarP(&o.Port, "port", "p", 5000, "Port to listen on")
f.StringVarP(&o.ConfigFile, "config", "c", "", "Path to a config file, will override all other configs")
}
func RegistryCmd(ctx context.Context, o *RegistryOpts) error {
ctx = dcontext.WithVersion(ctx, version.Version)
cfg := o.defaultConfig()
if o.ConfigFile != "" {
ucfg, err := loadConfig(o.ConfigFile)
if err != nil {
return err
}
cfg = ucfg
}
s, err := server.NewRegistry(ctx, cfg)
if err != nil {
return err
}
if err := s.ListenAndServe(); err != nil {
return err
}
// TODO: Graceful cancelling
return nil
}
func loadConfig(filename string) (*configuration.Configuration, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
return configuration.Parse(f)
}
func (o *RegistryOpts) defaultConfig() *configuration.Configuration {
cfg := &configuration.Configuration{
Version: "0.1",
Storage: configuration.Storage{
"cache": configuration.Parameters{"blobdescriptor": "inmemory"},
"filesystem": configuration.Parameters{"rootdirectory": o.Root},
// TODO: Ensure this is toggleable via cli arg if necessary
// "maintenance": configuration.Parameters{"readonly.enabled": false},
},
}
cfg.Log.Level = "info"
cfg.HTTP.Addr = fmt.Sprintf(":%d", o.Port)
cfg.HTTP.Headers = http.Header{
"X-Content-Type-Options": []string{"nosniff"},
}
return cfg
}
+11 -9
View File
@@ -22,7 +22,7 @@ func addStore(parent *cobra.Command) {
addStoreLoad(),
addStoreSave(),
addStoreServe(),
addStoreList(),
addStoreInfo(),
addStoreCopy(),
// TODO: Remove this in favor of sync?
@@ -92,8 +92,9 @@ func addStoreLoad() *cobra.Command {
if err != nil {
return err
}
_ = s
return store.LoadCmd(ctx, o, s.DataDir, args...)
return store.LoadCmd(ctx, o, "", args...)
},
}
o.AddFlags(cmd)
@@ -137,8 +138,9 @@ func addStoreSave() *cobra.Command {
if err != nil {
return err
}
_ = s
return store.SaveCmd(ctx, o, o.FileName, s.DataDir)
return store.SaveCmd(ctx, o, o.FileName, "")
},
}
o.AddArgs(cmd)
@@ -146,14 +148,14 @@ func addStoreSave() *cobra.Command {
return cmd
}
func addStoreList() *cobra.Command {
o := &store.ListOpts{}
func addStoreInfo() *cobra.Command {
o := &store.InfoOpts{}
cmd := &cobra.Command{
Use: "list",
Short: "List all content references in a store",
Use: "info",
Short: "Print out information about the store",
Args: cobra.ExactArgs(0),
Aliases: []string{"ls"},
Aliases: []string{"i"},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -162,7 +164,7 @@ func addStoreList() *cobra.Command {
return err
}
return store.ListCmd(ctx, o, s)
return store.InfoCmd(ctx, o, s)
},
}
o.AddFlags(cmd)
+66 -38
View File
@@ -2,10 +2,8 @@ package store
import (
"context"
"path/filepath"
"github.com/google/go-containerregistry/pkg/name"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1"
@@ -25,13 +23,68 @@ func (o *AddFileOpts) AddFlags(cmd *cobra.Command) {
f.StringVarP(&o.Name, "name", "n", "", "(Optional) Name to assign to file in store")
}
func AddFileCmd(ctx context.Context, o *AddFileOpts, s *store.Store, reference string) error {
s.Open()
defer s.Close()
// func addContent(ctx context.Context, o *AddFileOpts, s *store.Store, meta metav1.TypeMeta) error {
// l := log.FromContext(ctx)
//
// var (
// oci artifact.OCI
// loc string
// )
//
// switch cfg := meta.(type) {
// case cfg == v1alpha1.FilesContentKind:
// f := file.NewFile(cfg)
// oci = f
//
// loc = f.Name(reference)
//
// case "chart":
// oci, err := chart.NewChart(ch.Name, ch.RepoURL, ch.Version)
// if err != nil {
// return err
// }
//
// tag := ch.Version
// if tag == "" {
// tag = name.DefaultTag
// }
//
// ref, err := name.ParseReference(ch.Name, name.WithDefaultRegistry(""), name.WithDefaultTag(tag))
// if err != nil {
// return err
// }
//
//
// case "image":
// i, err := image.NewImage(reference)
// if err != nil {
// return err
// }
// oci = i
//
// loc = reference
//
// default:
// return nil
//
// }
// ref, err := name.ParseReference(loc, name.WithDefaultRegistry(""))
// if err != nil {
// return err
// }
//
// desc, err := s.AddArtifact(ctx, oci, ref)
// if err != nil {
// return err
// }
//
// l.Infof("added [%s] of type [%s] to store", ref.Name(), s.Identify(ctx, desc))
// return nil
// }
func AddFileCmd(ctx context.Context, o *AddFileOpts, s *store.Store, reference string) error {
cfg := v1alpha1.File{
Ref: reference,
Name: o.Name,
Ref: reference,
}
return storeFile(ctx, s, cfg)
@@ -40,28 +93,18 @@ func AddFileCmd(ctx context.Context, o *AddFileOpts, s *store.Store, reference s
func storeFile(ctx context.Context, s *store.Store, fi v1alpha1.File) error {
l := log.FromContext(ctx)
if fi.Name == "" {
base := filepath.Base(fi.Ref)
fi.Name = filepath.Base(fi.Ref)
l.Warnf("no name specified for file reference [%s], using base filepath: [%s]", fi.Ref, base)
}
oci, err := file.NewFile(fi.Ref, fi.Name)
f := file.NewFile(fi.Ref)
ref, err := name.ParseReference(f.Name(fi.Ref), name.WithDefaultRegistry(""))
if err != nil {
return err
}
ref, err := name.ParseReference(fi.Name, name.WithDefaultRegistry(""))
desc, err := s.AddArtifact(ctx, f, ref)
if err != nil {
return err
}
desc, err := s.AddArtifact(ctx, oci, ref)
if err != nil {
return err
}
l.Infof("file [%s] added at: [%s]", ref.Name(), desc.Annotations[ocispec.AnnotationTitle])
l.With(log.Fields{"type": s.Identify(ctx, desc)}).Infof("added [%s] to store", ref.Name())
return nil
}
@@ -75,9 +118,6 @@ func (o *AddImageOpts) AddFlags(cmd *cobra.Command) {
}
func AddImageCmd(ctx context.Context, o *AddImageOpts, s *store.Store, reference string) error {
s.Open()
defer s.Close()
cfg := v1alpha1.Image{
Ref: reference,
}
@@ -103,7 +143,7 @@ func storeImage(ctx context.Context, s *store.Store, i v1alpha1.Image) error {
return err
}
l.Infof("image [%s] added at: [%s]", ref.Name(), desc.Annotations[ocispec.AnnotationTitle])
l.With(log.Fields{"type": s.Identify(ctx, desc)}).Infof("added [%s] to store", ref.Name())
return nil
}
@@ -112,15 +152,6 @@ type AddChartOpts struct {
RepoURL string
// TODO: Support helm auth
Username string
Password string
PassCredentialsAll bool
CertFile string
KeyFile string
CaFile string
InsecureSkipTLSverify bool
RepositoryConfig string
RepositoryCache string
}
func (o *AddChartOpts) AddFlags(cmd *cobra.Command) {
@@ -131,9 +162,6 @@ func (o *AddChartOpts) AddFlags(cmd *cobra.Command) {
}
func AddChartCmd(ctx context.Context, o *AddChartOpts, s *store.Store, chartName string) error {
s.Open()
defer s.Close()
cfg := v1alpha1.Chart{
Name: chartName,
RepoURL: o.RepoURL,
@@ -166,6 +194,6 @@ func storeChart(ctx context.Context, s *store.Store, ch v1alpha1.Chart) error {
return err
}
l.Infof("chart [%s] added at: [%s]", ref.Name(), desc.Annotations[ocispec.AnnotationTitle])
l.With(log.Fields{"type": s.Identify(ctx, desc)}).Infof("added [%s] to store", ref.Name())
return nil
}
+42 -36
View File
@@ -2,56 +2,62 @@ package store
import (
"context"
"fmt"
"strings"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"oras.land/oras-go/pkg/content"
"github.com/rancherfederal/hauler/pkg/log"
"github.com/rancherfederal/hauler/pkg/store"
)
type CopyOpts struct{}
type CopyOpts struct {
Target string
}
func (o *CopyOpts) AddFlags(cmd *cobra.Command) {
f := cmd.Flags()
_ = f
// TODO: Regex matching
_ = cmd.Flags()
}
func CopyCmd(ctx context.Context, o *CopyOpts, s *store.Store, registry string) error {
func CopyCmd(ctx context.Context, o *CopyOpts, s *store.Store, targetRef string) error {
l := log.FromContext(ctx)
_ = l
s.Open()
defer s.Close()
components := strings.SplitN(targetRef, "://", 2)
switch components[0] {
case "dir":
fs := content.NewFile(components[1])
defer fs.Close()
if err := s.Copy(ctx, fs, nil); err != nil {
return err
}
case "registry":
r, err := content.NewRegistry(content.RegistryOptions{})
if err != nil {
return err
}
fmt.Println(components[1])
mapperFn := func(reference string) (string, error) {
ref, err := store.RelocateReference(reference, components[1])
if err != nil {
return "", err
}
return ref.Name(), nil
}
if err := s.Copy(ctx, r, mapperFn); err != nil {
return err
}
default:
return errors.Errorf("determining target protocol from: [%s]", targetRef)
refs, err := s.List(ctx)
if err != nil {
return err
}
for _, r := range refs {
ref, err := name.ParseReference(r, name.WithDefaultRegistry(s.Registry()))
if err != nil {
return err
}
o, err := remote.Image(ref)
if err != nil {
return err
}
rref, err := name.ParseReference(r, name.WithDefaultRegistry(registry))
if err != nil {
return err
}
l.Infof("copying [%s] -> [%s]", ref.Name(), rref.Name())
if err := remote.Write(rref, o); err != nil {
return err
}
}
return nil
}
+1 -5
View File
@@ -6,7 +6,6 @@ import (
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/cmd/hauler/cli/download"
"github.com/rancherfederal/hauler/pkg/layout"
"github.com/rancherfederal/hauler/pkg/store"
)
@@ -21,10 +20,7 @@ func (o *ExtractOpts) AddArgs(cmd *cobra.Command) {
}
func ExtractCmd(ctx context.Context, o *ExtractOpts, s *store.Store, reference string) error {
s.Open()
defer s.Close()
eref, err := layout.RelocateReference(reference, s.Registry())
eref, err := store.RelocateReference(reference, "")
if err != nil {
return err
}
+43
View File
@@ -0,0 +1,43 @@
package store
import (
"context"
"fmt"
"os"
"text/tabwriter"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/pkg/store"
)
type InfoOpts struct{}
func (o *InfoOpts) AddFlags(cmd *cobra.Command) {
f := cmd.Flags()
_ = f
// TODO: Regex matching
}
func InfoCmd(ctx context.Context, o *InfoOpts, s *store.Store) error {
refs, err := s.List(ctx)
if err != nil {
return err
}
tw := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
defer tw.Flush()
fmt.Fprintf(tw, "Reference\tTag/Digest\tType\n")
fmt.Fprintf(tw, "---------\t----------\t----\n")
for _, r := range refs {
if _, ok := r.Annotations[ocispec.AnnotationRefName]; !ok {
continue
}
fmt.Fprintf(tw, "%s\t%s\n", r.Annotations[ocispec.AnnotationRefName], "")
}
return nil
}
-47
View File
@@ -1,47 +0,0 @@
package store
import (
"context"
"fmt"
"os"
"text/tabwriter"
"github.com/google/go-containerregistry/pkg/name"
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/pkg/store"
)
type ListOpts struct{}
func (o *ListOpts) AddFlags(cmd *cobra.Command) {
f := cmd.Flags()
_ = f
// TODO: Regex matching
}
func ListCmd(ctx context.Context, o *ListOpts, s *store.Store) error {
s.Open()
defer s.Close()
refs, err := s.List(ctx)
if err != nil {
return err
}
tw := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
defer tw.Flush()
fmt.Fprintf(tw, "Reference\tTag/Digest\n")
for _, r := range refs {
ref, err := name.ParseReference(r, name.WithDefaultRegistry(""))
if err != nil {
return err
}
fmt.Fprintf(tw, "%s\t%s\n", ref.Context().String(), ref.Identifier())
}
return nil
}
+28 -8
View File
@@ -7,14 +7,20 @@ import (
"os"
"github.com/distribution/distribution/v3/configuration"
"github.com/distribution/distribution/v3/registry"
dcontext "github.com/distribution/distribution/v3/context"
_ "github.com/distribution/distribution/v3/registry/storage/driver/base"
_ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
"github.com/distribution/distribution/v3/version"
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/pkg/server"
"github.com/rancherfederal/hauler/pkg/store"
)
type ServeOpts struct {
Port int
RootDir string
ConfigFile string
Daemon bool
@@ -25,13 +31,28 @@ func (o *ServeOpts) AddFlags(cmd *cobra.Command) {
f := cmd.Flags()
f.IntVarP(&o.Port, "port", "p", 5000, "Port to listen on")
f.StringVar(&o.RootDir, "directory", "registry", "Directory to use for registry backend (defaults to '$PWD/registry')")
f.StringVarP(&o.ConfigFile, "config", "c", "", "Path to a config file, will override all other configs")
f.BoolVarP(&o.Daemon, "daemon", "d", false, "Toggle serving as a daemon")
}
// ServeCmd does
// ServeCmd serves the embedded registry almost identically to how distribution/v3 does it
func ServeCmd(ctx context.Context, o *ServeOpts, s *store.Store) error {
cfg := o.defaultConfig(s)
ctx = dcontext.WithVersion(ctx, version.Version)
tr := server.NewTempRegistry(ctx, o.RootDir)
if err := tr.Start(); err != nil {
return err
}
opts := &CopyOpts{}
if err := CopyCmd(ctx, opts, s, "registry://"+tr.Registry()); err != nil {
return err
}
tr.Close()
cfg := o.defaultConfig()
if o.ConfigFile != "" {
ucfg, err := loadConfig(o.ConfigFile)
if err != nil {
@@ -40,7 +61,7 @@ func ServeCmd(ctx context.Context, o *ServeOpts, s *store.Store) error {
cfg = ucfg
}
r, err := registry.NewRegistry(ctx, cfg)
r, err := server.NewRegistry(ctx, cfg)
if err != nil {
return err
}
@@ -48,7 +69,6 @@ func ServeCmd(ctx context.Context, o *ServeOpts, s *store.Store) error {
if err = r.ListenAndServe(); err != nil {
return err
}
return nil
}
@@ -61,15 +81,15 @@ func loadConfig(filename string) (*configuration.Configuration, error) {
return configuration.Parse(f)
}
func (o *ServeOpts) defaultConfig(s *store.Store) *configuration.Configuration {
func (o *ServeOpts) defaultConfig() *configuration.Configuration {
cfg := &configuration.Configuration{
Version: "0.1",
Storage: configuration.Storage{
"cache": configuration.Parameters{"blobdescriptor": "inmemory"},
"filesystem": configuration.Parameters{"rootdirectory": s.DataDir},
"filesystem": configuration.Parameters{"rootdirectory": o.RootDir},
// TODO: Ensure this is toggleable via cli arg if necessary
"maintenance": configuration.Parameters{"readonly.enabled": true},
// "maintenance": configuration.Parameters{"readonly.enabled": false},
},
}
cfg.Log.Level = "info"
+3 -6
View File
@@ -32,14 +32,11 @@ func SyncCmd(ctx context.Context, o *SyncOpts, s *store.Store) error {
l := log.FromContext(ctx)
// Start from an empty store (contents are cached elsewhere)
l.Debugf("flushing any existing content in store: %s", s.DataDir)
l.Debugf("flushing content store")
if err := s.Flush(ctx); err != nil {
return err
}
s.Open()
defer s.Close()
for _, filename := range o.ContentFiles {
l.Debugf("processing content file: '%s'", filename)
fi, err := os.Open(filename)
@@ -68,7 +65,7 @@ func SyncCmd(ctx context.Context, o *SyncOpts, s *store.Store) error {
return err
}
l.Infof("syncing [%s] to [%s]", obj.GroupVersionKind().String(), s.DataDir)
l.Infof("syncing [%s] to store", obj.GroupVersionKind().String())
// TODO: Should type switch instead...
switch obj.GroupVersionKind().Kind {
@@ -133,7 +130,7 @@ func SyncCmd(ctx context.Context, o *SyncOpts, s *store.Store) error {
}
for _, cfg := range cfg.Spec.Charts {
tc, err := tchart.NewChart(cfg.Name, cfg.RepoURL, cfg.Version)
tc, err := tchart.NewChart(cfg)
if err != nil {
return err
}
+2 -3
View File
@@ -5,7 +5,7 @@ import (
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/pkg/version"
"github.com/rancherfederal/hauler/internal/version"
)
func addVersion(parent *cobra.Command) {
@@ -13,8 +13,7 @@ func addVersion(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "version",
Short: "Print current hauler version",
Long: "Print current hauler version",
Short: "Print the current version",
Aliases: []string{"v"},
RunE: func(cmd *cobra.Command, args []string) error {
v := version.GetVersionInfo()
+37 -37
View File
@@ -3,32 +3,35 @@ module github.com/rancherfederal/hauler
go 1.17
require (
github.com/containerd/containerd v1.5.7
github.com/distribution/distribution/v3 v3.0.0-20210926092439-1563384b69df
github.com/google/go-containerregistry v0.6.0
github.com/containerd/containerd v1.5.8
github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684
github.com/docker/go-metrics v0.0.1
github.com/google/go-containerregistry v0.7.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/mholt/archiver/v3 v3.5.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/image-spec v1.0.2
github.com/pkg/errors v0.9.1
github.com/rancher/wrangler v0.8.4
github.com/rs/zerolog v1.26.0
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.2.1
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
helm.sh/helm/v3 v3.7.1
k8s.io/apimachinery v0.22.2
k8s.io/client-go v0.22.2
oras.land/oras-go v0.4.0
sigs.k8s.io/controller-runtime v0.10.3
helm.sh/helm/v3 v3.6.1-0.20211119214113-6a1daecd0c29
k8s.io/apimachinery v0.22.3
k8s.io/client-go v0.22.3
oras.land/oras-go v1.0.0
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/BurntSushi/toml v0.4.1 // indirect
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
github.com/Masterminds/squirrel v1.5.0 // indirect
github.com/Masterminds/squirrel v1.5.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
@@ -40,16 +43,14 @@ require (
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.7.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.2 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.9+incompatible // indirect
github.com/docker/cli v20.10.11+incompatible // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.9+incompatible // indirect
github.com/docker/docker v20.10.11+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect
github.com/dsnet/compress v0.0.1 // indirect
@@ -59,7 +60,7 @@ require (
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-logr/logr v0.4.0 // indirect
github.com/go-logr/logr v1.2.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
@@ -75,21 +76,19 @@ require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmoiron/sqlx v1.3.1 // indirect
github.com/jmoiron/sqlx v1.3.4 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/klauspost/pgzip v1.2.4 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.10.0 // indirect
github.com/lib/pq v1.10.4 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
@@ -107,9 +106,10 @@ require (
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/gomega v1.15.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pierrec/lz4/v4 v4.0.3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
@@ -124,7 +124,7 @@ require (
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/ulikunitz/xz v0.5.7 // indirect
github.com/ulikunitz/xz v0.5.8 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
@@ -134,32 +134,32 @@ require (
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492 // indirect
google.golang.org/grpc v1.39.0 // indirect
google.golang.org/genproto v0.0.0-20211111162719-482062a4217b // indirect
google.golang.org/grpc v1.42.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/gorp.v1 v1.7.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/api v0.22.2 // indirect
k8s.io/apiextensions-apiserver v0.22.2 // indirect
k8s.io/apiserver v0.22.2 // indirect
k8s.io/cli-runtime v0.22.1 // indirect
k8s.io/component-base v0.22.2 // indirect
k8s.io/klog/v2 v2.9.0 // indirect
k8s.io/api v0.22.3 // indirect
k8s.io/apiextensions-apiserver v0.22.3 // indirect
k8s.io/apiserver v0.22.3 // indirect
k8s.io/cli-runtime v0.22.3 // indirect
k8s.io/component-base v0.22.3 // indirect
k8s.io/klog/v2 v2.30.0 // indirect
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect
k8s.io/kubectl v0.22.1 // indirect
k8s.io/kubectl v0.22.3 // indirect
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect
sigs.k8s.io/kustomize/api v0.8.11 // indirect
sigs.k8s.io/kustomize/kyaml v0.11.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
+127 -90
View File
@@ -20,6 +20,12 @@ cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECH
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -69,8 +75,9 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
@@ -87,8 +94,8 @@ github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZC
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8=
github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Masterminds/squirrel v1.5.1 h1:kWAKlLLJFxZG7N2E0mBMNWVp5AuUX+JUrnhFN74Eg+w=
github.com/Masterminds/squirrel v1.5.1/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
@@ -98,8 +105,8 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX
github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
@@ -107,8 +114,9 @@ github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg3
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=
github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600=
github.com/Microsoft/hcsshim v0.8.21 h1:btRfUDThBE5IKcvI8O8jOiIkujUsAMBSRsYDYmEi6oM=
github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=
github.com/Microsoft/hcsshim v0.8.23 h1:47MSwtKGXet80aIn+7h4YI6fwPmwIghAnsx2aOUrG2M=
github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg=
github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
@@ -147,7 +155,6 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -173,6 +180,7 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
@@ -195,7 +203,11 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
@@ -228,14 +240,15 @@ github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX
github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ=
github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=
github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI=
github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM=
github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=
github.com/containerd/containerd v1.5.8 h1:NmkCC1/QxyZFBny8JogwLpOy2f+VEbO/f6bV2Mqtwuw=
github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
@@ -263,13 +276,14 @@ github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJ
github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c=
github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
github.com/containerd/stargz-snapshotter/estargz v0.7.0 h1:1d/rydzTywc76lnjJb6qbPCiTiCwts49AzKps/Ecblw=
github.com/containerd/stargz-snapshotter/estargz v0.7.0/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw=
github.com/containerd/stargz-snapshotter/estargz v0.10.0 h1:glqzafvxBBAMo+x2w2sdDjUDZeTqqLJmqZPY05qehCU=
github.com/containerd/stargz-snapshotter/estargz v0.10.0/go.mod h1:aE5PCyhFMwR8sbrErO5eM2GcvkyXTTJremG883D4qF0=
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=
github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ=
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=
github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
@@ -310,12 +324,14 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
@@ -331,23 +347,20 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3/go.mod h1:gt38b7cvVKazi5XkHvINNytZXgTEntyhtyM3HQz46Nk=
github.com/distribution/distribution/v3 v3.0.0-20210926092439-1563384b69df h1:zafDqOsnugdrReF9Pe0wybnfFtEIaegSyHNIvnwKPVk=
github.com/distribution/distribution/v3 v3.0.0-20210926092439-1563384b69df/go.mod h1:ZDZib/BOniVWcXcsy0voU8gR00znhe5VJm47d3H2Y5g=
github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY=
github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v20.10.9+incompatible h1:OJ7YkwQA+k2Oi51lmCojpjiygKpi76P7bg91b2eJxYU=
github.com/docker/cli v20.10.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v20.10.10+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc=
github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.9+incompatible h1:JlsVnETOjM2RLQa0Cc1XCIspUdXW3Zenq9P54uXBm6k=
github.com/docker/docker v20.10.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/docker v20.10.10+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.11+incompatible h1:OqzI/g/W54LczvhnccGqniFoQghHx3pklbLuhfXpqGo=
github.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o=
github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
@@ -383,9 +396,9 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -407,7 +420,6 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -429,10 +441,10 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
@@ -534,6 +546,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -579,8 +592,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-containerregistry v0.6.0 h1:niQ+8XD//kKgArIFwDVBXsWVWbde16LPdHMyNwSC8h4=
github.com/google/go-containerregistry v0.6.0/go.mod h1:euCCtNbZ6tKqi1E72vwDj2xZcN5ttKpZLfa/wSo5iLw=
github.com/google/go-containerregistry v0.7.0 h1:u0onUUOcyoCDHEiJoyR1R1gx5er1+r06V5DBhUU5ndk=
github.com/google/go-containerregistry v0.7.0/go.mod h1:2zaoelrL0d08gGbpdP3LqyUuBmhWbpD6IOe2s9nLS2k=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
@@ -601,6 +614,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
@@ -612,6 +627,7 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
@@ -625,7 +641,6 @@ github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
@@ -688,8 +703,8 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE=
github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -717,8 +732,6 @@ github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
@@ -744,8 +757,9 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
@@ -888,8 +902,10 @@ github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.2-0.20210730191737-8e42a01fb1b7/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
@@ -1073,12 +1089,16 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4=
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
@@ -1169,7 +1189,6 @@ go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslx
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -1187,7 +1206,6 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -1196,10 +1214,9 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1296,10 +1313,10 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8=
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1312,8 +1329,12 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1423,10 +1444,13 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 h1:WecRHqgE09JBkh/584XIE6PMz5KKE/vER4izNUi30AQ=
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -1518,6 +1542,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1526,7 +1552,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
@@ -1554,6 +1579,12 @@ google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBz
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1610,8 +1641,21 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492 h1:7yQQsvnwjfEahbNNEKcBHv3mR+HnB1ctGY/z1JXzx8M=
google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211111162719-482062a4217b h1:qvEQEwKjZRAg6rjY/jqfJ7T8/w/D7jTIFJGcaSka96k=
google.golang.org/genproto v0.0.0-20211111162719-482062a4217b/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@@ -1638,8 +1682,11 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1704,8 +1751,8 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
helm.sh/helm/v3 v3.7.1 h1:kED/HWx09QHHSJhYaJY6ttj/BhmzBmT1oupKslncibY=
helm.sh/helm/v3 v3.7.1/go.mod h1:3eOeBD3Z+O/ELiuu19zynZSN8jP1ErXLuyP21SZeMq8=
helm.sh/helm/v3 v3.6.1-0.20211119214113-6a1daecd0c29 h1:XV2RveKv7jaAhAO4nfDdwBvpZnuT47N69O9MNgYAFVw=
helm.sh/helm/v3 v3.6.1-0.20211119214113-6a1daecd0c29/go.mod h1:tplOkx4vact5IEykn7M9HmaBktm2RzwInMEQxlA4P/E=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1721,15 +1768,13 @@ k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY=
k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=
k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=
k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY=
k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw=
k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8=
k8s.io/api v0.22.3 h1:wOoES2GoSkUsdped2RB4zYypPqWtvprGoKCENTOOjP4=
k8s.io/api v0.22.3/go.mod h1:azgiXFiXqiWyLCfI62/eYBOu19rj2LKmIhFPP4+33fs=
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=
k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo=
k8s.io/apiextensions-apiserver v0.22.1/go.mod h1:HeGmorjtRmRLE+Q8dJu6AYRoZccvCMsghwS8XTUYb2c=
k8s.io/apiextensions-apiserver v0.22.2 h1:zK7qI8Ery7j2CaN23UCFaC1hj7dMiI87n01+nKuewd4=
k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA=
k8s.io/apiextensions-apiserver v0.22.3 h1:bKku7MqawIbtTZc084BZoMV4fz0WZuvCnB5E+yrQXGM=
k8s.io/apiextensions-apiserver v0.22.3/go.mod h1:f4plF+CXeqI89jAXL0Ml4LI/kSAZ54JS94+XOX1sae8=
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
k8s.io/apimachinery v0.0.0-20191214185652-442f8fb2f03a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY=
k8s.io/apimachinery v0.0.0-20191216025728-0ee8b4573e3a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY=
@@ -1739,22 +1784,20 @@ k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMi
k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=
k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk=
k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/apimachinery v0.22.3 h1:mrvBG5CZnEfwgpVqWcrRKvdsYECTrhAR6cApAgdsflk=
k8s.io/apimachinery v0.22.3/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw=
k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=
k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=
k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400=
k8s.io/apiserver v0.22.2 h1:TdIfZJc6YNhu2WxeAOWq1TvukHF0Sfx0+ln4XK9qnL4=
k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI=
k8s.io/apiserver v0.22.3 h1:x21xyLQ2qvPr5vjOTVOBaSJu8svnU2wfLOfSjNJEOdw=
k8s.io/apiserver v0.22.3/go.mod h1:oam7lH/F1Kto/WTamyQYrD68fS0mGUBORAFf6x/9Mxs=
k8s.io/cli-runtime v0.0.0-20191214191754-e6dc6d5c8724/go.mod h1:wzlq80lvjgHW9if6MlE4OIGC86MDKsy5jtl9nxz/IYY=
k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI=
k8s.io/cli-runtime v0.22.1 h1:WIueieKvT+IiSVSFosRLI6rkM0tyBGEGH1WUEztVjho=
k8s.io/cli-runtime v0.22.1/go.mod h1:YqwGrlXeEk15Yn3em2xzr435UGwbrCw5x+COQoTYfoo=
k8s.io/cli-runtime v0.22.3 h1:AeOgaDpb/k36amWsjyyIU+FLpLzzdmoLD5gn38c5fio=
k8s.io/cli-runtime v0.22.3/go.mod h1:um6JvCxV9Hrhq0zCUxcqYoY7/wF64g6IYgOViI8sg6Q=
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk=
k8s.io/client-go v0.0.0-20191214190045-a32a6f7a3052/go.mod h1:tAaoc/sYuIL0+njJefSAmE28CIcxyaFV4kbIujBlY2s=
k8s.io/client-go v0.0.0-20191219150334-0b8da7416048/go.mod h1:ZEe8ZASDUAuqVGJ+UN0ka0PfaR+b6a6E1PGsSNZRui8=
@@ -1764,15 +1807,13 @@ k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU=
k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k=
k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0=
k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk=
k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc=
k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U=
k8s.io/client-go v0.22.3 h1:6onkOSc+YNdwq5zXE0wFXicq64rrym+mXwHu/CPVGO4=
k8s.io/client-go v0.22.3/go.mod h1:ElDjYf8gvZsKDYexmsmnMQ0DYO8W9RwBjfQ1PI53yow=
k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
k8s.io/code-generator v0.0.0-20191214185510-0b9b3c99f9f2/go.mod h1:BjGKcoq1MRUmcssvHiSxodCco1T6nVIt4YeCT5CMSao=
k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
k8s.io/code-generator v0.22.3/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
k8s.io/component-base v0.0.0-20191214190519-d868452632e2/go.mod h1:wupxkh1T/oUDqyTtcIjiEfpbmIHGm8By/vqpSKC6z8c=
k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs=
@@ -1780,10 +1821,9 @@ k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1
k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI=
k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM=
k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo=
k8s.io/component-base v0.22.2 h1:vNIvE0AIrLhjX8drH0BgCNJcR4QZxMXcJzBsDplDx9M=
k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug=
k8s.io/component-helpers v0.22.1/go.mod h1:QvBcDbX+qU5I2tMZABBF5fRwAlQwiv771IGBHK9WYh4=
k8s.io/component-base v0.22.3 h1:/+hryAW03u3FpJQww+GSMsArJNUbGjH66lrgxaRynLU=
k8s.io/component-base v0.22.3/go.mod h1:kuybv1miLCMoOk3ebrqF93GbQHQx6W2287FC0YEQY6s=
k8s.io/component-helpers v0.22.3/go.mod h1:7OVySVH5elhHKuJKUOxZEfpT1Bm3ChmBQZHmuFfbGHk=
k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM=
k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
@@ -1801,8 +1841,9 @@ k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM=
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw=
k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-aggregator v0.18.0/go.mod h1:ateewQ5QbjMZF/dihEFXwaEwoA4v/mayRvzfmvb6eqI=
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
@@ -1812,16 +1853,15 @@ k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAG
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd/go.mod h1:9ehGcuUGjXVZh0qbYSB0vvofQw2JQe6c6cO0k4wu/Oo=
k8s.io/kubectl v0.22.1 h1:kpXO+ajPNTzAVLDM9pAzCsWH9MtCMr92zpcvXMt7P6E=
k8s.io/kubectl v0.22.1/go.mod h1:mjAOgEbMNMtZWxnfM6jd+nPjPsaoLqO5xanc78WcSbw=
k8s.io/kubectl v0.22.3 h1:xziSHHyFHg2nt9vE6A0XqW5dOePNSlzxG8z3z+IY63E=
k8s.io/kubectl v0.22.3/go.mod h1:gcpQHPOx+Jke9Og6Li7YxR/ZuaOtFUeJw7xHH617tHs=
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/metrics v0.0.0-20191214191643-6b1944c9f765/go.mod h1:5V7rewilItwK0cz4nomU0b3XCcees2Ka5EBYWS1HBeM=
k8s.io/metrics v0.22.1/go.mod h1:i/ZNap89UkV1gLa26dn7fhKAdheJaKy+moOqJbiif7E=
k8s.io/metrics v0.22.3/go.mod h1:HbLFLRKtXzoC/6tHLQAlO9AeOBXZp2eB6SsgkbujoNI=
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g=
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
@@ -1829,11 +1869,9 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
oras.land/oras-go v0.4.0 h1:u6+7D+raZDYHwlz/uOwNANiRmyYDSSMW7A9E1xXycUQ=
oras.land/oras-go v0.4.0/go.mod h1:VJcU+VE4rkclUbum5C0O7deEZbBYnsnpbGSACwTjOcg=
oras.land/oras-go v1.0.0 h1:R5+g6OYqsOPGcdwHZkMpT0tpKvTiIB8zAer0Nv+WF3c=
oras.land/oras-go v1.0.0/go.mod h1:MZN6VbUUZjfWRF1EJKPbAWhJtE+R8JVicRmeYZp8qDg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM=
rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
@@ -1842,8 +1880,6 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyz
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/cli-utils v0.16.0/go.mod h1:9Jqm9K2W6ShhCxsEuaz6HSRKKOXigPUx3ZfypGgxBLY=
sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
sigs.k8s.io/controller-runtime v0.10.3 h1:s5Ttmw/B4AuIbwrXD3sfBkXwnPMMWrqpVj4WRt1dano=
sigs.k8s.io/controller-runtime v0.10.3/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/kustomize/api v0.8.11 h1:LzQzlq6Z023b+mBtc6v72N2mSHYmN8x7ssgbf/hv0H8=
@@ -1865,6 +1901,7 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
View File
View File
+3 -3
View File
@@ -7,7 +7,7 @@ import (
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/rancherfederal/hauler/pkg/artifact/local"
"github.com/rancherfederal/hauler/internal/layer"
)
type fs struct {
@@ -37,14 +37,14 @@ func (f *fs) Put(l v1.Layer) (v1.Layer, error) {
func (f *fs) Get(h v1.Hash) (v1.Layer, error) {
opener := f.open(h)
l, err := local.LayerFromOpener(opener)
l, err := layer.FromOpener(opener)
if os.IsNotExist(err) {
return nil, ErrLayerNotFound
}
return l, err
}
func (f *fs) open(h v1.Hash) local.Opener {
func (f *fs) open(h v1.Hash) layer.Opener {
return func() (io.ReadCloser, error) {
return os.Open(layerpath(f.root, h))
}
+148
View File
@@ -0,0 +1,148 @@
package getter
import (
"archive/tar"
"compress/gzip"
"context"
"io"
"net/url"
"os"
"path/filepath"
"time"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
type directory struct {
file
}
func (d directory) Open(ctx context.Context, u *url.URL) (io.ReadCloser, error) {
tmpfile, err := os.CreateTemp("", "hauler")
if err != nil {
return nil, err
}
digester := digest.Canonical.Digester()
zw := gzip.NewWriter(io.MultiWriter(tmpfile, digester.Hash()))
defer zw.Close()
tarDigester := digest.Canonical.Digester()
if err := tarDir(d.path(u), d.Name(u), io.MultiWriter(zw, tarDigester.Hash()), true); err != nil {
return nil, err
}
if err := zw.Close(); err != nil {
return nil, err
}
if err := tmpfile.Sync(); err != nil {
return nil, err
}
fi, err := os.Open(tmpfile.Name())
if err != nil {
return nil, err
}
// rc := &closer{
// t: io.TeeReader(tmpfile, fi),
// closes: []func() error{fi.Close, tmpfile.Close, zw.Close},
// }
return fi, nil
}
func (d directory) Detect(u *url.URL) bool {
if len(d.path(u)) == 0 {
return false
}
if fi, err := os.Stat(d.path(u)); err != nil {
return false
} else if !fi.IsDir() {
return false
}
return true
}
func tarDir(root string, prefix string, w io.Writer, stripTimes bool) error {
tw := tar.NewWriter(w)
defer tw.Close()
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Rename path
name, err := filepath.Rel(root, path)
if err != nil {
return err
}
name = filepath.Join(prefix, name)
name = filepath.ToSlash(name)
// Generate header
var link string
mode := info.Mode()
if mode&os.ModeSymlink != 0 {
if link, err = os.Readlink(path); err != nil {
return err
}
}
header, err := tar.FileInfoHeader(info, link)
if err != nil {
return errors.Wrap(err, path)
}
header.Name = name
header.Uid = 0
header.Gid = 0
header.Uname = ""
header.Gname = ""
if stripTimes {
header.ModTime = time.Time{}
header.AccessTime = time.Time{}
header.ChangeTime = time.Time{}
}
// Write file
if err := tw.WriteHeader(header); err != nil {
return errors.Wrap(err, "tar")
}
if mode.IsRegular() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
if _, err := io.Copy(tw, file); err != nil {
return errors.Wrap(err, path)
}
}
return nil
}); err != nil {
return err
}
return nil
}
type closer struct {
t io.Reader
closes []func() error
}
func (c *closer) Read(p []byte) (n int, err error) {
return c.t.Read(p)
}
func (c *closer) Close() error {
var err error
for _, c := range c.closes {
lastErr := c()
if err == nil {
err = lastErr
}
}
return err
}
+45
View File
@@ -0,0 +1,45 @@
package getter
import (
"context"
"io"
"net/url"
"os"
"path/filepath"
"github.com/rancherfederal/hauler/pkg/artifact"
)
type file struct{}
func (f file) Name(u *url.URL) string {
return filepath.Base(f.path(u))
}
func (f file) Open(ctx context.Context, u *url.URL) (io.ReadCloser, error) {
return os.Open(f.path(u))
}
func (f file) Detect(u *url.URL) bool {
if len(f.path(u)) == 0 {
return false
}
if fi, err := os.Stat(f.path(u)); err != nil {
return false
} else if fi.IsDir() {
return false
}
return true
}
func (f file) path(u *url.URL) string {
return filepath.Join(u.Host, u.Path)
}
func (f file) Config(u *url.URL) artifact.Config {
c := &config{
Reference: u.String(),
}
return artifact.ToConfig(c)
}
+121
View File
@@ -0,0 +1,121 @@
package getter
import (
"context"
"io"
"net/url"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/types"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"oras.land/oras-go/pkg/content"
"github.com/rancherfederal/hauler/internal/layer"
"github.com/rancherfederal/hauler/pkg/artifact"
htypes "github.com/rancherfederal/hauler/pkg/consts"
)
type Client struct {
Getters map[string]Getter
Options ClientOptions
}
// TODO: Make some valid ClientOptions
type ClientOptions struct{}
var (
ErrGetterTypeUnknown = errors.New("no getter type found matching reference")
)
type Getter interface {
Open(context.Context, *url.URL) (io.ReadCloser, error)
Detect(*url.URL) bool
Name(*url.URL) string
Config(*url.URL) artifact.Config
}
func NewClient(opts ClientOptions) *Client {
defaults := map[string]Getter{
"file": new(file),
"directory": new(directory),
"http": new(https),
"https": new(https),
}
c := &Client{
Getters: defaults,
Options: opts,
}
return c
}
func (c *Client) LayerFrom(ctx context.Context, source string) (v1.Layer, error) {
u, err := url.Parse(source)
if err != nil {
return nil, err
}
for _, g := range c.Getters {
if g.Detect(u) {
opener := func() (io.ReadCloser, error) {
return g.Open(ctx, u)
}
cfg := g.Config(u)
mt, err := cfg.MediaType()
if err != nil {
return nil, err
}
annotations := make(map[string]string)
annotations[ocispec.AnnotationTitle] = g.Name(u)
switch g.(type) {
case *directory:
annotations[content.AnnotationUnpack] = "true"
}
l, err := layer.FromOpener(opener,
layer.WithMediaType(string(mt)),
layer.WithAnnotations(annotations))
if err != nil {
return nil, err
}
return l, nil
}
}
return nil, errors.Wrapf(ErrGetterTypeUnknown, "%s", source)
}
func (c *Client) Name(source string) string {
u, err := url.Parse(source)
if err != nil {
return source
}
for _, g := range c.Getters {
return g.Name(u)
}
return source
}
func (c *Client) Config(source string) artifact.Config {
u, err := url.Parse(source)
if err != nil {
return nil
}
for _, g := range c.Getters {
return g.Config(u)
}
return nil
}
type config struct {
Reference string `json:"reference"`
Annotations map[string]string `json:"annotations,omitempty"`
}
func (c *config) MediaType() (types.MediaType, error) {
return htypes.FileConfigMediaType, nil
}
+58
View File
@@ -0,0 +1,58 @@
package getter
import (
"context"
"io"
"mime"
"net/http"
"net/url"
"path/filepath"
"strings"
"github.com/rancherfederal/hauler/pkg/artifact"
)
type https struct{}
func (h https) Name(u *url.URL) string {
resp, err := http.Head(u.String())
if err != nil {
return ""
}
contentType := resp.Header.Get("Content-Type")
for _, v := range strings.Split(contentType, ",") {
t, _, err := mime.ParseMediaType(v)
if err != nil {
break
}
// TODO: Identify known mimetypes for hints at a filename
_ = t
}
// TODO: Not this
return filepath.Base(u.String())
}
func (h https) Open(ctx context.Context, u *url.URL) (io.ReadCloser, error) {
resp, err := http.Get(u.String())
if err != nil {
return nil, err
}
return resp.Body, nil
}
func (h https) Detect(u *url.URL) bool {
switch u.Scheme {
case "http", "https":
return true
}
return false
}
func (h https) Config(u *url.URL) artifact.Config {
c := &config{
Reference: u.String(),
}
return artifact.ToConfig(c)
}
@@ -1,4 +1,4 @@
package local
package layer
import (
"io"
@@ -6,16 +6,16 @@ import (
v1 "github.com/google/go-containerregistry/pkg/v1"
gtypes "github.com/google/go-containerregistry/pkg/v1/types"
"github.com/rancherfederal/hauler/pkg/artifact/types"
"github.com/rancherfederal/hauler/pkg/consts"
)
type Opener func() (io.ReadCloser, error)
func LayerFromOpener(opener Opener, opts ...LayerOption) (v1.Layer, error) {
func FromOpener(opener Opener, opts ...Option) (v1.Layer, error) {
var err error
layer := &layer{
mediaType: types.UnknownLayer,
mediaType: consts.UnknownLayer,
annotations: make(map[string]string, 1),
}
@@ -25,7 +25,7 @@ func LayerFromOpener(opener Opener, opts ...LayerOption) (v1.Layer, error) {
if err != nil {
return nil, err
}
// TODO: actually compress this
return rc, nil
}
@@ -53,15 +53,15 @@ func compute(opener Opener) (v1.Hash, int64, error) {
return v1.SHA256(rc)
}
type LayerOption func(*layer)
type Option func(*layer)
func WithMediaType(mt string) LayerOption {
func WithMediaType(mt string) Option {
return func(l *layer) {
l.mediaType = mt
}
}
func WithAnnotations(annotations map[string]string) LayerOption {
func WithAnnotations(annotations map[string]string) Option {
return func(l *layer) {
if l.annotations == nil {
l.annotations = make(map[string]string)
+59
View File
@@ -0,0 +1,59 @@
package mapper
import (
"fmt"
"os"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/rancherfederal/hauler/pkg/consts"
)
type Fn func(desc v1.Descriptor) (*os.File, error)
type maps struct {
}
func NewMapper() *maps {
return &maps{}
}
type Mapper interface{}
type image struct{}
func (i *image) mapper() map[string]Fn {
m := make(map[string]Fn)
manifestMapperFn := Fn(func(desc v1.Descriptor) (*os.File, error) {
return os.Create("manifest.json")
})
for _, l := range []string{consts.DockerManifestSchema2, consts.OCIManifestSchema1} {
m[l] = manifestMapperFn
}
layerMapperFn := Fn(func(desc v1.Descriptor) (*os.File, error) {
n := fmt.Sprintf("%s.tar.gz", desc.Digest.String())
return os.Create(n)
})
for _, l := range []string{consts.OCILayer, consts.DockerLayer} {
m[l] = layerMapperFn
}
configMapperFn := Fn(func(desc v1.Descriptor) (*os.File, error) {
return os.Create("config.json")
})
for _, l := range []string{consts.DockerConfigJSON} {
m[l] = configMapperFn
}
return m
}
func (i *image) identify() bool {
return false
}
+187
View File
@@ -0,0 +1,187 @@
package mapper
import (
"context"
"io"
"io/ioutil"
"os"
"strings"
"time"
ccontent "github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/remotes"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"oras.land/oras-go/pkg/content"
)
func NewStore(root string, mapper map[string]Fn) *store {
fs := content.NewFile(root)
return &store{
File: fs,
mapper: mapper,
}
}
func (s *store) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) {
var tag, hash string
parts := strings.SplitN(ref, "@", 2)
if len(parts) > 0 {
tag = parts[0]
}
if len(parts) > 1 {
hash = parts[1]
}
return &pusher{
tag: tag,
ref: hash,
mapper: s.mapper,
}, nil
}
type store struct {
*content.File
mapper map[string]Fn
}
type pusher struct {
tag string
ref string
mapper map[string]Fn
}
func (s *pusher) Push(ctx context.Context, desc ocispec.Descriptor) (ccontent.Writer, error) {
now := time.Now()
if _, ok := s.mapper[desc.MediaType]; !ok {
return content.NewIoContentWriter(ioutil.Discard, content.WithOutputHash(desc.Digest)), nil
}
f, err := s.mapper[desc.MediaType](desc)
if err != nil {
return nil, err
}
return &fileWriter{
file: f,
digester: digest.Canonical.Digester(),
status: ccontent.Status{
Ref: f.Name(),
Total: desc.Size,
StartedAt: now,
UpdatedAt: now,
},
aftercommit: nil,
}, nil
}
type fileWriter struct {
// store *content.File
file *os.File
digester digest.Digester
status ccontent.Status
aftercommit func() error
}
// NewFileWriter will open a new file for writing, existing file will be truncated
func NewFileWriter(path string, perm os.FileMode, size int64) (*fileWriter, error) {
now := time.Now()
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, perm)
if err != nil {
return nil, err
}
return &fileWriter{
file: f,
digester: digest.Canonical.Digester(),
status: ccontent.Status{
Ref: f.Name(),
Total: size,
StartedAt: now,
UpdatedAt: now,
},
}, nil
}
func (w *fileWriter) Write(p []byte) (n int, err error) {
n, err = w.file.Write(p)
w.digester.Hash().Write(p[:n])
w.status.Offset += int64(len(p))
w.status.UpdatedAt = time.Now()
return n, err
}
func (w *fileWriter) Close() error {
if w.file == nil {
return nil
}
w.file.Sync()
err := w.file.Close()
w.file = nil
return err
}
func (w *fileWriter) Digest() digest.Digest {
return w.digester.Digest()
}
func (w *fileWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...ccontent.Opt) error {
var base ccontent.Info
for _, opt := range opts {
if err := opt(&base); err != nil {
return err
}
}
if w.file == nil {
return errors.Wrap(errdefs.ErrFailedPrecondition, "cannot commit on closed writer")
}
file := w.file
w.file = nil
if err := file.Sync(); err != nil {
file.Close()
return errors.Wrap(err, "sync failed")
}
fileInfo, err := file.Stat()
if err != nil {
file.Close()
return errors.Wrap(err, "stat failed")
}
if err := file.Close(); err != nil {
return errors.Wrap(err, "failed to close file")
}
if size > 0 && size != fileInfo.Size() {
return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit size %d, expected %d", fileInfo.Size(), size)
}
if dgst := w.digester.Digest(); expected != "" && expected != dgst {
return errors.Wrapf(errdefs.ErrFailedPrecondition, "unexpected commit digest %s, expected %s", dgst, expected)
}
// w.store.set(w.desc)
if w.aftercommit != nil {
return w.aftercommit()
}
return nil
}
func (w *fileWriter) Status() (ccontent.Status, error) {
return w.status, nil
}
func (w *fileWriter) Truncate(size int64) error {
if size != 0 {
return content.ErrUnsupportedSize
}
w.status.Offset = 0
w.digester.Hash().Reset()
if _, err := w.file.Seek(0, io.SeekStart); err != nil {
return err
}
return w.file.Truncate(0)
}
+10 -5
View File
@@ -30,15 +30,20 @@ type ThickCharts struct {
*metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ChartSpec `json:"spec,omitempty"`
Spec ThickChartSpec `json:"spec,omitempty"`
}
type ThickChartSpec struct {
ThickCharts []ThickChart `json:"charts,omitempty"`
Charts []ThickChart `json:"charts,omitempty"`
}
type ThickChart struct {
Name string `json:"name"`
RepoURL string `json:"repoURL"`
Version string `json:"version"`
Name string `json:"name"`
RepoURL string `json:"repoURL"`
Version string `json:"version"`
ExtraImages []ChartImage `json:"extraImages"`
}
type ChartImage struct {
Reference string `json:"ref"`
}
+1 -2
View File
@@ -18,6 +18,5 @@ type FileSpec struct {
}
type File struct {
Ref string `json:"ref"`
Name string `json:"name,omitempty"`
Ref string `json:"ref"`
}
@@ -2,7 +2,6 @@ package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
const (
@@ -13,7 +12,7 @@ const (
var (
ContentGroupVersion = schema.GroupVersion{Group: ContentGroup, Version: Version}
SchemeBuilder = &scheme.Builder{GroupVersion: ContentGroupVersion}
// SchemeBuilder = &scheme.Builder{GroupVersion: ContentGroupVersion}
CollectionGroupVersion = schema.GroupVersion{Group: CollectionGroup, Version: Version}
)
+67 -2
View File
@@ -1,10 +1,75 @@
package artifact
import v1 "github.com/google/go-containerregistry/pkg/v1"
import (
"bytes"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/partial"
"github.com/google/go-containerregistry/pkg/v1/types"
"k8s.io/apimachinery/pkg/util/json"
)
var _ partial.Describable = (*marshallableConfig)(nil)
type Config interface {
// Raw returns the config bytes
Raw() ([]byte, error)
Descriptor() (v1.Descriptor, error)
Digest() (v1.Hash, error)
MediaType() (types.MediaType, error)
Size() (int64, error)
}
type Marshallable interface {
MediaType() (types.MediaType, error)
}
// ToConfig takes anything that is marshallabe and converts it into a Config
func ToConfig(i Marshallable) Config {
return &marshallableConfig{
Marshallable: i,
}
}
// marshallableConfig implements Config using helper methods
type marshallableConfig struct {
Marshallable
hash v1.Hash
size int64
}
func (c *marshallableConfig) Raw() ([]byte, error) {
return json.Marshal(c.Marshallable)
}
func (c *marshallableConfig) Digest() (v1.Hash, error) {
return Digest(c)
}
func (c *marshallableConfig) Size() (int64, error) {
return Size(c)
}
type WithRawConfig interface {
Raw() ([]byte, error)
}
func Digest(c WithRawConfig) (v1.Hash, error) {
b, err := c.Raw()
if err != nil {
return v1.Hash{}, err
}
digest, _, err := v1.SHA256(bytes.NewReader(b))
return digest, err
}
func Size(c WithRawConfig) (int64, error) {
b, err := c.Raw()
if err != nil {
return -1, err
}
return int64(len(b)), nil
}
+27 -13
View File
@@ -3,6 +3,7 @@ package chart
import (
gname "github.com/google/go-containerregistry/pkg/name"
"github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1"
"github.com/rancherfederal/hauler/pkg/artifact"
"github.com/rancherfederal/hauler/pkg/content/chart"
"github.com/rancherfederal/hauler/pkg/content/image"
@@ -12,26 +13,22 @@ var _ artifact.Collection = (*tchart)(nil)
// tchart is a thick chart that includes all the dependent images as well as the chart itself
type tchart struct {
name string
repo string
version string
chart *chart.Chart
chart *chart.Chart
config v1alpha1.ThickChart
computed bool
contents map[gname.Reference]artifact.OCI
}
func NewChart(name, repo, version string) (artifact.Collection, error) {
o, err := chart.NewChart(name, repo, version)
func NewChart(cfg v1alpha1.ThickChart) (artifact.Collection, error) {
o, err := chart.NewChart(cfg.Name, cfg.RepoURL, cfg.Version)
if err != nil {
return nil, err
}
return &tchart{
name: name,
repo: repo,
version: version,
chart: o,
config: cfg,
contents: make(map[gname.Reference]artifact.OCI),
}, nil
}
@@ -51,27 +48,29 @@ func (c *tchart) compute() error {
if err := c.dependentImages(); err != nil {
return err
}
if err := c.chartContents(); err != nil {
return err
}
if err := c.extraImages(); err != nil {
return err
}
c.computed = true
return nil
}
func (c *tchart) chartContents() error {
oci, err := chart.NewChart(c.name, c.repo, c.version)
oci, err := chart.NewChart(c.config.Name, c.config.RepoURL, c.config.Version)
if err != nil {
return err
}
tag := c.version
tag := c.config.Version
if tag == "" {
tag = gname.DefaultTag
}
ref, err := gname.ParseReference(c.name, gname.WithDefaultRegistry(""), gname.WithDefaultTag(tag))
ref, err := gname.ParseReference(c.config.Name, gname.WithDefaultRegistry(""), gname.WithDefaultTag(tag))
if err != nil {
return err
}
@@ -103,6 +102,21 @@ func (c *tchart) dependentImages() error {
}
c.contents[ref] = i
}
return nil
}
func (c *tchart) extraImages() error {
for _, img := range c.config.ExtraImages {
ref, err := gname.ParseReference(img.Reference)
if err != nil {
return err
}
i, err := image.NewImage(img.Reference)
if err != nil {
return err
}
c.contents[ref] = i
}
return nil
}
+7 -10
View File
@@ -12,6 +12,7 @@ import (
"github.com/google/go-containerregistry/pkg/name"
"github.com/rancherfederal/hauler/internal/getter"
"github.com/rancherfederal/hauler/pkg/artifact"
"github.com/rancherfederal/hauler/pkg/content/file"
"github.com/rancherfederal/hauler/pkg/content/image"
@@ -39,6 +40,7 @@ type k3s struct {
computed bool
contents map[name.Reference]artifact.OCI
channels map[string]string
client *getter.Client
}
func NewK3s(version string) (artifact.Collection, error) {
@@ -94,12 +96,9 @@ func (k *k3s) executable() error {
return ErrExecutableNotfound
}
f, err := file.NewFile(fref, "k3s")
if err != nil {
return err
}
f := file.NewFile(fref)
ref, err := name.ParseReference("hauler/k3s", name.WithDefaultTag(k.dnsCompliantVersion()), name.WithDefaultRegistry(""))
ref, err := name.ParseReference("k3s", name.WithDefaultTag(k.dnsCompliantVersion()), name.WithDefaultRegistry(""))
if err != nil {
return err
}
@@ -109,12 +108,10 @@ func (k *k3s) executable() error {
}
func (k *k3s) bootstrap() error {
f, err := file.NewFile(bootstrapUrl, "get-k3s.io")
if err != nil {
return err
}
namedBootstrapUrl := fmt.Sprintf("%s?filename=%s", bootstrapUrl, "k3s-init.sh")
f := file.NewFile(namedBootstrapUrl)
ref, err := name.ParseReference("hauler/get-k3s.io", name.WithDefaultRegistry(""), name.WithDefaultTag("latest"))
ref, err := name.ParseReference("k3s-init.sh", name.WithDefaultRegistry(""), name.WithDefaultTag("latest"))
if err != nil {
return err
}
+4 -3
View File
@@ -22,9 +22,10 @@ func TestNewK3s(t *testing.T) {
}
defer os.Remove(tmpdir)
s := store.NewStore(ctx, tmpdir)
s.Open()
defer s.Close()
s, err := store.NewStore(tmpdir)
if err != nil {
t.Error(err)
}
type args struct {
version string
@@ -1,10 +1,14 @@
package types
package consts
const (
OCIManifestSchema1 = "application/vnd.oci.image.manifest.v1+json"
DockerManifestSchema2 = "application/vnd.docker.distribution.manifest.v2+json"
DockerConfigJSON = "application/vnd.docker.container.image.v1+json"
DockerConfigJSON = "application/vnd.docker.container.image.v1+json"
DockerLayer = "application/vnd.docker.image.rootfs.diff.tar.gzip"
DockerForeignLayer = "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip"
DockerUncompressedLayer = "application/vnd.docker.image.rootfs.diff.tar"
OCILayer = "application/vnd.oci.image.layer.v1.tar+gzip"
// ChartConfigMediaType is the reserved media type for the Helm chart manifest config
ChartConfigMediaType = "application/vnd.cncf.helm.config.v1+json"
+26 -8
View File
@@ -16,9 +16,10 @@ import (
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/cli"
"github.com/rancherfederal/hauler/internal/layer"
"github.com/rancherfederal/hauler/internal/mapper"
"github.com/rancherfederal/hauler/pkg/artifact"
"github.com/rancherfederal/hauler/pkg/artifact/local"
"github.com/rancherfederal/hauler/pkg/artifact/types"
"github.com/rancherfederal/hauler/pkg/consts"
)
var _ artifact.OCI = (*Chart)(nil)
@@ -46,7 +47,7 @@ func NewChart(name, repo, version string) (*Chart, error) {
}
func (h *Chart) MediaType() string {
return types.OCIManifestSchema1
return consts.OCIManifestSchema1
}
func (h *Chart) Manifest() (*gv1.Manifest, error) {
@@ -94,7 +95,7 @@ func (h *Chart) configDescriptor() (gv1.Descriptor, error) {
}
return gv1.Descriptor{
MediaType: types.ChartConfigMediaType,
MediaType: consts.ChartConfigMediaType,
Size: size,
Digest: hash,
}, nil
@@ -129,13 +130,30 @@ func (h *Chart) chartDataLayer() (gv1.Layer, error) {
annotations := make(map[string]string)
annotations[ocispec.AnnotationTitle] = filepath.Base(h.path)
return local.LayerFromOpener(chartOpener(h.path),
local.WithMediaType(types.ChartLayerMediaType),
local.WithAnnotations(annotations))
return layer.FromOpener(chartOpener(h.path),
layer.WithMediaType(consts.ChartLayerMediaType),
layer.WithAnnotations(annotations))
}
func chartOpener(path string) local.Opener {
func chartOpener(path string) layer.Opener {
return func() (io.ReadCloser, error) {
return os.Open(path)
}
}
func Mapper() map[string]mapper.Fn {
m := make(map[string]mapper.Fn)
chartMapperFn := mapper.Fn(func(desc ocispec.Descriptor) (*os.File, error) {
// TODO: Fix this
return os.Create("chart.tar.gz")
})
provMapperFn := mapper.Fn(func(desc ocispec.Descriptor) (*os.File, error) {
return os.Create("prov.json")
})
m[consts.ChartLayerMediaType] = chartMapperFn
m[consts.ProvLayerMediaType] = provMapperFn
return m
}
+5 -4
View File
@@ -25,9 +25,10 @@ func TestChart_Copy(t *testing.T) {
}
defer os.Remove(tmpdir)
s := store.NewStore(ctx, tmpdir)
s.Open()
defer s.Close()
s, err := store.NewStore(tmpdir)
if err != nil {
t.Error(err)
}
type args struct {
ctx context.Context
@@ -48,7 +49,7 @@ func TestChart_Copy(t *testing.T) {
},
args: args{
ctx: ctx,
registry: s.Registry(),
registry: "",
},
wantErr: false,
},
-82
View File
@@ -1,82 +0,0 @@
package file
import (
"bytes"
"encoding/json"
gv1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/partial"
gtypes "github.com/google/go-containerregistry/pkg/v1/types"
"github.com/rancherfederal/hauler/pkg/artifact/types"
)
var _ partial.Describable = (*config)(nil)
type config struct {
Reference string `json:"ref"` // Reference is the reference from where the file was sourced
Name string `json:"name"` // Name is the files name on disk
Annotations map[string]string `json:"annotations,omitempty"`
URLs []string `json:"urls,omitempty"`
computed bool
size int64
hash gv1.Hash
}
func (c config) Descriptor() (gv1.Descriptor, error) {
if err := c.compute(); err != nil {
return gv1.Descriptor{}, err
}
return gv1.Descriptor{
MediaType: types.FileConfigMediaType,
Size: c.size,
Digest: c.hash,
URLs: c.URLs,
Annotations: c.Annotations,
// Platform: nil,
}, nil
}
func (c config) Digest() (gv1.Hash, error) {
if err := c.compute(); err != nil {
return gv1.Hash{}, err
}
return c.hash, nil
}
func (c config) MediaType() (gtypes.MediaType, error) {
return types.FileConfigMediaType, nil
}
func (c config) Size() (int64, error) {
if err := c.compute(); err != nil {
return 0, err
}
return c.size, nil
}
func (c *config) Raw() ([]byte, error) {
return json.Marshal(c)
}
func (c *config) compute() error {
if c.computed {
return nil
}
data, err := c.Raw()
if err != nil {
return err
}
h, size, err := gv1.SHA256(bytes.NewBuffer(data))
if err != nil {
return err
}
c.size = size
c.hash = h
return nil
}
+71 -56
View File
@@ -1,107 +1,122 @@
package file
import (
"io"
"net/http"
"context"
"os"
"strings"
gv1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/partial"
gtypes "github.com/google/go-containerregistry/pkg/v1/types"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/rancherfederal/hauler/internal/getter"
"github.com/rancherfederal/hauler/internal/mapper"
"github.com/rancherfederal/hauler/pkg/artifact"
"github.com/rancherfederal/hauler/pkg/artifact/local"
"github.com/rancherfederal/hauler/pkg/artifact/types"
"github.com/rancherfederal/hauler/pkg/consts"
)
// interface guard
var _ artifact.OCI = (*file)(nil)
type file struct {
blob gv1.Layer
config config
blobMap map[gv1.Hash]gv1.Layer
ref string
client *getter.Client
computed bool
config artifact.Config
blob gv1.Layer
manifest *gv1.Manifest
annotations map[string]string
}
func NewFile(ref string, filename string) (*file, error) {
var getter local.Opener
if strings.HasPrefix(ref, "http") || strings.HasPrefix(ref, "https") {
getter = remoteOpener(ref)
} else {
getter = localOpener(ref)
}
func NewFile(ref string) *file {
// TODO: Allow user to configure this
client := getter.NewClient(getter.ClientOptions{})
annotations := make(map[string]string)
annotations[ocispec.AnnotationTitle] = filename // For oras FileStore to recognize
annotations[ocispec.AnnotationSource] = ref
blob, err := local.LayerFromOpener(getter,
local.WithMediaType(types.FileLayerMediaType),
local.WithAnnotations(annotations))
if err != nil {
return nil, err
return &file{
client: client,
ref: ref,
}
}
f := &file{
blob: blob,
config: config{
Reference: ref,
Name: filename,
},
}
return f, nil
func (f *file) Name(ref string) string {
return f.client.Name(ref)
}
func (f *file) MediaType() string {
return types.OCIManifestSchema1
return consts.OCIManifestSchema1
}
func (f *file) RawConfig() ([]byte, error) {
if err := f.compute(); err != nil {
return nil, err
}
return f.config.Raw()
}
func (f *file) Layers() ([]gv1.Layer, error) {
if err := f.compute(); err != nil {
return nil, err
}
var layers []gv1.Layer
layers = append(layers, f.blob)
return layers, nil
}
func (f *file) Manifest() (*gv1.Manifest, error) {
desc, err := partial.Descriptor(f.blob)
if err != nil {
if err := f.compute(); err != nil {
return nil, err
}
layerDescs := []gv1.Descriptor{*desc}
return f.manifest, nil
}
cfgDesc, err := f.config.Descriptor()
if err != nil {
return nil, err
func (f *file) compute() error {
if f.computed {
return nil
}
return &gv1.Manifest{
ctx := context.Background()
blob, err := f.client.LayerFrom(ctx, f.ref)
if err != nil {
return err
}
layer, err := partial.Descriptor(blob)
if err != nil {
return err
}
cfg := f.client.Config(f.ref)
cfgDesc, err := partial.Descriptor(cfg)
if err != nil {
return err
}
m := &gv1.Manifest{
SchemaVersion: 2,
MediaType: gtypes.MediaType(f.MediaType()),
Config: cfgDesc,
Layers: layerDescs,
Config: *cfgDesc,
Layers: []gv1.Descriptor{*layer},
Annotations: f.annotations,
}, nil
}
func localOpener(path string) local.Opener {
return func() (io.ReadCloser, error) {
return os.Open(path)
}
f.manifest = m
f.config = cfg
f.blob = blob
f.computed = true
return nil
}
func remoteOpener(url string) local.Opener {
return func() (io.ReadCloser, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
func Mapper() map[string]mapper.Fn {
m := make(map[string]mapper.Fn)
blobMapperFn := mapper.Fn(func(desc ocispec.Descriptor) (*os.File, error) {
if _, ok := desc.Annotations[ocispec.AnnotationTitle]; !ok {
return nil, errors.Errorf("unkown file name")
}
return resp.Body, nil
}
return os.Create(desc.Annotations[ocispec.AnnotationTitle])
})
m[consts.FileLayerMediaType] = blobMapperFn
return m
}
+165 -186
View File
@@ -1,188 +1,167 @@
package file_test
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1"
"github.com/rancherfederal/hauler/pkg/artifact/types"
"github.com/rancherfederal/hauler/pkg/content/file"
"github.com/rancherfederal/hauler/pkg/log"
"github.com/rancherfederal/hauler/pkg/store"
)
func TestFile_Copy(t *testing.T) {
ctx := context.Background()
l := log.NewLogger(os.Stdout)
ctx = l.WithContext(ctx)
tmpdir, err := os.MkdirTemp("", "hauler")
if err != nil {
t.Error(err)
}
defer os.Remove(tmpdir)
// Make a temp file
f, err := os.CreateTemp(tmpdir, "tmp")
f.Write([]byte("content"))
defer f.Close()
fs := newTestFileServer(tmpdir)
fs.Start()
defer fs.Stop()
s := store.NewStore(ctx, tmpdir)
s.Open()
defer s.Close()
type args struct {
ctx context.Context
registry string
}
tests := []struct {
name string
cfg v1alpha1.File
args args
wantErr bool
}{
{
name: "should copy a local file successfully without an explicit name",
cfg: v1alpha1.File{
Ref: f.Name(),
Name: filepath.Base(f.Name()),
},
args: args{
ctx: ctx,
},
},
{
name: "should copy a local file successfully with an explicit name",
cfg: v1alpha1.File{
Ref: f.Name(),
Name: "my-other-file",
},
args: args{
ctx: ctx,
},
},
{
name: "should fail to copy a local file successfully with a malformed explicit name",
cfg: v1alpha1.File{
Ref: f.Name(),
Name: "my!invalid~@file",
},
args: args{
ctx: ctx,
},
wantErr: true,
},
{
name: "should copy a remote file successfully without an explicit name",
cfg: v1alpha1.File{
Ref: fmt.Sprintf("%s/%s", fs.server.URL, filepath.Base(f.Name())),
},
args: args{
ctx: ctx,
},
},
{
name: "should copy a remote file successfully with an explicit name",
cfg: v1alpha1.File{
Ref: fmt.Sprintf("%s/%s", fs.server.URL, filepath.Base(f.Name())),
Name: "my-other-file",
},
args: args{
ctx: ctx,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, err := file.NewFile(tt.cfg.Ref, tt.cfg.Name)
if err != nil {
t.Fatal(err)
}
ref, err := name.ParseReference("myfile")
if err != nil {
t.Fatal(err)
}
_, err = s.AddArtifact(ctx, f, ref)
if (err != nil) != tt.wantErr {
t.Error(err)
}
// if err := validate(tt.cfg.Ref, tt.cfg.Name, m); err != nil {
// t.Error(err)
// }
})
}
}
type testFileServer struct {
server *httptest.Server
}
func newTestFileServer(path string) *testFileServer {
s := httptest.NewUnstartedServer(http.FileServer(http.Dir(path)))
return &testFileServer{server: s}
}
func (s *testFileServer) Start() *httptest.Server {
s.server.Start()
return s.server
}
func (s *testFileServer) Stop() {
s.server.Close()
}
// validate ensure
func validate(ref string, name string, got *v1.Manifest) error {
data, err := os.ReadFile(ref)
if err != nil {
return err
}
d := digest.FromBytes(data)
annotations := make(map[string]string)
annotations[ocispec.AnnotationTitle] = name
annotations[ocispec.AnnotationSource] = ref
want := &v1.Manifest{
SchemaVersion: 2,
MediaType: types.OCIManifestSchema1,
Config: v1.Descriptor{},
Layers: []v1.Descriptor{
{
MediaType: types.FileLayerMediaType,
Size: int64(len(data)),
Digest: v1.Hash{
Algorithm: d.Algorithm().String(),
Hex: d.Hex(),
},
Annotations: annotations,
},
},
Annotations: nil,
}
if !reflect.DeepEqual(want.Layers, got.Layers) {
return fmt.Errorf("want = (%v) | got = (%v)", want, got)
}
return nil
}
// func TestFile_Copy(t *testing.T) {
// ctx := context.Background()
// l := log.NewLogger(os.Stdout)
// ctx = l.WithContext(ctx)
//
// tmpdir, err := os.MkdirTemp("", "hauler")
// if err != nil {
// t.Error(err)
// }
// defer os.Remove(tmpdir)
//
// // Make a temp file
// f, err := os.CreateTemp(tmpdir, "tmp")
// f.Write([]byte("content"))
// defer f.Close()
//
// fs := newTestFileServer(tmpdir)
// fs.Start()
// defer fs.Stop()
//
// s, err := store.NewBundle(tmpdir)
// if err != nil {
// t.Error(err)
// }
//
// type args struct {
// ctx context.Context
// registry string
// }
//
// tests := []struct {
// name string
// cfg v1alpha1.File
// args args
// wantErr bool
// }{
// {
// name: "should copy a local file successfully without an explicit name",
// cfg: v1alpha1.File{
// Ref: f.Name(),
// Name: filepath.Base(f.Name()),
// },
// args: args{
// ctx: ctx,
// },
// },
// {
// name: "should copy a local file successfully with an explicit name",
// cfg: v1alpha1.File{
// Ref: f.Name(),
// Name: "my-other-file",
// },
// args: args{
// ctx: ctx,
// },
// },
// {
// name: "should fail to copy a local file successfully with a malformed explicit name",
// cfg: v1alpha1.File{
// Ref: f.Name(),
// Name: "my!invalid~@file",
// },
// args: args{
// ctx: ctx,
// },
// wantErr: true,
// },
// {
// name: "should copy a remote file successfully without an explicit name",
// cfg: v1alpha1.File{
// Ref: fmt.Sprintf("%s/%s", fs.server.URL, filepath.Base(f.Name())),
// },
// args: args{
// ctx: ctx,
// },
// },
// {
// name: "should copy a remote file successfully with an explicit name",
// cfg: v1alpha1.File{
// Ref: fmt.Sprintf("%s/%s", fs.server.URL, filepath.Base(f.Name())),
// Name: "my-other-file",
// },
// args: args{
// ctx: ctx,
// },
// },
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// f, err := file.NewFile(tt.cfg.Ref, tt.cfg.Name)
// if err != nil {
// t.Fatal(err)
// }
//
// ref, err := name.ParseReference("myfile")
// if err != nil {
// t.Fatal(err)
// }
//
// _, err = s.AddArtifact(ctx, f, ref)
// if (err != nil) != tt.wantErr {
// t.Error(err)
// }
//
// // if err := validate(tt.cfg.Ref, tt.cfg.Name, m); err != nil {
// // t.Error(err)
// // }
// })
// }
// }
//
// type testFileServer struct {
// server *httptest.Server
// }
//
// func newTestFileServer(path string) *testFileServer {
// s := httptest.NewUnstartedServer(http.FileServer(http.Dir(path)))
// return &testFileServer{server: s}
// }
//
// func (s *testFileServer) Start() *httptest.Server {
// s.server.Start()
// return s.server
// }
//
// func (s *testFileServer) Stop() {
// s.server.Close()
// }
//
// // validate ensure
// func validate(ref string, name string, got *v1.Manifest) error {
// data, err := os.ReadFile(ref)
// if err != nil {
// return err
// }
//
// d := digest.FromBytes(data)
//
// annotations := make(map[string]string)
// annotations[ocispec.AnnotationTitle] = name
// annotations[ocispec.AnnotationSource] = ref
//
// want := &v1.Manifest{
// SchemaVersion: 2,
// MediaType: types.OCIManifestSchema1,
// Config: v1.Descriptor{},
// Layers: []v1.Descriptor{
// {
// MediaType: types.FileLayerMediaType,
// Size: int64(len(data)),
// Digest: v1.Hash{
// Algorithm: d.Algorithm().String(),
// Hex: d.Hex(),
// },
// Annotations: annotations,
// },
// },
// Annotations: nil,
// }
//
// if !reflect.DeepEqual(want.Layers, got.Layers) {
// return fmt.Errorf("want = (%v) | got = (%v)", want, got)
// }
// return nil
// }
+114
View File
@@ -0,0 +1,114 @@
package file
import (
"errors"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/rancherfederal/hauler/internal/layer"
)
// Locatable represents content that can be referenced by a
type Locatable interface {
Locate() string
Name() string
Annotate() map[string]string
Open() layer.Opener
}
var (
_ Locatable = (*localFile)(nil)
_ Locatable = (*remoteFile)(nil)
)
func NewLocatableLocalFile(path string) (*localFile, error) {
if _, err := os.Stat(path); err != nil {
return nil, err
}
abs, err := filepath.Abs(path)
if err != nil {
return nil, err
}
return &localFile{
path: abs,
}, nil
}
type localFile struct {
path string
}
func (l localFile) Locate() string {
return l.path
}
func (l localFile) Name() string {
return filepath.Base(l.path)
}
func (l localFile) Annotate() map[string]string {
annotations := make(map[string]string)
annotations[ocispec.AnnotationTitle] = l.Name() // For oras FileStore to recognize
annotations[ocispec.AnnotationSource] = l.Locate()
return annotations
}
func (l localFile) Open() layer.Opener {
return func() (io.ReadCloser, error) {
return os.Open(l.Locate())
}
}
func NewLocatableRemoteFile(ref string) (*remoteFile, error) {
u, err := url.Parse(ref)
if err != nil {
return nil, err
}
if u.Scheme != "https" && u.Scheme != "http" {
return nil, errors.New("not a valid remote file protocol (http or https)")
}
return &remoteFile{
ref: ref,
}, err
}
type remoteFile struct {
ref string
name string
}
func (r remoteFile) Locate() string {
return r.ref
}
func (r remoteFile) Name() string {
if r.name != "" {
return r.name
}
return filepath.Base(r.ref)
}
func (r remoteFile) Annotate() map[string]string {
annotations := make(map[string]string)
annotations[ocispec.AnnotationTitle] = r.Name()
annotations[ocispec.AnnotationSource] = r.Locate()
return annotations
}
func (r remoteFile) Open() layer.Opener {
return func() (io.ReadCloser, error) {
resp, err := http.Get(r.ref)
if err != nil {
return nil, err
}
return resp.Body, nil
}
}
+37
View File
@@ -1,11 +1,17 @@
package image
import (
"fmt"
"os"
"github.com/google/go-containerregistry/pkg/name"
gv1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/rancherfederal/hauler/internal/mapper"
"github.com/rancherfederal/hauler/pkg/artifact"
"github.com/rancherfederal/hauler/pkg/consts"
)
var _ artifact.OCI = (*image)(nil)
@@ -41,3 +47,34 @@ func NewImage(ref string) (*image, error) {
Image: img,
}, nil
}
func Mapper() map[string]mapper.Fn {
m := make(map[string]mapper.Fn)
manifestMapperFn := mapper.Fn(func(desc ocispec.Descriptor) (*os.File, error) {
return os.Create("manifest.json")
})
for _, l := range []string{consts.DockerManifestSchema2, consts.OCIManifestSchema1} {
m[l] = manifestMapperFn
}
layerMapperFn := mapper.Fn(func(desc ocispec.Descriptor) (*os.File, error) {
n := fmt.Sprintf("%s.tar.gz", desc.Digest.String())
return os.Create(n)
})
for _, l := range []string{consts.OCILayer, consts.DockerLayer} {
m[l] = layerMapperFn
}
configMapperFn := mapper.Fn(func(desc ocispec.Descriptor) (*os.File, error) {
return os.Create("config.json")
})
for _, l := range []string{consts.DockerConfigJSON} {
m[l] = configMapperFn
}
return m
}
+4 -3
View File
@@ -26,9 +26,10 @@ func TestImage_Copy(t *testing.T) {
}
defer os.Remove(tmpdir)
s := store.NewStore(ctx, tmpdir)
s.Open()
defer s.Close()
s, err := store.NewStore(tmpdir)
if err != nil {
t.Error(err)
}
type args struct {
ctx context.Context
-146
View File
@@ -1,146 +0,0 @@
package layout
import (
"bytes"
"encoding/json"
"io"
"os"
"strings"
"github.com/google/go-containerregistry/pkg/name"
gv1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/layout"
gtypes "github.com/google/go-containerregistry/pkg/v1/types"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"golang.org/x/sync/errgroup"
"github.com/rancherfederal/hauler/pkg/artifact"
)
// Path is a wrapper around layout.Path
type Path struct {
layout.Path
}
// FromPath returns a new Path or creates one if one doesn't exist
func FromPath(path string) (Path, error) {
p, err := layout.FromPath(path)
if os.IsNotExist(err) {
p, err = layout.Write(path, empty.Index)
if err != nil {
return Path{}, err
}
}
return Path{Path: p}, err
}
// WriteOci will write oci content (artifact.OCI) to the given Path
func (l Path) WriteOci(o artifact.OCI, reference name.Reference) (ocispec.Descriptor, error) {
layers, err := o.Layers()
if err != nil {
return ocispec.Descriptor{}, err
}
// Write layers concurrently
var g errgroup.Group
for _, layer := range layers {
layer := layer
g.Go(func() error {
return l.writeLayer(layer)
})
}
if err := g.Wait(); err != nil {
return ocispec.Descriptor{}, err
}
// Write the config
cfgBlob, err := o.RawConfig()
if err != nil {
return ocispec.Descriptor{}, err
}
if err = l.writeBlob(cfgBlob); err != nil {
return ocispec.Descriptor{}, err
}
m, err := o.Manifest()
if err != nil {
return ocispec.Descriptor{}, err
}
manifest, err := json.Marshal(m)
if err != nil {
return ocispec.Descriptor{}, err
}
if err := l.writeBlob(manifest); err != nil {
return ocispec.Descriptor{}, err
}
desc := ocispec.Descriptor{
MediaType: o.MediaType(),
Size: int64(len(manifest)),
Digest: digest.FromBytes(manifest),
Annotations: map[string]string{
ocispec.AnnotationRefName: reference.Name(),
ocispec.AnnotationTitle: deregistry(reference).Name(),
},
}
if err := l.appendDescriptor(desc); err != nil {
return ocispec.Descriptor{}, err
}
return desc, nil
}
// writeBlob differs from layer.WriteBlob in that it requires data instead
func (l Path) writeBlob(data []byte) error {
h, _, err := gv1.SHA256(bytes.NewReader(data))
if err != nil {
return err
}
return l.WriteBlob(h, io.NopCloser(bytes.NewReader(data)))
}
// writeLayer is a verbatim reimplementation of layout.writeLayer
func (l Path) writeLayer(layer gv1.Layer) error {
d, err := layer.Digest()
if err != nil {
return err
}
r, err := layer.Compressed()
if err != nil {
return err
}
return l.WriteBlob(d, r)
}
// appendDescriptor is a helper that translates a ocispec.Descriptor into a gv1.Descriptor
func (l Path) appendDescriptor(desc ocispec.Descriptor) error {
gdesc := gv1.Descriptor{
MediaType: gtypes.MediaType(desc.MediaType),
Size: desc.Size,
Digest: gv1.Hash{
Algorithm: desc.Digest.Algorithm().String(),
Hex: desc.Digest.Hex(),
},
URLs: desc.URLs,
Annotations: desc.Annotations,
}
return l.AppendDescriptor(gdesc)
}
// deregistry removes the registry content from a name.Reference
func deregistry(ref name.Reference) name.Reference {
// No error checking b/c at this point we're already assumed to have a valid enough reference
dereg := strings.TrimLeft(strings.ReplaceAll(ref.Name(), ref.Context().RegistryStr(), ""), "/")
deref, _ := name.ParseReference(dereg, name.WithDefaultRegistry(""))
return deref
}
-191
View File
@@ -1,191 +0,0 @@
package layout
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/content/local"
"github.com/containerd/containerd/remotes/docker"
"github.com/google/go-containerregistry/pkg/name"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
orascontent "oras.land/oras-go/pkg/content"
"oras.land/oras-go/pkg/oras"
"github.com/rancherfederal/hauler/pkg/artifact/types"
)
// interface guards
var (
_ content.Provider = (*OCIStore)(nil)
_ content.Ingester = (*OCIStore)(nil)
)
// OCIStore represents a content compatible store adhering by the oci-layout spec
type OCIStore struct {
content.Store
root string
index *ocispec.Index
digestMap map[string]ocispec.Descriptor
}
// Copy placeholder until we migrate to oras 0.5
// Will loop through each appropriately named index and copy the contents to the desired registry
func Copy(ctx context.Context, s *OCIStore, registry string) error {
for _, desc := range s.index.Manifests {
manifestBlobPath, err := s.blobPath(desc.Digest)
if err != nil {
return err
}
manifestData, err := os.ReadFile(manifestBlobPath)
if err != nil {
return err
}
m, mdesc, err := loadManifest(manifestData)
if err != nil {
return err
}
refName, ok := desc.Annotations[ocispec.AnnotationRefName]
if !ok {
return fmt.Errorf("no name found to push image")
}
rref, err := RelocateReference(refName, registry)
if err != nil {
return err
}
resolver := docker.NewResolver(docker.ResolverOptions{})
_, err = oras.Push(ctx, resolver, rref.Name(), s, m.Layers,
oras.WithConfig(m.Config), oras.WithNameValidation(nil), oras.WithManifest(mdesc))
if err != nil {
return err
}
}
return nil
}
// NewOCIStore will return a new OCIStore given a path to an oci-layout compatible directory
func NewOCIStore(path string) (*OCIStore, error) {
fs, err := local.NewStore(path)
if err != nil {
return nil, err
}
store := &OCIStore{
Store: fs,
root: path,
}
if err := store.validateOCILayout(); err != nil {
return nil, err
}
if err := store.LoadIndex(); err != nil {
return nil, nil
}
return store, nil
}
// LoadIndex will load an oci-layout compatible directory
func (s *OCIStore) LoadIndex() error {
path := filepath.Join(s.root, types.OCIImageIndexFile)
indexFile, err := os.Open(path)
if err != nil {
// TODO: Don't just bomb out?
return err
}
defer indexFile.Close()
if err := json.NewDecoder(indexFile).Decode(&s.index); err != nil {
return err
}
s.digestMap = make(map[string]ocispec.Descriptor)
for _, desc := range s.index.Manifests {
if name := desc.Annotations[ocispec.AnnotationRefName]; name != "" {
s.digestMap[name] = desc
}
}
return nil
}
func (s *OCIStore) validateOCILayout() error {
layoutFilePath := filepath.Join(s.root, ocispec.ImageLayoutFile)
layoutFile, err := os.Open(layoutFilePath)
if err != nil {
return err
}
defer layoutFile.Close()
var layout *ocispec.ImageLayout
if err := json.NewDecoder(layoutFile).Decode(&layout); err != nil {
return err
}
if layout.Version != ocispec.ImageLayoutVersion {
return orascontent.ErrUnsupportedVersion
}
return nil
}
func (s *OCIStore) blobPath(d digest.Digest) (string, error) {
if err := d.Validate(); err != nil {
return "", err
}
return filepath.Join(s.root, "blobs", d.Algorithm().String(), d.Hex()), nil
}
// manifest is a field wrapper around ocispec.Manifest that contains the mediaType field
type manifest struct {
ocispec.Manifest `json:",inline"`
MediaType string `json:"mediaType"`
}
// loadManifest
func loadManifest(data []byte) (ocispec.Manifest, ocispec.Descriptor, error) {
var m manifest
if err := json.Unmarshal(data, &m); err != nil {
return ocispec.Manifest{}, ocispec.Descriptor{}, err
}
desc := ocispec.Descriptor{
MediaType: m.MediaType,
Digest: digest.FromBytes(data),
Size: int64(len(data)),
}
return m.Manifest, desc, nil
}
// RelocateReference returns a name.Reference given a reference and registry
func RelocateReference(reference string, registry string) (name.Reference, error) {
ref, err := name.ParseReference(reference)
if err != nil {
return nil, err
}
relocated, err := name.ParseReference(ref.Context().RepositoryStr(), name.WithDefaultRegistry(registry))
if err != nil {
return nil, err
}
if _, err := name.NewDigest(ref.Name()); err == nil {
return relocated.Context().Digest(ref.Identifier()), nil
}
return relocated.Context().Tag(ref.Identifier()), nil
}
+27
View File
@@ -0,0 +1,27 @@
package server
import (
"context"
"net/http"
"os"
"time"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
func NewFile(ctx context.Context, root string) (Server, error) {
http.FileServer(http.Dir(root))
r := mux.NewRouter()
r.Handle("/", handlers.LoggingHandler(os.Stdout, http.FileServer(http.Dir(root))))
srv := &http.Server{
Handler: r,
Addr: ":8000",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
return srv, nil
}
+118
View File
@@ -0,0 +1,118 @@
package server
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"time"
"github.com/distribution/distribution/v3/configuration"
"github.com/distribution/distribution/v3/registry"
"github.com/distribution/distribution/v3/registry/handlers"
"github.com/docker/go-metrics"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
func NewRegistry(ctx context.Context, cfg *configuration.Configuration) (*registry.Registry, error) {
r, err := registry.NewRegistry(ctx, cfg)
if err != nil {
return nil, err
}
if cfg.HTTP.Debug.Prometheus.Enabled {
path := cfg.HTTP.Debug.Prometheus.Path
if path == "" {
path = "/metrics"
}
http.Handle(path, metrics.Handler())
}
return r, nil
}
type tmpRegistryServer struct {
*httptest.Server
}
func NewTempRegistry(ctx context.Context, root string) *tmpRegistryServer {
cfg := &configuration.Configuration{
Version: "0.1",
Storage: configuration.Storage{
"cache": configuration.Parameters{"blobdescriptor": "inmemory"},
"filesystem": configuration.Parameters{"rootdirectory": root},
},
}
cfg.Log.Level = "error"
cfg.HTTP.Headers = http.Header{
"X-Content-Type-Options": []string{"nosniff"},
}
l, err := logrus.ParseLevel("panic")
if err != nil {
l = logrus.ErrorLevel
}
logrus.SetLevel(l)
app := handlers.NewApp(ctx, cfg)
app.RegisterHealthChecks()
handler := alive("/", app)
s := httptest.NewUnstartedServer(handler)
return &tmpRegistryServer{
Server: s,
}
}
func (t *tmpRegistryServer) Registry() string {
return strings.Replace(t.Server.URL, "http://", "", 1)
}
func (t *tmpRegistryServer) Start() error {
t.Server.Start()
err := retry(5, 1*time.Second, func() (err error) {
resp, err := http.Get(t.Server.URL + "/v2")
if err != nil {
return err
}
resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return nil
}
return errors.New("to start temporary registry")
})
return err
}
func (t *tmpRegistryServer) Stop() {
t.Server.Close()
}
func alive(path string, handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == path {
w.Header().Set("Cache-Control", "no-cache")
w.WriteHeader(http.StatusOK)
return
}
handler.ServeHTTP(w, r)
})
}
func retry(attempts int, sleep time.Duration, f func() error) (err error) {
for i := 0; i < attempts; i++ {
if i > 0 {
time.Sleep(sleep)
sleep *= 2
}
err = f()
if err == nil {
return nil
}
}
return fmt.Errorf("after %d attempts, last error: %s", attempts, err)
}
+5
View File
@@ -0,0 +1,5 @@
package server
type Server interface {
ListenAndServe() error
}
-146
View File
@@ -1,146 +0,0 @@
package store
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"github.com/google/go-containerregistry/pkg/name"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/rancherfederal/hauler/pkg/artifact"
"github.com/rancherfederal/hauler/pkg/cache"
"github.com/rancherfederal/hauler/pkg/layout"
)
// AddArtifact will add an artifact.OCI to the store
// The method to achieve this is to save artifact.OCI to a temporary directory in an OCI layout compatible form. Once
// saved, the entirety of the layout is copied to the store (which is just a registry). This allows us to not only use
// strict types to define generic content, but provides a processing pipeline suitable for extensibility. In the
// future we'll allow users to define their own content that must adhere either by artifact.OCI or simply an OCI layout.
func (s *Store) AddArtifact(ctx context.Context, oci artifact.OCI, reference name.Reference) (ocispec.Descriptor, error) {
if err := s.precheck(); err != nil {
return ocispec.Descriptor{}, err
}
stg, err := newOciStage()
if err != nil {
return ocispec.Descriptor{}, err
}
if s.cache != nil {
cached := cache.Oci(oci, s.cache)
oci = cached
}
pdesc, err := stg.add(ctx, oci, reference)
if err != nil {
return ocispec.Descriptor{}, err
}
if err := stg.commit(ctx, s); err != nil {
return ocispec.Descriptor{}, nil
}
return pdesc, nil
}
// Flush is a fancy name for delete-all-the-things, in this case it's as trivial as deleting everything in the underlying store directory
// This can be a highly destructive operation if the store's directory happens to be inline with other non-store contents
// To reduce the blast radius and likelihood of deleting things we don't own, Flush explicitly includes docker/registry/v2
// in the search dir
func (s *Store) Flush(ctx context.Context) error {
contentDir := filepath.Join(s.DataDir, "docker", "registry", "v2")
fs, err := ioutil.ReadDir(contentDir)
if !os.IsNotExist(err) && err != nil {
return err
}
for _, f := range fs {
err := os.RemoveAll(filepath.Join(contentDir, f.Name()))
if err != nil {
return err
}
}
return nil
}
// AddCollection .
func (s *Store) AddCollection(ctx context.Context, coll artifact.Collection) ([]ocispec.Descriptor, error) {
if err := s.precheck(); err != nil {
return nil, err
}
cnts, err := coll.Contents()
if err != nil {
return nil, err
}
for ref, o := range cnts {
if _, err := s.AddArtifact(ctx, o, ref); err != nil {
return nil, nil
}
}
return nil, err
}
type stager interface {
// add adds an artifact.OCI to the stage
add(artifact.OCI) error
// commit pushes all the staged contents into the store and closes the stage
commit(*Store) error
// close flushes and closes the stage
close() error
}
type oci struct {
layout layout.Path
root string
}
func (o *oci) add(ctx context.Context, oci artifact.OCI, reference name.Reference) (ocispec.Descriptor, error) {
mdesc, err := o.layout.WriteOci(oci, reference)
if err != nil {
return ocispec.Descriptor{}, err
}
return mdesc, err
}
func (o *oci) commit(ctx context.Context, s *Store) error {
defer o.close()
ts, err := layout.NewOCIStore(o.root)
if err != nil {
return err
}
if err = layout.Copy(ctx, ts, s.Registry()); err != nil {
return err
}
return err
}
func (o *oci) close() error {
return os.RemoveAll(o.root)
}
func newOciStage() (*oci, error) {
tmpdir, err := os.MkdirTemp("", "hauler")
if err != nil {
return nil, err
}
l, err := layout.FromPath(tmpdir)
if err != nil {
return nil, err
}
return &oci{
layout: l,
root: tmpdir,
}, nil
}
+233
View File
@@ -0,0 +1,233 @@
package store
import (
"context"
"encoding/json"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
ccontent "github.com/containerd/containerd/content"
"github.com/containerd/containerd/content/local"
"github.com/containerd/containerd/remotes"
"github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/pkg/content"
"oras.land/oras-go/pkg/target"
"github.com/rancherfederal/hauler/internal/mapper"
"github.com/rancherfederal/hauler/pkg/consts"
)
var _ target.Target = (*oci)(nil)
type oci struct {
ccontent.Store
root string
index *ocispec.Index
nameMap *sync.Map // map[string]ocispec.Descriptor
}
func NewOCI(root string) (*oci, error) {
fs, err := local.NewStore(root)
if err != nil {
return nil, err
}
return &oci{
Store: fs,
root: root,
nameMap: &sync.Map{},
}, nil
}
func (o *oci) LoadIndex() error {
path := o.path(consts.OCIImageIndexFile)
idx, err := os.Open(path)
if err != nil {
if !os.IsNotExist(err) {
return err
}
o.index = &ocispec.Index{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
}
return nil
}
defer idx.Close()
if err := json.NewDecoder(idx).Decode(&o.index); err != nil {
return err
}
for _, desc := range o.index.Manifests {
if name := desc.Annotations[ocispec.AnnotationRefName]; name != "" {
o.nameMap.Store(name, desc)
}
}
return nil
}
func (o *oci) SaveIndex() error {
var descs []ocispec.Descriptor
o.nameMap.Range(func(name, desc interface{}) bool {
n := name.(string)
d := desc.(ocispec.Descriptor)
if d.Annotations == nil {
d.Annotations = make(map[string]string)
}
d.Annotations[ocispec.AnnotationRefName] = n
descs = append(descs, d)
return true
})
o.index.Manifests = descs
data, err := json.Marshal(o.index)
if err != nil {
return err
}
return os.WriteFile(o.path(consts.OCIImageIndexFile), data, 0644)
}
// Resolve attempts to resolve the reference into a name and descriptor.
//
// The argument `ref` should be a scheme-less URI representing the remote.
// Structurally, it has a host and path. The "host" can be used to directly
// reference a specific host or be matched against a specific handler.
//
// The returned name should be used to identify the referenced entity.
// Dependending on the remote namespace, this may be immutable or mutable.
// While the name may differ from ref, it should itself be a valid ref.
//
// If the resolution fails, an error will be returned.
func (o *oci) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) {
if err := o.LoadIndex(); err != nil {
return "", ocispec.Descriptor{}, err
}
d, ok := o.nameMap.Load(ref)
if !ok {
return "", ocispec.Descriptor{}, err
}
desc = d.(ocispec.Descriptor)
return ref, desc, nil
}
// Fetcher returns a new fetcher for the provided reference.
// All content fetched from the returned fetcher will be
// from the namespace referred to by ref.
func (o *oci) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) {
if err := o.LoadIndex(); err != nil {
return nil, err
}
if _, ok := o.nameMap.Load(ref); !ok {
return nil, nil
}
return o, nil
}
func (o *oci) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) {
readerAt, err := o.blobReaderAt(desc)
if err != nil {
return nil, err
}
return readerAt, nil
}
// Pusher returns a new pusher for the provided reference
// The returned Pusher should satisfy content.Ingester and concurrent attempts
// to push the same blob using the Ingester API should result in ErrUnavailable.
func (o *oci) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) {
if err := o.LoadIndex(); err != nil {
return nil, err
}
var (
baseRef, hash string
)
parts := strings.SplitN(ref, "@", 2)
baseRef = parts[0]
if len(parts) > 1 {
hash = parts[1]
}
return &ociPusher{
oci: o,
ref: baseRef,
digest: hash,
}, nil
}
func (o *oci) blobReaderAt(desc ocispec.Descriptor) (*os.File, error) {
blobPath, err := o.ensureBlob(desc)
if err != nil {
return nil, err
}
return os.Open(blobPath)
}
func (o *oci) blobWriterAt(desc ocispec.Descriptor) (*os.File, error) {
blobPath, err := o.ensureBlob(desc)
if err != nil {
return nil, err
}
return os.OpenFile(blobPath, os.O_WRONLY|os.O_CREATE, 0644)
}
func (o *oci) ensureBlob(desc ocispec.Descriptor) (string, error) {
dir := o.path("blobs", desc.Digest.Algorithm().String())
if err := os.MkdirAll(dir, os.ModePerm); err != nil && !os.IsExist(err) {
return "", err
}
return filepath.Join(dir, desc.Digest.Hex()), nil
}
func (o *oci) path(elem ...string) string {
complete := []string{string(o.root)}
return filepath.Join(append(complete, elem...)...)
}
type ociPusher struct {
oci *oci
ref string
digest string
}
// Push returns a content writer for the given resource identified
// by the descriptor.
func (p *ociPusher) Push(ctx context.Context, d ocispec.Descriptor) (ccontent.Writer, error) {
switch d.MediaType {
case ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex:
// if the hash of the content matches that which was provided as the hash for the root, mark it
if p.digest != "" && p.digest == d.Digest.String() {
if err := p.oci.LoadIndex(); err != nil {
return nil, err
}
p.oci.nameMap.Store(p.ref, d)
if err := p.oci.SaveIndex(); err != nil {
return nil, err
}
}
}
blobPath, err := p.oci.ensureBlob(d)
if err != nil {
return nil, err
}
if _, err := os.Stat(blobPath); err == nil {
// file already exists, discard (but validate digest)
return content.NewIoContentWriter(ioutil.Discard, content.WithOutputHash(d.Digest)), nil
}
writer, err := mapper.NewFileWriter(blobPath, 0644, d.Size)
if err != nil {
return nil, err
}
return writer, nil
}
+5 -12
View File
@@ -1,20 +1,13 @@
package store
import "github.com/rancherfederal/hauler/pkg/cache"
import (
"github.com/rancherfederal/hauler/internal/cache"
)
// Options defines options for Store
type Options func(*Store)
// WithCache initializes a Store with a cache.Cache, all content added to the Store will first be cached
func WithCache(c cache.Cache) Options {
return func(s *Store) {
s.cache = c
}
}
// WithDefaultRepository sets the default repository to use when none is specified (defaults to "library")
func WithDefaultRepository(repo string) Options {
return func(s *Store) {
s.DefaultRepository = repo
return func(b *Store) {
b.cache = c
}
}
+204
View File
@@ -0,0 +1,204 @@
package store
import (
"context"
"encoding/json"
"io"
"os"
"path/filepath"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/static"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"oras.land/oras-go/pkg/oras"
"github.com/rancherfederal/hauler/pkg/artifact"
"github.com/rancherfederal/hauler/pkg/consts"
)
type stager interface {
add(ctx context.Context, oci artifact.OCI, ref name.Reference) error
commit(ctx context.Context, b *Store) (ocispec.Descriptor, error)
flush(ctx context.Context) error
}
var _ stager = (*layout)(nil)
func newLayout() (*layout, error) {
tmpdir, err := os.MkdirTemp("", "hauler")
if err != nil {
return nil, err
}
return &layout{
root: tmpdir,
blobs: make(map[digest.Digest]v1.Layer),
}, nil
}
type layout struct {
root string
descs []ocispec.Descriptor
blobs map[digest.Digest]v1.Layer
}
func (l *layout) commit(ctx context.Context, b *Store) (ocispec.Descriptor, error) {
defer l.flush(ctx)
var g errgroup.Group
for d, blob := range l.blobs {
blob := blob
d := d
g.Go(func() error {
rc, err := blob.Compressed()
if err != nil {
return err
}
return l.writeBlob(d, rc)
})
}
if err := g.Wait(); err != nil {
return ocispec.Descriptor{}, err
}
idx := ocispec.Index{
Versioned: specs.Versioned{SchemaVersion: 2},
Manifests: l.descs,
Annotations: nil,
}
data, err := json.Marshal(idx)
if err != nil {
return ocispec.Descriptor{}, err
}
if err := os.WriteFile(l.path("index.json"), data, 0666); err != nil {
return ocispec.Descriptor{}, err
}
// src, err := content.NewOCI(l.path(""))
src, err := NewOCI(l.path(""))
if err != nil {
return ocispec.Descriptor{}, err
}
// dst, err := content.NewOCI(b.root)
dst, err := NewOCI(b.root)
if err != nil {
return ocispec.Descriptor{}, err
}
if err := src.LoadIndex(); err != nil {
return ocispec.Descriptor{}, err
}
ref := src.index.Manifests[0].Annotations[ocispec.AnnotationRefName]
// refs := src.ListReferences()
// if len(refs) > 1 {
// return ocispec.Descriptor{}, errors.Errorf("expected 1 reference to commit, got %d", len(refs))
// }
//
// var ref string
// for r := range refs {
// ref = r
// }
desc, err := oras.Copy(ctx, src, ref, dst, "",
oras.WithAdditionalCachedMediaTypes(consts.DockerManifestSchema2))
if err != nil {
return ocispec.Descriptor{}, errors.Wrap(err, "comitting staging layout")
}
// TODO: This is hacky but needed since oras.Copy between oci layouts doesn't update the index
// dst.AddReference(ref, desc)
if err := dst.SaveIndex(); err != nil {
return ocispec.Descriptor{}, err
}
return desc, err
}
func (l *layout) flush(ctx context.Context) error {
return os.RemoveAll(l.path(""))
}
func (l *layout) add(ctx context.Context, oci artifact.OCI, ref name.Reference) error {
m, err := oci.Manifest()
if err != nil {
return err
}
mdata, err := json.Marshal(m)
if err != nil {
return err
}
mdigest := digest.FromBytes(mdata)
l.blobs[mdigest] = static.NewLayer(mdata, m.MediaType)
cdata, err := oci.RawConfig()
if err != nil {
return err
}
l.blobs[digest.FromBytes(cdata)] = static.NewLayer(cdata, "")
layers, err := oci.Layers()
if err != nil {
return err
}
for _, layer := range layers {
h, err := layer.Digest()
if err != nil {
return err
}
d := digest.NewDigestFromHex(h.Algorithm, h.Hex)
l.blobs[d] = layer
}
mdesc := ocispec.Descriptor{
MediaType: oci.MediaType(),
Digest: mdigest,
Size: int64(len(mdata)),
Annotations: map[string]string{
ocispec.AnnotationRefName: ref.Name(),
},
URLs: nil,
Platform: nil,
}
l.descs = append(l.descs, mdesc)
return nil
}
func (l *layout) writeBlob(d digest.Digest, rc io.ReadCloser) error {
dir := l.path("blobs", d.Algorithm().String())
if err := os.MkdirAll(dir, os.ModePerm); err != nil && !os.IsExist(err) {
return err
}
file := filepath.Join(dir, d.Hex())
if _, err := os.Stat(file); err == nil {
return err
}
w, err := os.Create(file)
if err != nil {
return err
}
defer w.Close()
_, err = io.Copy(w, rc)
return err
}
func (l *layout) path(elem ...string) string {
complete := []string{string(l.root)}
return filepath.Join(append(complete, elem...)...)
}
+146 -175
View File
@@ -2,212 +2,183 @@ package store
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"regexp"
"strconv"
"time"
"os"
"path/filepath"
"github.com/distribution/distribution/v3/configuration"
dcontext "github.com/distribution/distribution/v3/context"
"github.com/distribution/distribution/v3/reference"
"github.com/distribution/distribution/v3/registry/client"
"github.com/distribution/distribution/v3/registry/handlers"
"github.com/google/go-containerregistry/pkg/name"
"github.com/sirupsen/logrus"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/pkg/content"
"oras.land/oras-go/pkg/oras"
"oras.land/oras-go/pkg/target"
// Init filesystem distribution storage driver
_ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
"github.com/rancherfederal/hauler/pkg/cache"
"github.com/rancherfederal/hauler/internal/cache"
"github.com/rancherfederal/hauler/pkg/artifact"
"github.com/rancherfederal/hauler/pkg/consts"
)
var (
httpRegex = regexp.MustCompile("https?://")
)
// Store is a simple wrapper around distribution/distribution to enable hauler's use case
type Store struct {
DataDir string
DefaultRepository string
config *configuration.Configuration
handler http.Handler
server *httptest.Server
cache cache.Cache
root string
store *content.OCI
cache cache.Cache
}
// NewStore creates a new registry store, designed strictly for use within haulers embedded operations and _not_ for serving
func NewStore(ctx context.Context, dataDir string, opts ...Options) *Store {
cfg := &configuration.Configuration{
Version: "0.1",
Storage: configuration.Storage{
"cache": configuration.Parameters{"blobdescriptor": "inmemory"},
"filesystem": configuration.Parameters{"rootdirectory": dataDir},
},
}
cfg.Log.Level = "panic"
cfg.HTTP.Headers = http.Header{"X-Content-Type-Options": []string{"nosniff"}}
handler := setupHandler(ctx, cfg)
s := &Store{
DataDir: dataDir,
config: cfg,
handler: handler,
}
for _, opt := range opts {
opt(s)
}
return s
}
// Open will create a new server and start it, it's up to the consumer to close it
func (s *Store) Open() *httptest.Server {
server := httptest.NewServer(s.handler)
s.server = server
return server
}
// Close stops the server
func (s *Store) Close() {
s.server.Close()
s.server = nil
return
}
// List will list all known content tags in the registry
// TODO: This fn is messy and needs cleanup, this is arguably easier with the catalog api as well
func (s *Store) List(ctx context.Context) ([]string, error) {
reg, err := client.NewRegistry(s.RegistryURL(), nil)
func NewStore(rootdir string, opts ...Options) (*Store, error) {
ociStore, err := content.NewOCI(rootdir)
if err != nil {
return nil, err
}
entries := make(map[string]reference.Named)
last := ""
for {
chunk := make([]string, 20) // randomly chosen number...
nf, err := reg.Repositories(ctx, chunk, last)
last = strconv.Itoa(nf)
for _, e := range chunk {
if e == "" {
continue
}
ref, err := reference.WithName(e)
if err != nil {
return nil, err
}
entries[e] = ref
}
if err == io.EOF {
break
}
b := &Store{
root: rootdir,
store: ociStore,
}
var refs []string
for ref, named := range entries {
repo, err := client.NewRepository(named, s.RegistryURL(), nil)
for _, opt := range opts {
opt(b)
}
return b, nil
}
// AddArtifact will add an artifact.OCI to the store
// The method to achieve this is to save artifact.OCI to a temporary directory in an OCI layout compatible form. Once
// saved, the entirety of the layout is copied to the store (which is just a registry). This allows us to not only use
// strict types to define generic content, but provides a processing pipeline suitable for extensibility. In the
// future we'll allow users to define their own content that must adhere either by artifact.OCI or simply an OCI layout.
func (s *Store) AddArtifact(ctx context.Context, oci artifact.OCI, reference name.Reference) (ocispec.Descriptor, error) {
stage, err := newLayout()
if err != nil {
return ocispec.Descriptor{}, err
}
if s.cache != nil {
cached := cache.Oci(oci, s.cache)
oci = cached
}
if err := stage.add(ctx, oci, reference); err != nil {
return ocispec.Descriptor{}, err
}
return stage.commit(ctx, s)
}
// AddCollection .
func (s *Store) AddCollection(ctx context.Context, coll artifact.Collection) ([]ocispec.Descriptor, error) {
cnts, err := coll.Contents()
if err != nil {
return nil, err
}
var descs []ocispec.Descriptor
for ref, oci := range cnts {
ds, err := s.AddArtifact(ctx, oci, ref)
if err != nil {
return nil, err
}
tsvc := repo.Tags(ctx)
ts, err := tsvc.All(ctx)
if err != nil {
continue
}
for _, t := range ts {
ref, err := name.ParseReference(ref, name.WithDefaultRegistry(""), name.WithDefaultTag(t))
if err != nil {
return nil, err
}
refs = append(refs, ref.Name())
}
descs = append(descs, ds)
}
return refs, nil
return descs, nil
}
// precheck checks whether server is appropriately started and errors if it's not
// used to safely run Store operations without fear of panics
func (s *Store) precheck() error {
if s.server == nil || s.server.URL == "" {
return fmt.Errorf("server is not started yet")
// Flush is a fancy name for delete-all-the-things, in this case it's as trivial as deleting oci-layout content
// This can be a highly destructive operation if the store's directory happens to be inline with other non-store contents
// To reduce the blast radius and likelihood of deleting things we don't own, Flush explicitly deletes oci-layout content only
func (s *Store) Flush(ctx context.Context) error {
blobs := filepath.Join(s.root, "blobs")
if err := os.RemoveAll(blobs); err != nil {
return err
}
index := filepath.Join(s.root, "index.json")
if err := os.RemoveAll(index); err != nil {
return err
}
layout := filepath.Join(s.root, "oci-layout")
if err := os.RemoveAll(layout); err != nil {
return err
}
return nil
}
func (s *Store) List(ctx context.Context) ([]ocispec.Descriptor, error) {
refs := s.store.ListReferences()
var descs []ocispec.Descriptor
for _, desc := range refs {
descs = append(descs, desc)
}
return descs, nil
}
func (s *Store) Get(ctx context.Context, to target.Target, reference string) error {
_, err := oras.Copy(ctx, s.store, reference, to, "",
oras.WithAdditionalCachedMediaTypes(consts.DockerManifestSchema2))
if err != nil {
return err
}
return nil
}
// Registry returns the registries URL without the protocol, suitable for image relocation operations
func (s *Store) Registry() string {
return httpRegex.ReplaceAllString(s.server.URL, "")
}
// RegistryURL returns the registries URL
func (s *Store) RegistryURL() string {
return s.server.URL
}
func alive(path string, handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == path {
w.Header().Set("Cache-Control", "no-cache")
w.WriteHeader(http.StatusOK)
return
}
handler.ServeHTTP(w, r)
})
}
// setupHandler will set up the registry handler
func setupHandler(ctx context.Context, config *configuration.Configuration) http.Handler {
ctx, _ = configureLogging(ctx, config)
app := handlers.NewApp(ctx, config)
app.RegisterHealthChecks()
handler := alive("/", app)
return handler
}
func configureLogging(ctx context.Context, cfg *configuration.Configuration) (context.Context, context.CancelFunc) {
logrus.SetLevel(logLevel(cfg.Log.Level))
formatter := cfg.Log.Formatter
if formatter == "" {
formatter = "text"
}
logrus.SetFormatter(&logrus.TextFormatter{
TimestampFormat: time.RFC3339Nano,
})
if len(cfg.Log.Fields) > 0 {
var fields []interface{}
for k := range cfg.Log.Fields {
fields = append(fields, k)
// Copy performs bulk copy operations on the stores oci layout to a provided target.Target
func (s *Store) Copy(ctx context.Context, to target.Target, toMapper func(string) (string, error)) error {
for ref := range s.store.ListReferences() {
toRef := ""
if toMapper != nil {
tr, err := toMapper(ref)
if err != nil {
return err
}
toRef = tr
}
ctx = dcontext.WithValues(ctx, cfg.Log.Fields)
ctx = dcontext.WithLogger(ctx, dcontext.GetLogger(ctx, fields...))
fmt.Println("copying to: ", toRef)
_, err := oras.Copy(ctx, s.store, ref, to, toRef,
oras.WithAdditionalCachedMediaTypes(consts.DockerManifestSchema2))
if err != nil {
return err
}
}
dcontext.SetDefaultLogger(dcontext.GetLogger(ctx))
return context.WithCancel(ctx)
return nil
}
func logLevel(level configuration.Loglevel) logrus.Level {
l, err := logrus.ParseLevel(string(level))
// Identify is a helper function that will identify the content type given a descriptor
func (s *Store) Identify(ctx context.Context, desc ocispec.Descriptor) string {
rc, err := s.store.Fetch(ctx, desc)
if err != nil {
l = logrus.InfoLevel
logrus.Warnf("error parsing log level %q: %v, using %q", level, err, l)
return ""
}
return l
defer rc.Close()
m := struct {
Config struct {
MediaType string `json:"mediaType"`
} `json:"config"`
}{}
if err := json.NewDecoder(rc).Decode(&m); err != nil {
return ""
}
return m.Config.MediaType
}
// RelocateReference returns a name.Reference given a reference and registry
func RelocateReference(reference string, registry string) (name.Reference, error) {
ref, err := name.ParseReference(reference)
if err != nil {
return nil, err
}
relocated, err := name.ParseReference(ref.Context().RepositoryStr(), name.WithDefaultRegistry(registry))
if err != nil {
return nil, err
}
if _, err := name.NewDigest(ref.Name()); err == nil {
return relocated.Context().Digest(ref.Identifier()), nil
}
return relocated.Context().Tag(ref.Identifier()), nil
}
+33 -52
View File
@@ -1,87 +1,68 @@
package store
package store_test
import (
"context"
"os"
"reflect"
"testing"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/random"
"github.com/google/go-containerregistry/pkg/v1/remote"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/rancherfederal/hauler/pkg/artifact"
"github.com/rancherfederal/hauler/pkg/content/image"
"github.com/rancherfederal/hauler/pkg/store"
)
func TestStore_List(t *testing.T) {
func TestStore_AddArtifact(t *testing.T) {
ctx := context.Background()
s, err := testStore(ctx)
tmpdir, err := os.MkdirTemp("", "hauler")
if err != nil {
t.Fatal(err)
}
s.Open()
defer s.Close()
s, err := store.NewStore(tmpdir)
if err != nil {
t.Fatal(err)
}
r := randomImage(t)
addImageToStore(t, s, r, "hauler/tester:latest")
addImageToStore(t, s, r, "hauler/tester:non")
addImageToStore(t, s, r, "other/ns:more")
addImageToStore(t, s, r, "unique/donkey:v1.2.2")
img, _ := image.NewImage("ghcr.io/stefanprodan/podinfo:6.0.3")
ref, _ := name.ParseReference("ghcr.io/stephanprodan/podinfo:6.0.3")
type args struct {
ctx context.Context
ctx context.Context
oci artifact.OCI
reference name.Reference
}
tests := []struct {
name string
args args
want v1.Descriptor
wantErr bool
}{
{
name: "should list",
args: args{},
name: "should add artifact",
args: args{
ctx: ctx,
oci: img,
reference: ref,
},
want: v1.Descriptor{},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
refs, err := s.List(ctx)
if (err != nil) != tt.wantErr {
t.Errorf("List() error = %v, wantErr %v", err, tt.wantErr)
}
// TODO: Make this more robust
if len(refs) != 4 {
t.Errorf("Expected 4, got %d", len(refs))
got, err := s.AddArtifact(tt.args.ctx, tt.args.oci, tt.args.reference)
if (err != nil) != tt.wantErr {
t.Errorf("AddArtifact() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("AddArtifact() got = %v, want %v", got, tt.want)
}
})
}
}
func testStore(ctx context.Context) (*Store, error) {
tmpdir, err := os.MkdirTemp("", "hauler")
if err != nil {
return nil, err
}
s := NewStore(ctx, tmpdir)
return s, nil
}
func randomImage(t *testing.T) v1.Image {
r, err := random.Image(1024, 3)
if err != nil {
t.Fatalf("random.Image() = %v", err)
}
return r
}
func addImageToStore(t *testing.T, s *Store, image v1.Image, reference string) {
ref, err := name.ParseReference(reference, name.WithDefaultRegistry(s.Registry()))
if err != nil {
t.Error(err)
}
if err := remote.Write(ref, image); err != nil {
t.Error(err)
}
}
+14 -2
View File
@@ -7,5 +7,17 @@ spec:
charts:
# charts are also fetched and served as OCI content (currently experimental in helm)
# HELM_EXPERIMENTAL_OCI=1 helm chart pull <hauler-registry>/loki:2.6.2
- name: loki
repoURL: https://grafana.github.io/helm-charts
# - name: loki
# repoURL: https://grafana.github.io/helm-charts
# - name: longhorn
# repoURL: https://charts.longhorn.io
# - name: cert-manager
# repoURL: https://charts.jetstack.io
# version: v1.6.1
# extraImages:
# - ref: quay.io/jetstack/cert-manager-cainjector:v1.6.1
- name: podinfo
repoURL: https://stefanprodan.github.io/podinfo
+1 -3
View File
@@ -15,9 +15,7 @@ spec:
- ref: "https://github.com/k3s-io/k3s/releases/download/v1.22.2%2Bk3s2/k3s-images.txt"
# when filenames are not appropriate, a name should be specified
# this will still work, but default to a filename of "get.k3s.io"
- ref: https://get.k3s.io
name: get-k3s.sh
- ref: https://get.k3s.io?filename=get-k3s.sh
---
apiVersion: content.hauler.cattle.io/v1alpha1