Merge pull request #55 from rancherfederal/cli-ux

* cli ux and verbiage cleanup
* add `hauler store add` command
This commit is contained in:
Josh Wolf
2021-11-01 14:36:24 -07:00
committed by GitHub
13 changed files with 387 additions and 167 deletions
+1 -4
View File
@@ -39,10 +39,7 @@ func New() *cobra.Command {
pf.StringVar(&ro.cacheDir, "cache", "", "Location of where to store cache data (defaults to XDG_CACHE_DIR/hauler)")
// Add subcommands
addGet(cmd)
addSave(cmd)
addLoad(cmd)
addServe(cmd)
addDownload(cmd)
addStore(cmd)
return cmd
+26
View File
@@ -0,0 +1,26 @@
package cli
import (
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/cmd/hauler/cli/download"
)
func addDownload(parent *cobra.Command) {
o := &download.Opts{}
cmd := &cobra.Command{
Use: "download",
Short: "Download OCI content from a registry and populate it on disk",
Aliases: []string{"dl"},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, arg []string) error {
ctx := cmd.Context()
return download.Cmd(ctx, o, arg[0])
},
}
o.AddArgs(cmd)
parent.AddCommand(cmd)
}
@@ -1,4 +1,4 @@
package get
package download
import (
"context"
@@ -29,7 +29,7 @@ func (o *Opts) AddArgs(cmd *cobra.Command) {
func Cmd(ctx context.Context, o *Opts, reference string) error {
l := log.FromContext(ctx)
l.Debugf("running command `hauler get`")
l.Debugf("running command `hauler download`")
cs := content.NewFileStore(o.DestinationDir)
defer cs.Close()
-25
View File
@@ -1,25 +0,0 @@
package cli
import (
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/cmd/hauler/cli/get"
)
func addGet(parent *cobra.Command) {
o := &get.Opts{}
cmd := &cobra.Command{
Use: "get",
Short: "Get OCI content from a registry",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, arg []string) error {
ctx := cmd.Context()
return get.Cmd(ctx, o, arg[0])
},
}
o.AddArgs(cmd)
parent.AddCommand(cmd)
}
-29
View File
@@ -1,29 +0,0 @@
package cli
import (
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/cmd/hauler/cli/load"
)
func addLoad(parent *cobra.Command) {
o := &load.Opts{}
cmd := &cobra.Command{
Use: "load",
Short: "Load archived content into hauler's store",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
s, err := ro.getStore(ctx)
if err != nil {
return err
}
return load.Cmd(ctx, o, s.DataDir, args...)
},
}
parent.AddCommand(cmd)
}
-30
View File
@@ -1,30 +0,0 @@
package cli
import (
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/cmd/hauler/cli/save"
)
func addSave(parent *cobra.Command) {
o := &save.Opts{}
cmd := &cobra.Command{
Use: "save",
Short: "Save hauler's store into a transportable compressed archive",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
s, err := ro.getStore(ctx)
if err != nil {
return err
}
return save.Cmd(ctx, o, o.FileName, s.DataDir)
},
}
o.AddArgs(cmd)
parent.AddCommand(cmd)
}
-29
View File
@@ -1,29 +0,0 @@
package cli
import (
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/cmd/hauler/cli/serve"
)
func addServe(parent *cobra.Command) {
o := &serve.Opts{}
cmd := &cobra.Command{
Use: "serve",
Short: "Serve artifacts in hauler's embedded store",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
s, err := ro.getStore(ctx)
if err != nil {
return err
}
return serve.Cmd(ctx, o, s.DataDir)
},
}
o.AddFlags(cmd)
parent.AddCommand(cmd)
}
+182 -9
View File
@@ -9,7 +9,7 @@ import (
func addStore(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "store",
Short: "Interact with hauler's content store",
Short: "Interact with hauler's embedded content store",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
@@ -17,19 +17,26 @@ func addStore(parent *cobra.Command) {
cmd.AddCommand(
addStoreSync(),
addStoreGet(),
addStoreExtract(),
addStoreLoad(),
addStoreSave(),
addStoreServe(),
// TODO: Remove this in favor of sync only
addStoreAdd(),
)
parent.AddCommand(cmd)
}
func addStoreGet() *cobra.Command {
o := &store.GetOpts{}
func addStoreExtract() *cobra.Command {
o := &store.ExtractOpts{}
cmd := &cobra.Command{
Use: "get",
Short: "Get content from hauler's embedded content store",
Args: cobra.ExactArgs(1),
Use: "extract",
Short: "Extract content from the store to disk",
Aliases: []string{"x"},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -38,7 +45,7 @@ func addStoreGet() *cobra.Command {
return err
}
return store.GetCmd(ctx, o, s, args[0])
return store.ExtractCmd(ctx, o, s, args[0])
},
}
o.AddArgs(cmd)
@@ -51,7 +58,7 @@ func addStoreSync() *cobra.Command {
cmd := &cobra.Command{
Use: "sync",
Short: "Sync content to hauler's embedded content store",
Short: "Sync content to the embedded content store",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -67,3 +74,169 @@ func addStoreSync() *cobra.Command {
return cmd
}
func addStoreLoad() *cobra.Command {
o := &store.LoadOpts{}
cmd := &cobra.Command{
Use: "load",
Short: "Load a content store from a store archive",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
s, err := ro.getStore(ctx)
if err != nil {
return err
}
return store.LoadCmd(ctx, o, s.DataDir, args...)
},
}
o.AddFlags(cmd)
return cmd
}
func addStoreServe() *cobra.Command {
o := &store.ServeOpts{}
cmd := &cobra.Command{
Use: "serve",
Short: "Expose the content of a local store through an OCI compliant server",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
s, err := ro.getStore(ctx)
if err != nil {
return err
}
return store.ServeCmd(ctx, o, s)
},
}
o.AddFlags(cmd)
return cmd
}
func addStoreSave() *cobra.Command {
o := &store.SaveOpts{}
cmd := &cobra.Command{
Use: "save",
Short: "Save a content store to a store archive",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
s, err := ro.getStore(ctx)
if err != nil {
return err
}
return store.SaveCmd(ctx, o, o.FileName, s.DataDir)
},
}
o.AddArgs(cmd)
return cmd
}
func addStoreAdd() *cobra.Command {
cmd := &cobra.Command{
Use: "add",
Short: "Add content to store",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
cmd.AddCommand(
addStoreAddFile(),
addStoreAddImage(),
addStoreAddChart(),
)
return cmd
}
func addStoreAddFile() *cobra.Command {
o := &store.AddFileOpts{}
cmd := &cobra.Command{
Use: "file",
Short: "Add a file to the content store",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
s, err := ro.getStore(ctx)
if err != nil {
return err
}
ref := args[0]
return store.AddFileCmd(ctx, o, s, ref)
},
}
o.AddFlags(cmd)
return cmd
}
func addStoreAddImage() *cobra.Command {
o := &store.AddImageOpts{}
cmd := &cobra.Command{
Use: "image",
Short: "Add an image to the content store",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
s, err := ro.getStore(ctx)
if err != nil {
return err
}
ref := args[0]
return store.AddImageCmd(ctx, o, s, ref)
},
}
o.AddFlags(cmd)
return cmd
}
func addStoreAddChart() *cobra.Command {
o := &store.AddChartOpts{}
cmd := &cobra.Command{
Use: "chart",
Short: "Add a chart to the content store",
Example: `
# add a chart
hauler store add longhorn --repo "https://charts.longhorn.io"
# add a specific version of a chart
hauler store add chart rancher --repo "https://releases.rancher.com/server-charts/latest" --version "2.6.2"
`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
s, err := ro.getStore(ctx)
if err != nil {
return err
}
return store.AddChartCmd(ctx, o, s, args[0])
},
}
o.AddFlags(cmd)
return cmd
}
+104
View File
@@ -0,0 +1,104 @@
package store
import (
"context"
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1"
"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"
"github.com/rancherfederal/hauler/pkg/store"
)
type AddFileOpts struct {
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.Store, ref string) error {
l := log.FromContext(ctx)
l.Debugf("running cli command `hauler store add`")
s.Open()
defer s.Close()
cfg := v1alpha1.File{
Ref: ref,
Name: o.Name,
}
f := file.NewFile(cfg)
return s.Add(ctx, f)
}
type AddImageOpts struct {
Name string
}
func (o *AddImageOpts) AddFlags(cmd *cobra.Command) {
f := cmd.Flags()
_ = f
}
func AddImageCmd(ctx context.Context, o *AddImageOpts, s *store.Store, ref string) error {
l := log.FromContext(ctx)
l.Debugf("running cli command `hauler store add image`")
s.Open()
defer s.Close()
cfg := v1alpha1.Image{
Ref: ref,
}
i := image.NewImage(cfg)
return s.Add(ctx, i)
}
type AddChartOpts struct {
Name string
Version string
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) {
f := cmd.Flags()
f.StringVarP(&o.RepoURL, "repo", "r", "", "Chart repository URL")
f.StringVar(&o.Version, "version", "", "(Optional) Version of the chart to download, defaults to latest if not specified")
}
func AddChartCmd(ctx context.Context, o *AddChartOpts, s *store.Store, name string) error {
l := log.FromContext(ctx)
l.Debugf("running cli command `hauler store add chart`")
s.Open()
defer s.Close()
cfg := v1alpha1.Chart{
Name: name,
RepoURL: o.RepoURL,
Version: o.Version,
}
c := chart.NewChart(cfg)
return s.Add(ctx, c)
}
@@ -6,24 +6,24 @@ import (
"github.com/google/go-containerregistry/pkg/name"
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/cmd/hauler/cli/get"
"github.com/rancherfederal/hauler/cmd/hauler/cli/download"
"github.com/rancherfederal/hauler/pkg/log"
"github.com/rancherfederal/hauler/pkg/store"
)
type GetOpts struct {
type ExtractOpts struct {
DestinationDir string
}
func (o *GetOpts) AddArgs(cmd *cobra.Command) {
func (o *ExtractOpts) AddArgs(cmd *cobra.Command) {
f := cmd.Flags()
f.StringVar(&o.DestinationDir, "dir", "", "Directory to save contents to (defaults to current directory)")
}
func GetCmd(ctx context.Context, o *GetOpts, s *store.Store, reference string) error {
func ExtractCmd(ctx context.Context, o *ExtractOpts, s *store.Store, reference string) error {
l := log.FromContext(ctx)
l.Debugf("running command `hauler store get`")
l.Debugf("running command `hauler store extract`")
s.Open()
defer s.Close()
@@ -35,9 +35,9 @@ func GetCmd(ctx context.Context, o *GetOpts, s *store.Store, reference string) e
eref := s.RelocateReference(ref)
gopts := &get.Opts{
gopts := &download.Opts{
DestinationDir: o.DestinationDir,
}
return get.Cmd(ctx, gopts, eref.Name())
return download.Cmd(ctx, gopts, eref.Name())
}
@@ -1,20 +1,26 @@
package load
package store
import (
"context"
"github.com/mholt/archiver/v3"
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/pkg/log"
)
type Opts struct{}
type LoadOpts struct{}
// Cmd
func (o *LoadOpts) AddFlags(cmd *cobra.Command) {
f := cmd.Flags()
_ = f
}
// LoadCmd
// TODO: Just use mholt/archiver for now, even though we don't need most of it
func Cmd(ctx context.Context, o *Opts, dir string, archiveRefs ...string) error {
func LoadCmd(ctx context.Context, o *LoadOpts, dir string, archiveRefs ...string) error {
l := log.FromContext(ctx)
l.Debugf("running command `hauler load`")
l.Debugf("running command `hauler store load`")
// TODO: Support more formats?
a := archiver.NewTarZstd()
@@ -1,4 +1,4 @@
package save
package store
import (
"context"
@@ -11,21 +11,21 @@ import (
"github.com/rancherfederal/hauler/pkg/log"
)
type Opts struct {
type SaveOpts struct {
FileName string
}
func (o *Opts) AddArgs(cmd *cobra.Command) {
func (o *SaveOpts) AddArgs(cmd *cobra.Command) {
f := cmd.Flags()
f.StringVarP(&o.FileName, "filename", "f", "pkg.tar.zst", "Name of archive")
}
// Cmd
// SaveCmd
// TODO: Just use mholt/archiver for now, even though we don't need most of it
func Cmd(ctx context.Context, o *Opts, outputFile string, dir string) error {
func SaveCmd(ctx context.Context, o *SaveOpts, outputFile string, dir string) error {
l := log.FromContext(ctx)
l.Debugf("running command `hauler save`")
l.Debugf("running command `hauler store save`")
// TODO: Support more formats?
a := archiver.NewTarZstd()
@@ -1,47 +1,45 @@
package serve
package store
import (
"context"
"fmt"
"net/http"
"os"
"github.com/distribution/distribution/v3/configuration"
"github.com/distribution/distribution/v3/registry"
"github.com/spf13/cobra"
"github.com/rancherfederal/hauler/pkg/log"
"github.com/rancherfederal/hauler/pkg/store"
)
type Opts struct {
type ServeOpts struct {
Port int
configFile string
ConfigFile string
storedir string
}
func (o *Opts) AddFlags(cmd *cobra.Command) {
func (o *ServeOpts) AddFlags(cmd *cobra.Command) {
f := cmd.Flags()
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")
}
// Cmd does
func Cmd(ctx context.Context, o *Opts, dir string) error {
// ServeCmd does
func ServeCmd(ctx context.Context, o *ServeOpts, s *store.Store) error {
l := log.FromContext(ctx)
l.Debugf("running command `hauler serve`")
l.Debugf("running command `hauler store serve`")
cfg := &configuration.Configuration{
Version: "0.1",
Storage: configuration.Storage{
"cache": configuration.Parameters{"blobdescriptor": "inmemory"},
"filesystem": configuration.Parameters{"rootdirectory": dir},
// TODO: Ensure this is toggleable via cli arg if necessary
"maintenance": configuration.Parameters{"readonly.enabled": true},
},
}
cfg.Log.Level = "info"
cfg.HTTP.Addr = fmt.Sprintf(":%d", o.Port)
cfg.HTTP.Headers = http.Header{
"X-Content-Type-Options": []string{"nosniff"},
cfg := o.defaultConfig(s)
if o.ConfigFile != "" {
ucfg, err := loadConfig(o.ConfigFile)
if err != nil {
return err
}
cfg = ucfg
}
r, err := registry.NewRegistry(ctx, cfg)
@@ -56,3 +54,32 @@ func Cmd(ctx context.Context, o *Opts, dir string) error {
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 *ServeOpts) defaultConfig(s *store.Store) *configuration.Configuration {
cfg := &configuration.Configuration{
Version: "0.1",
Storage: configuration.Storage{
"cache": configuration.Parameters{"blobdescriptor": "inmemory"},
"filesystem": configuration.Parameters{"rootdirectory": s.DataDir},
// TODO: Ensure this is toggleable via cli arg if necessary
"maintenance": configuration.Parameters{"readonly.enabled": true},
},
}
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
}