cmd/uplink: Add ability to generate named accesses

Change-Id: I2cf58c1c41bfffa800949dc441a488c16a448375
This commit is contained in:
Bryan White 2020-01-09 13:34:52 +01:00
parent 034f9845b1
commit 3b55b50eac
6 changed files with 117 additions and 29 deletions

View File

@ -52,6 +52,25 @@ func applyDefaultHostAndPortToAddr(address, defaultAddress string) (string, erro
return net.JoinHostPort(addressParts[0], defaultPort), nil
}
// PromptForAccessName handles user input for access name to be used with wizards
func PromptForAccessName() (string, error) {
_, err := fmt.Printf("Choose an access name [\"default\"]: ")
if err != nil {
return "", err
}
var accessName string
n, err := fmt.Scanln(&accessName)
if err != nil && n != 0 {
return "", err
}
if accessName == "" {
return "default", nil
}
return accessName, nil
}
// PromptForSatellite handles user input for a satellite address to be used with wizards
func PromptForSatellite(cmd *cobra.Command) (string, error) {
satellites := []string{"us-central-1.tardigrade.io", "europe-west-1.tardigrade.io", "asia-east-1.tardigrade.io"}

View File

@ -79,15 +79,34 @@ type Legacy struct {
}
}
// GetAccess returns the appropriate access for the config.
func (a AccessConfig) GetAccess() (_ *libuplink.Scope, err error) {
defer mon.Task()(nil)(&err)
// normalize looks for usage of deprecated config values and sets the respective
// non-deprecated config values accordingly and returns them in a copy of the config.
func (a AccessConfig) normalize() (_ AccessConfig) {
// fallback to scope if access not found
if a.Access == "" {
a.Access = a.Scope
}
if a.Accesses == nil {
a.Accesses = make(map[string]string)
}
// fallback to scopes if accesses not found
if len(a.Accesses) == 0 {
for name, access := range a.Scopes {
a.Accesses[name] = access
}
}
return a
}
// GetAccess returns the appropriate access for the config.
func (a AccessConfig) GetAccess() (_ *libuplink.Scope, err error) {
defer mon.Task()(nil)(&err)
a = a.normalize()
access, err := a.GetNamedAccess(a.Access)
if err != nil {
return nil, err
@ -150,11 +169,6 @@ func (a AccessConfig) GetNamedAccess(name string) (_ *libuplink.Scope, err error
if data, ok := a.Accesses[name]; ok {
return libuplink.ParseScope(data)
}
// fallback to scopes
if data, ok := a.Scopes[name]; ok {
return libuplink.ParseScope(data)
}
return nil, nil
}
@ -191,3 +205,10 @@ func (c Config) GetEncryptionParameters() storj.EncryptionParameters {
func (c Config) GetSegmentSize() memory.Size {
return c.Client.SegmentSize
}
// IsSerializedAccess returns whether the passed access is a serialized
// access string or not.
func IsSerializedAccess(access string) bool {
_, err := libuplink.ParseScope(access)
return err == nil
}

View File

@ -13,13 +13,12 @@ import (
"github.com/spf13/cobra"
"github.com/zeebo/errs"
libuplink "storj.io/storj/lib/uplink"
"storj.io/storj/pkg/cfgstruct"
"storj.io/storj/pkg/process"
)
var importCfg struct {
Overwrite bool `default:"false" help:"if true, allows a access to be overwritten" source:"flag"`
Overwrite bool `default:"false" help:"if true, allows an access to be overwritten" source:"flag"`
UplinkFlags
}
@ -44,6 +43,10 @@ func init() {
// importMain is the function executed when importCmd is called
func importMain(cmd *cobra.Command, args []string) (err error) {
if cmd.Flag("access").Changed {
return ErrAccessFlag
}
saveConfig := func(saveConfigOption process.SaveConfigOption) error {
path := filepath.Join(confDir, process.DefaultCfgFilename)
exists, err := fileExists(path)
@ -90,10 +93,7 @@ func importMain(cmd *cobra.Command, args []string) (err error) {
// This is a little hacky but viper deserializes accesses into a map[string]interface{}
// and complains if we try and override with map[string]string{}.
accesses := map[string]interface{}{}
for k, v := range importCfg.Accesses {
accesses[k] = v
}
accesses := toStringMapE(importCfg.Accesses)
overwritten := false
if _, ok := accesses[name]; ok {
@ -129,7 +129,7 @@ func importMain(cmd *cobra.Command, args []string) (err error) {
func findAccess(input string) (access string, err error) {
// check if parameter is a valid access, otherwise try to read it from file
if _, err := libuplink.ParseScope(input); err == nil {
if IsSerializedAccess(input) {
access = input
} else {
path := input
@ -140,7 +140,7 @@ func findAccess(input string) (access string, err error) {
}
// Parse the access data to ensure it is well formed
if _, err := libuplink.ParseScope(access); err != nil {
if !IsSerializedAccess(access) {
return "", err
}
}

View File

@ -37,9 +37,8 @@ func TestSetGetMeta(t *testing.T) {
{
output, err := exec.Command(uplinkExe,
"--config-dir", ctx.Dir("uplink"),
"setup",
"--non-interactive",
"--access", planet.Uplinks[0].GetConfig(planet.Satellites[0]).Access,
"import",
planet.Uplinks[0].GetConfig(planet.Satellites[0]).Access,
).CombinedOutput()
t.Log(string(output))
require.NoError(t, err)

View File

@ -11,6 +11,7 @@ import (
"runtime"
"runtime/pprof"
"github.com/spf13/cast"
"github.com/spf13/cobra"
"github.com/zeebo/errs"
"go.uber.org/zap"
@ -42,6 +43,8 @@ var (
// Error is the class of errors returned by this package
Error = errs.Class("uplink")
// ErrAccessFlag is used where the `--access` flag is registered but not supported.
ErrAccessFlag = Error.New("--access flag not supported with `setup` and `import` subcommands")
)
func init() {
@ -204,3 +207,17 @@ func writeMemoryProfile() error {
}
return f.Close()
}
func toStringMapE(from interface{}) (to map[string]interface{}) {
to = make(map[string]interface{})
switch f := from.(type) {
case map[string]string:
for key, value := range f {
to[key] = value
}
return to
default:
return cast.ToStringMap(from)
}
}

View File

@ -13,7 +13,6 @@ import (
"github.com/spf13/cobra"
"github.com/zeebo/errs"
"storj.io/common/fpath"
"storj.io/storj/cmd/internal/wizard"
libuplink "storj.io/storj/lib/uplink"
"storj.io/storj/pkg/cfgstruct"
@ -36,16 +35,15 @@ func init() {
}
func cmdSetup(cmd *cobra.Command, args []string) (err error) {
if cmd.Flag("access").Changed {
return ErrAccessFlag
}
setupDir, err := filepath.Abs(confDir)
if err != nil {
return err
}
valid, _ := fpath.IsValidSetupDir(setupDir)
if !valid {
return fmt.Errorf("uplink configuration already exists (%v)", setupDir)
}
err = os.MkdirAll(setupDir, 0700)
if err != nil {
return err
@ -105,6 +103,27 @@ func cmdSetupInteractive(cmd *cobra.Command, setupDir string) error {
return Error.Wrap(err)
}
var (
accessName string
defaultSerializedAccessExists bool
)
setupCfg.AccessConfig = setupCfg.AccessConfig.normalize()
defaultSerializedAccessExists = IsSerializedAccess(setupCfg.Access)
accessName, err = wizard.PromptForAccessName()
if err != nil {
return Error.Wrap(err)
}
if accessName == "default" && defaultSerializedAccessExists {
return Error.New("a default access already exists")
}
if access, err := setupCfg.GetNamedAccess(accessName); err == nil && access != nil {
return Error.New("an access with the name %q already exists", accessName)
}
apiKeyString, err := wizard.PromptForAPIKey()
if err != nil {
return Error.Wrap(err)
@ -146,9 +165,22 @@ func cmdSetupInteractive(cmd *cobra.Command, setupDir string) error {
return Error.Wrap(err)
}
err = process.SaveConfig(cmd, filepath.Join(setupDir, "config.yaml"),
process.SaveConfigWithOverride("access", accessData),
process.SaveConfigRemovingDeprecated())
// NB: accesses should always be `map[string]interface{}` for "conventional"
// config serialization/flattening.
accesses := toStringMapE(setupCfg.Accesses)
accesses[accessName] = accessData
saveCfgOpts := []process.SaveConfigOption{
process.SaveConfigWithOverride("accesses", accesses),
process.SaveConfigRemovingDeprecated(),
}
if setupCfg.Access == "" {
saveCfgOpts = append(saveCfgOpts, process.SaveConfigWithOverride("access", accessName))
}
configPath := filepath.Join(setupDir, process.DefaultCfgFilename)
err = process.SaveConfig(cmd, configPath, saveCfgOpts...)
if err != nil {
return Error.Wrap(err)
}