From 2d930b5653c187c2bd8bf156013cb2723bf31298 Mon Sep 17 00:00:00 2001 From: will <30413278+wcrum@users.noreply.github.com> Date: Sun, 25 Aug 2024 14:16:37 -0600 Subject: [PATCH] feat-273: TLS Flags (#303) * fix: move constant and flags to prevent loop * feat: add tls cert to serve * fix: add tls cli description * fix: remove unnecessary code * small updates/fixed unit test errors * fix: migrate all flags, use exported vars * fix: standardize to AddFlags --------- Signed-off-by: will <30413278+wcrum@users.noreply.github.com> Co-authored-by: Zack Brady --- .gitignore | 5 +- cmd/hauler/cli/cli.go | 11 +-- cmd/hauler/cli/login.go | 20 +--- cmd/hauler/cli/store.go | 35 +++---- cmd/hauler/cli/store/add.go | 51 +--------- cmd/hauler/cli/store/copy.go | 22 +---- cmd/hauler/cli/store/extract.go | 15 +-- cmd/hauler/cli/store/info.go | 25 +---- cmd/hauler/cli/store/load.go | 19 +--- cmd/hauler/cli/store/save.go | 15 +-- cmd/hauler/cli/store/serve.go | 93 ++++--------------- cmd/hauler/cli/store/sync.go | 29 +----- internal/flags/add.go | 49 ++++++++++ internal/flags/cli.go | 5 + internal/flags/copy.go | 21 +++++ internal/flags/extract.go | 14 +++ internal/flags/info.go | 22 +++++ internal/flags/load.go | 18 ++++ internal/flags/login.go | 16 ++++ internal/flags/save.go | 14 +++ internal/flags/serve.go | 87 +++++++++++++++++ .../store/flags.go => internal/flags/store.go | 18 ++-- internal/flags/sync.go | 24 +++++ internal/server/file.go | 16 +--- internal/server/server.go | 1 + pkg/consts/consts.go | 2 + 26 files changed, 350 insertions(+), 297 deletions(-) create mode 100644 internal/flags/add.go create mode 100644 internal/flags/cli.go create mode 100644 internal/flags/copy.go create mode 100644 internal/flags/extract.go create mode 100644 internal/flags/info.go create mode 100644 internal/flags/load.go create mode 100644 internal/flags/login.go create mode 100644 internal/flags/save.go create mode 100644 internal/flags/serve.go rename cmd/hauler/cli/store/flags.go => internal/flags/store.go (70%) create mode 100644 internal/flags/sync.go diff --git a/.gitignore b/.gitignore index da798ac7..cd7b62a4 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ airgap-scp.sh dist/ tmp/ bin/ -/store/ -/registry/ +store/ +registry/ +fileserver/ cmd/hauler/binaries diff --git a/cmd/hauler/cli/cli.go b/cmd/hauler/cli/cli.go index 0ae01902..5cc4a77f 100644 --- a/cmd/hauler/cli/cli.go +++ b/cmd/hauler/cli/cli.go @@ -3,14 +3,11 @@ package cli import ( "github.com/spf13/cobra" + "github.com/rancherfederal/hauler/internal/flags" "github.com/rancherfederal/hauler/pkg/log" ) -type rootOpts struct { - logLevel string -} - -var ro = &rootOpts{} +var ro = &flags.CliRootOpts{} func New() *cobra.Command { cmd := &cobra.Command{ @@ -18,7 +15,7 @@ func New() *cobra.Command { Short: "Airgap Swiss Army Knife", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { l := log.FromContext(cmd.Context()) - l.SetLevel(ro.logLevel) + l.SetLevel(ro.LogLevel) l.Debugf("running cli command [%s]", cmd.CommandPath()) return nil }, @@ -28,7 +25,7 @@ func New() *cobra.Command { } pf := cmd.PersistentFlags() - pf.StringVarP(&ro.logLevel, "log-level", "l", "info", "") + pf.StringVarP(&ro.LogLevel, "log-level", "l", "info", "") // Add subcommands addLogin(cmd) diff --git a/cmd/hauler/cli/login.go b/cmd/hauler/cli/login.go index 7ca6f872..70e09170 100644 --- a/cmd/hauler/cli/login.go +++ b/cmd/hauler/cli/login.go @@ -10,24 +10,12 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/pkg/content" + "github.com/rancherfederal/hauler/internal/flags" "github.com/rancherfederal/hauler/pkg/cosign" ) -type Opts struct { - Username string - Password string - PasswordStdin bool -} - -func (o *Opts) AddArgs(cmd *cobra.Command) { - f := cmd.Flags() - f.StringVarP(&o.Username, "username", "u", "", "Username to use for authentication") - f.StringVarP(&o.Password, "password", "p", "", "Password to use for authentication") - f.BoolVar(&o.PasswordStdin, "password-stdin", false, "Password to use for authentication (from stdin)") -} - func addLogin(parent *cobra.Command) { - o := &Opts{} + o := &flags.LoginOpts{} cmd := &cobra.Command{ Use: "login", @@ -55,12 +43,12 @@ hauler login reg.example.com -u bob -p haulin`, return login(ctx, o, arg[0]) }, } - o.AddArgs(cmd) + o.AddFlags(cmd) parent.AddCommand(cmd) } -func login(ctx context.Context, o *Opts, registry string) error { +func login(ctx context.Context, o *flags.LoginOpts, registry string) error { ropts := content.RegistryOptions{ Username: o.Username, Password: o.Password, diff --git a/cmd/hauler/cli/store.go b/cmd/hauler/cli/store.go index 2f0c6f61..dd3d0b7a 100644 --- a/cmd/hauler/cli/store.go +++ b/cmd/hauler/cli/store.go @@ -7,9 +7,10 @@ import ( "helm.sh/helm/v3/pkg/action" "github.com/rancherfederal/hauler/cmd/hauler/cli/store" + "github.com/rancherfederal/hauler/internal/flags" ) -var rootStoreOpts = &store.RootOpts{} +var rootStoreOpts = &flags.StoreRootOpts{} func addStore(parent *cobra.Command) { cmd := &cobra.Command{ @@ -20,7 +21,7 @@ func addStore(parent *cobra.Command) { return cmd.Help() }, } - rootStoreOpts.AddArgs(cmd) + rootStoreOpts.AddFlags(cmd) cmd.AddCommand( addStoreSync(), @@ -39,7 +40,7 @@ func addStore(parent *cobra.Command) { } func addStoreExtract() *cobra.Command { - o := &store.ExtractOpts{RootOpts: rootStoreOpts} + o := &flags.ExtractOpts{StoreRootOpts: rootStoreOpts} cmd := &cobra.Command{ Use: "extract", @@ -57,13 +58,13 @@ func addStoreExtract() *cobra.Command { return store.ExtractCmd(ctx, o, s, args[0]) }, } - o.AddArgs(cmd) + o.AddFlags(cmd) return cmd } func addStoreSync() *cobra.Command { - o := &store.SyncOpts{RootOpts: rootStoreOpts} + o := &flags.SyncOpts{StoreRootOpts: rootStoreOpts} cmd := &cobra.Command{ Use: "sync", @@ -85,7 +86,7 @@ func addStoreSync() *cobra.Command { } func addStoreLoad() *cobra.Command { - o := &store.LoadOpts{RootOpts: rootStoreOpts} + o := &flags.LoadOpts{StoreRootOpts: rootStoreOpts} cmd := &cobra.Command{ Use: "load", @@ -126,7 +127,7 @@ func addStoreServe() *cobra.Command { // RegistryCmd serves the embedded registry func addStoreServeRegistry() *cobra.Command { - o := &store.ServeRegistryOpts{RootOpts: rootStoreOpts} + o := &flags.ServeRegistryOpts{StoreRootOpts: rootStoreOpts} cmd := &cobra.Command{ Use: "registry", Short: "Serve the embedded registry", @@ -149,7 +150,7 @@ func addStoreServeRegistry() *cobra.Command { // FileServerCmd serves the file server func addStoreServeFiles() *cobra.Command { - o := &store.ServeFilesOpts{RootOpts: rootStoreOpts} + o := &flags.ServeFilesOpts{StoreRootOpts: rootStoreOpts} cmd := &cobra.Command{ Use: "fileserver", Short: "Serve the file server", @@ -171,7 +172,7 @@ func addStoreServeFiles() *cobra.Command { } func addStoreSave() *cobra.Command { - o := &store.SaveOpts{RootOpts: rootStoreOpts} + o := &flags.SaveOpts{StoreRootOpts: rootStoreOpts} cmd := &cobra.Command{ Use: "save", @@ -189,13 +190,13 @@ func addStoreSave() *cobra.Command { return store.SaveCmd(ctx, o, o.FileName) }, } - o.AddArgs(cmd) + o.AddFlags(cmd) return cmd } func addStoreInfo() *cobra.Command { - o := &store.InfoOpts{RootOpts: rootStoreOpts} + o := &flags.InfoOpts{StoreRootOpts: rootStoreOpts} var allowedValues = []string{"image", "chart", "file", "sigs", "atts", "sbom", "all"} @@ -226,7 +227,7 @@ func addStoreInfo() *cobra.Command { } func addStoreCopy() *cobra.Command { - o := &store.CopyOpts{RootOpts: rootStoreOpts} + o := &flags.CopyOpts{StoreRootOpts: rootStoreOpts} cmd := &cobra.Command{ Use: "copy", @@ -267,7 +268,7 @@ func addStoreAdd() *cobra.Command { } func addStoreAddFile() *cobra.Command { - o := &store.AddFileOpts{RootOpts: rootStoreOpts} + o := &flags.AddFileOpts{StoreRootOpts: rootStoreOpts} cmd := &cobra.Command{ Use: "file", @@ -290,7 +291,7 @@ func addStoreAddFile() *cobra.Command { } func addStoreAddImage() *cobra.Command { - o := &store.AddImageOpts{RootOpts: rootStoreOpts} + o := &flags.AddImageOpts{StoreRootOpts: rootStoreOpts} cmd := &cobra.Command{ Use: "image", @@ -313,9 +314,9 @@ func addStoreAddImage() *cobra.Command { } func addStoreAddChart() *cobra.Command { - o := &store.AddChartOpts{ - RootOpts: rootStoreOpts, - ChartOpts: &action.ChartPathOptions{}, + o := &flags.AddChartOpts{ + StoreRootOpts: rootStoreOpts, + ChartOpts: &action.ChartPathOptions{}, } cmd := &cobra.Command{ diff --git a/cmd/hauler/cli/store/add.go b/cmd/hauler/cli/store/add.go index e640a39a..8029b4e5 100644 --- a/cmd/hauler/cli/store/add.go +++ b/cmd/hauler/cli/store/add.go @@ -5,9 +5,9 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/rancherfederal/hauler/pkg/artifacts/file/getter" - "github.com/spf13/cobra" "helm.sh/helm/v3/pkg/action" + "github.com/rancherfederal/hauler/internal/flags" "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" "github.com/rancherfederal/hauler/pkg/artifacts/file" "github.com/rancherfederal/hauler/pkg/content/chart" @@ -17,17 +17,7 @@ import ( "github.com/rancherfederal/hauler/pkg/store" ) -type AddFileOpts struct { - *RootOpts - Name string -} - -func (o *AddFileOpts) AddFlags(cmd *cobra.Command) { - f := cmd.Flags() - f.StringVarP(&o.Name, "name", "n", "", "(Optional) Name to assign to file in store") -} - -func AddFileCmd(ctx context.Context, o *AddFileOpts, s *store.Layout, reference string) error { +func AddFileCmd(ctx context.Context, o *flags.AddFileOpts, s *store.Layout, reference string) error { cfg := v1alpha1.File{ Path: reference, } @@ -61,20 +51,7 @@ func storeFile(ctx context.Context, s *store.Layout, fi v1alpha1.File) error { return nil } -type AddImageOpts struct { - *RootOpts - Name string - Key string - Platform string -} - -func (o *AddImageOpts) AddFlags(cmd *cobra.Command) { - f := cmd.Flags() - f.StringVarP(&o.Key, "key", "k", "", "(Optional) Path to the key for digital signature verification") - f.StringVarP(&o.Platform, "platform", "p", "", "(Optional) Specific platform to save. i.e. linux/amd64. Defaults to all if flag is omitted.") -} - -func AddImageCmd(ctx context.Context, o *AddImageOpts, s *store.Layout, reference string) error { +func AddImageCmd(ctx context.Context, o *flags.AddImageOpts, s *store.Layout, reference string) error { l := log.FromContext(ctx) cfg := v1alpha1.Image{ Name: reference, @@ -111,27 +88,7 @@ func storeImage(ctx context.Context, s *store.Layout, i v1alpha1.Image, platform return nil } -type AddChartOpts struct { - *RootOpts - - ChartOpts *action.ChartPathOptions -} - -func (o *AddChartOpts) AddFlags(cmd *cobra.Command) { - f := cmd.Flags() - - f.StringVar(&o.ChartOpts.RepoURL, "repo", "", "chart repository url where to locate the requested chart") - f.StringVar(&o.ChartOpts.Version, "version", "", "specify a version constraint for the chart version to use. This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0). If this is not specified, the latest version is used") - f.BoolVar(&o.ChartOpts.Verify, "verify", false, "verify the package before using it") - f.StringVar(&o.ChartOpts.Username, "username", "", "chart repository username where to locate the requested chart") - f.StringVar(&o.ChartOpts.Password, "password", "", "chart repository password where to locate the requested chart") - f.StringVar(&o.ChartOpts.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") - f.StringVar(&o.ChartOpts.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") - f.BoolVar(&o.ChartOpts.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") - f.StringVar(&o.ChartOpts.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") -} - -func AddChartCmd(ctx context.Context, o *AddChartOpts, s *store.Layout, chartName string) error { +func AddChartCmd(ctx context.Context, o *flags.AddChartOpts, s *store.Layout, chartName string) error { // TODO: Reduce duplicates between api chart and upstream helm opts cfg := v1alpha1.Chart{ Name: chartName, diff --git a/cmd/hauler/cli/store/copy.go b/cmd/hauler/cli/store/copy.go index 8694e85e..ee72ed64 100644 --- a/cmd/hauler/cli/store/copy.go +++ b/cmd/hauler/cli/store/copy.go @@ -5,33 +5,15 @@ import ( "fmt" "strings" - "github.com/spf13/cobra" "oras.land/oras-go/pkg/content" + "github.com/rancherfederal/hauler/internal/flags" "github.com/rancherfederal/hauler/pkg/cosign" "github.com/rancherfederal/hauler/pkg/log" "github.com/rancherfederal/hauler/pkg/store" ) -type CopyOpts struct { - *RootOpts - - Username string - Password string - Insecure bool - PlainHTTP bool -} - -func (o *CopyOpts) AddFlags(cmd *cobra.Command) { - f := cmd.Flags() - - f.StringVarP(&o.Username, "username", "u", "", "Username when copying to an authenticated remote registry") - f.StringVarP(&o.Password, "password", "p", "", "Password when copying to an authenticated remote registry") - f.BoolVar(&o.Insecure, "insecure", false, "Toggle allowing insecure connections when copying to a remote registry") - f.BoolVar(&o.PlainHTTP, "plain-http", false, "Toggle allowing plain http connections when copying to a remote registry") -} - -func CopyCmd(ctx context.Context, o *CopyOpts, s *store.Layout, targetRef string) error { +func CopyCmd(ctx context.Context, o *flags.CopyOpts, s *store.Layout, targetRef string) error { l := log.FromContext(ctx) components := strings.SplitN(targetRef, "://", 2) diff --git a/cmd/hauler/cli/store/extract.go b/cmd/hauler/cli/store/extract.go index 835b70b6..298609b1 100644 --- a/cmd/hauler/cli/store/extract.go +++ b/cmd/hauler/cli/store/extract.go @@ -7,26 +7,15 @@ import ( "strings" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/spf13/cobra" + "github.com/rancherfederal/hauler/internal/flags" "github.com/rancherfederal/hauler/internal/mapper" "github.com/rancherfederal/hauler/pkg/log" "github.com/rancherfederal/hauler/pkg/reference" "github.com/rancherfederal/hauler/pkg/store" ) -type ExtractOpts struct { - *RootOpts - DestinationDir string -} - -func (o *ExtractOpts) AddArgs(cmd *cobra.Command) { - f := cmd.Flags() - - f.StringVarP(&o.DestinationDir, "output", "o", "", "Directory to save contents to (defaults to current directory)") -} - -func ExtractCmd(ctx context.Context, o *ExtractOpts, s *store.Layout, ref string) error { +func ExtractCmd(ctx context.Context, o *flags.ExtractOpts, s *store.Layout, ref string) error { l := log.FromContext(ctx) r, err := reference.Parse(ref) diff --git a/cmd/hauler/cli/store/info.go b/cmd/hauler/cli/store/info.go index 91c53fa0..b5c38830 100644 --- a/cmd/hauler/cli/store/info.go +++ b/cmd/hauler/cli/store/info.go @@ -9,33 +9,14 @@ import ( "github.com/olekukonko/tablewriter" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/spf13/cobra" + "github.com/rancherfederal/hauler/internal/flags" "github.com/rancherfederal/hauler/pkg/consts" "github.com/rancherfederal/hauler/pkg/reference" "github.com/rancherfederal/hauler/pkg/store" ) -type InfoOpts struct { - *RootOpts - - OutputFormat string - TypeFilter string - SizeUnit string - ListRepos bool -} - -func (o *InfoOpts) AddFlags(cmd *cobra.Command) { - f := cmd.Flags() - - f.StringVarP(&o.OutputFormat, "output", "o", "table", "Output format (table, json)") - f.StringVarP(&o.TypeFilter, "type", "t", "all", "Filter on type (image, chart, file, sigs, atts, sbom)") - f.BoolVar(&o.ListRepos, "list-repos", false, "List all repository names") - - // TODO: Regex/globbing -} - -func InfoCmd(ctx context.Context, o *InfoOpts, s *store.Layout) error { +func InfoCmd(ctx context.Context, o *flags.InfoOpts, s *store.Layout) error { var items []item if err := s.Walk(func(ref string, desc ocispec.Descriptor) error { if _, ok := desc.Annotations[ocispec.AnnotationRefName]; !ok { @@ -229,7 +210,7 @@ func (a byReferenceAndArch) Less(i, j int) bool { return a[i].Reference < a[j].Reference } -func newItem(s *store.Layout, desc ocispec.Descriptor, m ocispec.Manifest, plat string, o *InfoOpts) item { +func newItem(s *store.Layout, desc ocispec.Descriptor, m ocispec.Manifest, plat string, o *flags.InfoOpts) item { var size int64 = 0 for _, l := range m.Layers { size += l.Size diff --git a/cmd/hauler/cli/store/load.go b/cmd/hauler/cli/store/load.go index 22c3dd61..0dd1fe3b 100644 --- a/cmd/hauler/cli/store/load.go +++ b/cmd/hauler/cli/store/load.go @@ -5,31 +5,16 @@ import ( "os" "github.com/mholt/archiver/v3" - "github.com/spf13/cobra" + "github.com/rancherfederal/hauler/internal/flags" "github.com/rancherfederal/hauler/pkg/content" "github.com/rancherfederal/hauler/pkg/log" "github.com/rancherfederal/hauler/pkg/store" ) -type LoadOpts struct { - *RootOpts - TempOverride string -} - -func (o *LoadOpts) AddFlags(cmd *cobra.Command) { - f := cmd.Flags() - - // On Unix systems, the default is $TMPDIR if non-empty, else /tmp. - // On Windows, the default is GetTempPath, returning the first non-empty - // value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory. - // On Plan 9, the default is /tmp. - f.StringVarP(&o.TempOverride, "tempdir", "t", "", "overrides the default directory for temporary files, as returned by your OS.") -} - // LoadCmd // TODO: Just use mholt/archiver for now, even though we don't need most of it -func LoadCmd(ctx context.Context, o *LoadOpts, archiveRefs ...string) error { +func LoadCmd(ctx context.Context, o *flags.LoadOpts, archiveRefs ...string) error { l := log.FromContext(ctx) for _, archiveRef := range archiveRefs { diff --git a/cmd/hauler/cli/store/save.go b/cmd/hauler/cli/store/save.go index c3d10195..0882d86d 100644 --- a/cmd/hauler/cli/store/save.go +++ b/cmd/hauler/cli/store/save.go @@ -6,25 +6,14 @@ import ( "path/filepath" "github.com/mholt/archiver/v3" - "github.com/spf13/cobra" + "github.com/rancherfederal/hauler/internal/flags" "github.com/rancherfederal/hauler/pkg/log" ) -type SaveOpts struct { - *RootOpts - FileName string -} - -func (o *SaveOpts) AddArgs(cmd *cobra.Command) { - f := cmd.Flags() - - f.StringVarP(&o.FileName, "filename", "f", "haul.tar.zst", "Name of archive") -} - // SaveCmd // TODO: Just use mholt/archiver for now, even though we don't need most of it -func SaveCmd(ctx context.Context, o *SaveOpts, outputFile string) error { +func SaveCmd(ctx context.Context, o *flags.SaveOpts, outputFile string) error { l := log.FromContext(ctx) // TODO: Support more formats? diff --git a/cmd/hauler/cli/store/serve.go b/cmd/hauler/cli/store/serve.go index 44638ff0..e77624be 100644 --- a/cmd/hauler/cli/store/serve.go +++ b/cmd/hauler/cli/store/serve.go @@ -2,8 +2,6 @@ package store import ( "context" - "fmt" - "net/http" "os" "github.com/distribution/distribution/v3/configuration" @@ -12,32 +10,14 @@ import ( _ "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/internal/flags" "github.com/rancherfederal/hauler/internal/server" "github.com/rancherfederal/hauler/pkg/log" "github.com/rancherfederal/hauler/pkg/store" ) -type ServeRegistryOpts struct { - *RootOpts - - Port int - RootDir string - ConfigFile string - ReadOnly bool -} - -func (o *ServeRegistryOpts) 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 backend. Defaults to $PWD/registry") - f.StringVarP(&o.ConfigFile, "config", "c", "", "Path to a config file, will override all other configs") - f.BoolVar(&o.ReadOnly, "readonly", true, "Run the registry as readonly.") -} - -func ServeRegistryCmd(ctx context.Context, o *ServeRegistryOpts, s *store.Layout) error { +func ServeRegistryCmd(ctx context.Context, o *flags.ServeRegistryOpts, s *store.Layout) error { l := log.FromContext(ctx) ctx = dcontext.WithVersion(ctx, version.Version) @@ -46,14 +26,14 @@ func ServeRegistryCmd(ctx context.Context, o *ServeRegistryOpts, s *store.Layout return err } - opts := &CopyOpts{} + opts := &flags.CopyOpts{} if err := CopyCmd(ctx, opts, s, "registry://"+tr.Registry()); err != nil { return err } tr.Close() - cfg := o.defaultRegistryConfig() + cfg := o.DefaultRegistryConfig() if o.ConfigFile != "" { ucfg, err := loadConfig(o.ConfigFile) if err != nil { @@ -75,45 +55,30 @@ func ServeRegistryCmd(ctx context.Context, o *ServeRegistryOpts, s *store.Layout return nil } -type ServeFilesOpts struct { - *RootOpts - - Port int - Timeout int - RootDir string -} - -func (o *ServeFilesOpts) AddFlags(cmd *cobra.Command) { - f := cmd.Flags() - - f.IntVarP(&o.Port, "port", "p", 8080, "Port to listen on.") - f.IntVarP(&o.Timeout, "timeout", "t", 60, "Set the http request timeout duration in seconds for both reads and write.") - f.StringVar(&o.RootDir, "directory", "fileserver", "Directory to use for backend. Defaults to $PWD/fileserver") -} - -func ServeFilesCmd(ctx context.Context, o *ServeFilesOpts, s *store.Layout) error { +func ServeFilesCmd(ctx context.Context, o *flags.ServeFilesOpts, s *store.Layout) error { l := log.FromContext(ctx) ctx = dcontext.WithVersion(ctx, version.Version) - opts := &CopyOpts{} + opts := &flags.CopyOpts{} if err := CopyCmd(ctx, opts, s, "dir://"+o.RootDir); err != nil { return err } - cfg := server.FileConfig{ - Root: o.RootDir, - Port: o.Port, - Timeout: o.Timeout, - } - - f, err := server.NewFile(ctx, cfg) + f, err := server.NewFile(ctx, *o) if err != nil { return err } - l.Infof("starting file server on port [%d]", o.Port) - if err := f.ListenAndServe(); err != nil { - return err + if o.TLSCert != "" && o.TLSKey != "" { + l.Infof("starting file server with tls on port [%d]", o.Port) + if err := f.ListenAndServeTLS(o.TLSCert, o.TLSKey); err != nil { + return err + } + } else { + l.Infof("starting file server on port [%d]", o.Port) + if err := f.ListenAndServe(); err != nil { + return err + } } return nil @@ -127,27 +92,3 @@ func loadConfig(filename string) (*configuration.Configuration, error) { return configuration.Parse(f) } - -func (o *ServeRegistryOpts) defaultRegistryConfig() *configuration.Configuration { - cfg := &configuration.Configuration{ - Version: "0.1", - Storage: configuration.Storage{ - "cache": configuration.Parameters{"blobdescriptor": "inmemory"}, - "filesystem": configuration.Parameters{"rootdirectory": o.RootDir}, - "maintenance": configuration.Parameters{ - "readonly": map[any]any{"enabled": o.ReadOnly}, - }, - }, - } - - // Add validation configuration - cfg.Validation.Manifests.URLs.Allow = []string{".+"} - - 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 -} diff --git a/cmd/hauler/cli/store/sync.go b/cmd/hauler/cli/store/sync.go index 3c5fc3ec..2763e6c6 100644 --- a/cmd/hauler/cli/store/sync.go +++ b/cmd/hauler/cli/store/sync.go @@ -9,10 +9,10 @@ import ( "strings" "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" "helm.sh/helm/v3/pkg/action" "k8s.io/apimachinery/pkg/util/yaml" + "github.com/rancherfederal/hauler/internal/flags" "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" tchart "github.com/rancherfederal/hauler/pkg/collection/chart" "github.com/rancherfederal/hauler/pkg/collection/imagetxt" @@ -25,28 +25,7 @@ import ( "github.com/rancherfederal/hauler/pkg/store" ) -type SyncOpts struct { - *RootOpts - ContentFiles []string - Key string - Products []string - Platform string - Registry string - ProductRegistry string -} - -func (o *SyncOpts) AddFlags(cmd *cobra.Command) { - f := cmd.Flags() - - f.StringSliceVarP(&o.ContentFiles, "files", "f", []string{}, "Path(s) to local content files (Manifests). i.e. '--files ./rke2-files.yml") - f.StringVarP(&o.Key, "key", "k", "", "(Optional) Path to the key for signature verification") - f.StringSliceVar(&o.Products, "products", []string{}, "(Optional) Feature for RGS Carbide customers to fetch collections and content from the Carbide Registry. i.e. '--product rancher=v2.8.5,rke2=v1.28.11+rke2r1'") - f.StringVarP(&o.Platform, "platform", "p", "", "(Optional) Specific platform to save. i.e. linux/amd64. Defaults to all if flag is omitted.") - f.StringVarP(&o.Registry, "registry", "r", "", "(Optional) Default pull registry for image refs that are not specifying a registry name.") - f.StringVarP(&o.ProductRegistry, "product-registry", "c", "", "(Optional) Specific Product Registry to use. Defaults to RGS Carbide Registry (rgcrprod.azurecr.us).") -} - -func SyncCmd(ctx context.Context, o *SyncOpts, s *store.Layout) error { +func SyncCmd(ctx context.Context, o *flags.SyncOpts, s *store.Layout) error { l := log.FromContext(ctx) // if passed products, check for a remote manifest to retrieve and use. @@ -70,7 +49,7 @@ func SyncCmd(ctx context.Context, o *SyncOpts, s *store.Layout) error { if err != nil { return err } - err = ExtractCmd(ctx, &ExtractOpts{RootOpts: o.RootOpts}, s, fmt.Sprintf("hauler/%s-manifest.yaml:%s", parts[0], tag)) + err = ExtractCmd(ctx, &flags.ExtractOpts{StoreRootOpts: o.StoreRootOpts}, s, fmt.Sprintf("hauler/%s-manifest.yaml:%s", parts[0], tag)) if err != nil { return err } @@ -102,7 +81,7 @@ func SyncCmd(ctx context.Context, o *SyncOpts, s *store.Layout) error { return nil } -func processContent(ctx context.Context, fi *os.File, o *SyncOpts, s *store.Layout) error { +func processContent(ctx context.Context, fi *os.File, o *flags.SyncOpts, s *store.Layout) error { l := log.FromContext(ctx) reader := yaml.NewYAMLReader(bufio.NewReader(fi)) diff --git a/internal/flags/add.go b/internal/flags/add.go new file mode 100644 index 00000000..f4fb138c --- /dev/null +++ b/internal/flags/add.go @@ -0,0 +1,49 @@ +package flags + +import ( + "github.com/spf13/cobra" + "helm.sh/helm/v3/pkg/action" +) + +type AddImageOpts struct { + *StoreRootOpts + Name string + Key string + Platform string +} + +func (o *AddImageOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + f.StringVarP(&o.Key, "key", "k", "", "(Optional) Path to the key for digital signature verification") + f.StringVarP(&o.Platform, "platform", "p", "", "(Optional) Specific platform to save. i.e. linux/amd64. Defaults to all if flag is omitted.") +} + +type AddFileOpts struct { + *StoreRootOpts + Name string +} + +func (o *AddFileOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + f.StringVarP(&o.Name, "name", "n", "", "(Optional) Name to assign to file in store") +} + +type AddChartOpts struct { + *StoreRootOpts + + ChartOpts *action.ChartPathOptions +} + +func (o *AddChartOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + + f.StringVar(&o.ChartOpts.RepoURL, "repo", "", "chart repository url where to locate the requested chart") + f.StringVar(&o.ChartOpts.Version, "version", "", "specify a version constraint for the chart version to use. This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0). If this is not specified, the latest version is used") + f.BoolVar(&o.ChartOpts.Verify, "verify", false, "verify the package before using it") + f.StringVar(&o.ChartOpts.Username, "username", "", "chart repository username where to locate the requested chart") + f.StringVar(&o.ChartOpts.Password, "password", "", "chart repository password where to locate the requested chart") + f.StringVar(&o.ChartOpts.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&o.ChartOpts.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.BoolVar(&o.ChartOpts.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") + f.StringVar(&o.ChartOpts.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") +} diff --git a/internal/flags/cli.go b/internal/flags/cli.go new file mode 100644 index 00000000..54cb01f1 --- /dev/null +++ b/internal/flags/cli.go @@ -0,0 +1,5 @@ +package flags + +type CliRootOpts struct { + LogLevel string +} diff --git a/internal/flags/copy.go b/internal/flags/copy.go new file mode 100644 index 00000000..b6848cea --- /dev/null +++ b/internal/flags/copy.go @@ -0,0 +1,21 @@ +package flags + +import "github.com/spf13/cobra" + +type CopyOpts struct { + *StoreRootOpts + + Username string + Password string + Insecure bool + PlainHTTP bool +} + +func (o *CopyOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + + f.StringVarP(&o.Username, "username", "u", "", "Username when copying to an authenticated remote registry") + f.StringVarP(&o.Password, "password", "p", "", "Password when copying to an authenticated remote registry") + f.BoolVar(&o.Insecure, "insecure", false, "Toggle allowing insecure connections when copying to a remote registry") + f.BoolVar(&o.PlainHTTP, "plain-http", false, "Toggle allowing plain http connections when copying to a remote registry") +} diff --git a/internal/flags/extract.go b/internal/flags/extract.go new file mode 100644 index 00000000..29156f55 --- /dev/null +++ b/internal/flags/extract.go @@ -0,0 +1,14 @@ +package flags + +import "github.com/spf13/cobra" + +type ExtractOpts struct { + *StoreRootOpts + DestinationDir string +} + +func (o *ExtractOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + + f.StringVarP(&o.DestinationDir, "output", "o", "", "Directory to save contents to (defaults to current directory)") +} diff --git a/internal/flags/info.go b/internal/flags/info.go new file mode 100644 index 00000000..68641d51 --- /dev/null +++ b/internal/flags/info.go @@ -0,0 +1,22 @@ +package flags + +import "github.com/spf13/cobra" + +type InfoOpts struct { + *StoreRootOpts + + OutputFormat string + TypeFilter string + SizeUnit string + ListRepos bool +} + +func (o *InfoOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + + f.StringVarP(&o.OutputFormat, "output", "o", "table", "Output format (table, json)") + f.StringVarP(&o.TypeFilter, "type", "t", "all", "Filter on type (image, chart, file, sigs, atts, sbom)") + f.BoolVar(&o.ListRepos, "list-repos", false, "List all repository names") + + // TODO: Regex/globbing +} diff --git a/internal/flags/load.go b/internal/flags/load.go new file mode 100644 index 00000000..7c13c35a --- /dev/null +++ b/internal/flags/load.go @@ -0,0 +1,18 @@ +package flags + +import "github.com/spf13/cobra" + +type LoadOpts struct { + *StoreRootOpts + TempOverride string +} + +func (o *LoadOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + + // On Unix systems, the default is $TMPDIR if non-empty, else /tmp. + // On Windows, the default is GetTempPath, returning the first non-empty + // value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory. + // On Plan 9, the default is /tmp. + f.StringVarP(&o.TempOverride, "tempdir", "t", "", "overrides the default directory for temporary files, as returned by your OS.") +} diff --git a/internal/flags/login.go b/internal/flags/login.go new file mode 100644 index 00000000..e4940e4b --- /dev/null +++ b/internal/flags/login.go @@ -0,0 +1,16 @@ +package flags + +import "github.com/spf13/cobra" + +type LoginOpts struct { + Username string + Password string + PasswordStdin bool +} + +func (o *LoginOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + f.StringVarP(&o.Username, "username", "u", "", "Username to use for authentication") + f.StringVarP(&o.Password, "password", "p", "", "Password to use for authentication") + f.BoolVar(&o.PasswordStdin, "password-stdin", false, "Password to use for authentication (from stdin)") +} diff --git a/internal/flags/save.go b/internal/flags/save.go new file mode 100644 index 00000000..c1d66aee --- /dev/null +++ b/internal/flags/save.go @@ -0,0 +1,14 @@ +package flags + +import "github.com/spf13/cobra" + +type SaveOpts struct { + *StoreRootOpts + FileName string +} + +func (o *SaveOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + + f.StringVarP(&o.FileName, "filename", "f", "haul.tar.zst", "Name of archive") +} diff --git a/internal/flags/serve.go b/internal/flags/serve.go new file mode 100644 index 00000000..fcbce8a9 --- /dev/null +++ b/internal/flags/serve.go @@ -0,0 +1,87 @@ +package flags + +import ( + "fmt" + "net/http" + + "github.com/distribution/distribution/v3/configuration" + "github.com/spf13/cobra" +) + +type ServeRegistryOpts struct { + *StoreRootOpts + + Port int + RootDir string + ConfigFile string + ReadOnly bool + + TLSCert string + TLSKey string +} + +func (o *ServeRegistryOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + + f.IntVarP(&o.Port, "port", "p", 5000, "Port used to accept incoming connections") + f.StringVar(&o.RootDir, "directory", "registry", "Directory to use for backend. Defaults to $PWD/registry") + f.StringVarP(&o.ConfigFile, "config", "c", "", "Path to config file, overrides all other flags") + f.BoolVar(&o.ReadOnly, "readonly", true, "Run the registry as readonly") + + f.StringVar(&o.TLSCert, "tls-cert", "", "Location of the TLS Certificate") + f.StringVar(&o.TLSKey, "tls-key", "", "Location of the TLS Key") + + cmd.MarkFlagsRequiredTogether("tls-cert", "tls-key") +} + +func (o *ServeRegistryOpts) DefaultRegistryConfig() *configuration.Configuration { + cfg := &configuration.Configuration{ + Version: "0.1", + Storage: configuration.Storage{ + "cache": configuration.Parameters{"blobdescriptor": "inmemory"}, + "filesystem": configuration.Parameters{"rootdirectory": o.RootDir}, + "maintenance": configuration.Parameters{ + "readonly": map[any]any{"enabled": o.ReadOnly}, + }, + }, + } + + if o.TLSCert != "" && o.TLSKey != "" { + cfg.HTTP.TLS.Certificate = o.TLSCert + cfg.HTTP.TLS.Key = o.TLSKey + } + + cfg.HTTP.Addr = fmt.Sprintf(":%d", o.Port) + cfg.HTTP.Headers = http.Header{ + "X-Content-Type-Options": []string{"nosniff"}, + } + + cfg.Log.Level = "info" + cfg.Validation.Manifests.URLs.Allow = []string{".+"} + + return cfg +} + +type ServeFilesOpts struct { + *StoreRootOpts + + Port int + Timeout int + RootDir string + + TLSCert string + TLSKey string +} + +func (o *ServeFilesOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + + f.IntVarP(&o.Port, "port", "p", 8080, "Port used to accept incoming connections") + f.IntVarP(&o.Timeout, "timeout", "t", 60, "Timeout duration for HTTP Requests in seconds for both reads/writes") + f.StringVar(&o.RootDir, "directory", "fileserver", "Directory to use for backend. Defaults to $PWD/fileserver") + + f.StringVar(&o.TLSCert, "tls-cert", "", "Location of the TLS Certificate") + f.StringVar(&o.TLSKey, "tls-key", "", "Location of the TLS Key") + + cmd.MarkFlagsRequiredTogether("tls-cert", "tls-key") +} diff --git a/cmd/hauler/cli/store/flags.go b/internal/flags/store.go similarity index 70% rename from cmd/hauler/cli/store/flags.go rename to internal/flags/store.go index 9276affb..0cdacde1 100644 --- a/cmd/hauler/cli/store/flags.go +++ b/internal/flags/store.go @@ -1,4 +1,4 @@ -package store +package flags import ( "context" @@ -6,28 +6,24 @@ import ( "os" "path/filepath" - "github.com/spf13/cobra" - + "github.com/rancherfederal/hauler/pkg/consts" "github.com/rancherfederal/hauler/pkg/log" "github.com/rancherfederal/hauler/pkg/store" + "github.com/spf13/cobra" ) -const ( - DefaultStoreName = "store" -) - -type RootOpts struct { +type StoreRootOpts struct { StoreDir string CacheDir string } -func (o *RootOpts) AddArgs(cmd *cobra.Command) { +func (o *StoreRootOpts) AddFlags(cmd *cobra.Command) { pf := cmd.PersistentFlags() - pf.StringVarP(&o.StoreDir, "store", "s", DefaultStoreName, "Location to create store at") + pf.StringVarP(&o.StoreDir, "store", "s", consts.DefaultStoreName, "Location to create store at") pf.StringVar(&o.CacheDir, "cache", "", "(deprecated flag and currently not used)") } -func (o *RootOpts) Store(ctx context.Context) (*store.Layout, error) { +func (o *StoreRootOpts) Store(ctx context.Context) (*store.Layout, error) { l := log.FromContext(ctx) dir := o.StoreDir diff --git a/internal/flags/sync.go b/internal/flags/sync.go new file mode 100644 index 00000000..30bae395 --- /dev/null +++ b/internal/flags/sync.go @@ -0,0 +1,24 @@ +package flags + +import "github.com/spf13/cobra" + +type SyncOpts struct { + *StoreRootOpts + ContentFiles []string + Key string + Products []string + Platform string + Registry string + ProductRegistry string +} + +func (o *SyncOpts) AddFlags(cmd *cobra.Command) { + f := cmd.Flags() + + f.StringSliceVarP(&o.ContentFiles, "files", "f", []string{}, "Path(s) to local content files (Manifests). i.e. '--files ./rke2-files.yml") + f.StringVarP(&o.Key, "key", "k", "", "(Optional) Path to the key for signature verification") + f.StringSliceVar(&o.Products, "products", []string{}, "(Optional) Feature for RGS Carbide customers to fetch collections and content from the Carbide Registry. i.e. '--product rancher=v2.8.5,rke2=v1.28.11+rke2r1'") + f.StringVarP(&o.Platform, "platform", "p", "", "(Optional) Specific platform to save. i.e. linux/amd64. Defaults to all if flag is omitted.") + f.StringVarP(&o.Registry, "registry", "r", "", "(Optional) Default pull registry for image refs that are not specifying a registry name.") + f.StringVarP(&o.ProductRegistry, "product-registry", "c", "", "(Optional) Specific Product Registry to use. Defaults to RGS Carbide Registry (rgcrprod.azurecr.us).") +} diff --git a/internal/server/file.go b/internal/server/file.go index 136e9944..cc037327 100644 --- a/internal/server/file.go +++ b/internal/server/file.go @@ -9,22 +9,16 @@ import ( "github.com/gorilla/handlers" "github.com/gorilla/mux" + "github.com/rancherfederal/hauler/internal/flags" ) -type FileConfig struct { - Root string - Host string - Port int - Timeout int -} - // NewFile returns a fileserver // TODO: Better configs -func NewFile(ctx context.Context, cfg FileConfig) (Server, error) { +func NewFile(ctx context.Context, cfg flags.ServeFilesOpts) (Server, error) { r := mux.NewRouter() - r.PathPrefix("/").Handler(handlers.LoggingHandler(os.Stdout, http.StripPrefix("/", http.FileServer(http.Dir(cfg.Root))))) - if cfg.Root == "" { - cfg.Root = "." + r.PathPrefix("/").Handler(handlers.LoggingHandler(os.Stdout, http.StripPrefix("/", http.FileServer(http.Dir(cfg.RootDir))))) + if cfg.RootDir == "" { + cfg.RootDir = "." } if cfg.Port == 0 { diff --git a/internal/server/server.go b/internal/server/server.go index 6c20e09a..c7b03100 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -2,4 +2,5 @@ package server type Server interface { ListenAndServe() error + ListenAndServeTLS(string, string) error } diff --git a/pkg/consts/consts.go b/pkg/consts/consts.go index 1c1d9517..579ed4e6 100644 --- a/pkg/consts/consts.go +++ b/pkg/consts/consts.go @@ -54,4 +54,6 @@ const ( ImageAnnotationKey = "hauler.dev/key" ImageAnnotationPlatform = "hauler.dev/platform" ImageAnnotationRegistry = "hauler.dev/registry" + + DefaultStoreName = "store" )