cmd/uplink: Add ability to generate named accesses
Change-Id: I2cf58c1c41bfffa800949dc441a488c16a448375
This commit is contained in:
parent
034f9845b1
commit
3b55b50eac
@ -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"}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user