Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): new storage config input from file when creating a repo #2756

Merged
merged 4 commits into from Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 41 additions & 9 deletions cli/command_repository_connect_from_config.go
Expand Up @@ -2,6 +2,8 @@

import (
"context"
"io"
"os"

"github.com/alecthomas/kingpin/v2"
"github.com/pkg/errors"
Expand All @@ -13,33 +15,45 @@
type storageFromConfigFlags struct {
connectFromConfigFile string
connectFromConfigToken string
connectFromTokenFile string
connectFromTokenStdin bool

sps StorageProviderServices
}

func (c *storageFromConfigFlags) Setup(sps StorageProviderServices, cmd *kingpin.CmdClause) {
cmd.Flag("file", "Path to the configuration file").StringVar(&c.connectFromConfigFile)
cmd.Flag("token", "Configuration token").StringVar(&c.connectFromConfigToken)
cmd.Flag("token-file", "Path to the configuration token file").StringVar(&c.connectFromTokenFile)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about (also) adding a boolean token-stdin or something similar ? This would make it work on Windows where /dev/stdin is not available.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea, will do

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkowalski, According to my understanding, we want to introduce a flag token-stdin, which is basically of boolean type. If the value is false than user have to pass the token file path and if true than user have to pass the value of token thru stdin ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the --token-stdin changes with the unit-test

cmd.Flag("token-stdin", "Read configuration token from stdin").BoolVar(&c.connectFromTokenStdin)

c.sps = sps
}

func (c *storageFromConfigFlags) Connect(ctx context.Context, isCreate bool, formatVersion int) (blob.Storage, error) {
_ = formatVersion

if isCreate {
return nil, errors.New("not supported")
}

if c.connectFromConfigFile != "" {
if !isCreate && c.connectFromConfigFile != "" {
return c.connectToStorageFromConfigFile(ctx)
}

if c.connectFromConfigToken != "" {
return c.connectToStorageFromConfigToken(ctx)
return c.connectToStorageFromConfigToken(ctx, c.connectFromConfigToken)
}

if c.connectFromTokenFile != "" {
return c.connectToStorageFromStorageConfigFile(ctx)
}

if c.connectFromTokenStdin {
return c.connectToStorageFromStorageConfigStdin(ctx)
}

if isCreate {
return nil, errors.New("one of --token-file, --token-stdin or --token must be provided")
}

return nil, errors.New("either --file or --token must be provided")
return nil, errors.New("one of --file, --token-file, --token-stdin or --token must be provided")
}

func (c *storageFromConfigFlags) connectToStorageFromConfigFile(ctx context.Context) (blob.Storage, error) {
Expand All @@ -56,8 +70,8 @@
return blob.NewStorage(ctx, *cfg.Storage, false)
}

func (c *storageFromConfigFlags) connectToStorageFromConfigToken(ctx context.Context) (blob.Storage, error) {
ci, pass, err := repo.DecodeToken(c.connectFromConfigToken)
func (c *storageFromConfigFlags) connectToStorageFromConfigToken(ctx context.Context, token string) (blob.Storage, error) {
ci, pass, err := repo.DecodeToken(token)
if err != nil {
return nil, errors.Wrap(err, "invalid token")
}
Expand All @@ -69,3 +83,21 @@
//nolint:wrapcheck
return blob.NewStorage(ctx, ci, false)
}

func (c *storageFromConfigFlags) connectToStorageFromStorageConfigFile(ctx context.Context) (blob.Storage, error) {
tokenData, err := os.ReadFile(c.connectFromTokenFile)
if err != nil {
return nil, errors.Wrap(err, "unable to open token file")
}

return c.connectToStorageFromConfigToken(ctx, string(tokenData))
}

func (c *storageFromConfigFlags) connectToStorageFromStorageConfigStdin(ctx context.Context) (blob.Storage, error) {
tokenData, err := io.ReadAll(c.sps.stdin())
if err != nil {
return nil, errors.Wrap(err, "unable to read token from stdin")
}

Check warning on line 100 in cli/command_repository_connect_from_config.go

View check run for this annotation

Codecov / codecov/patch

cli/command_repository_connect_from_config.go#L99-L100

Added lines #L99 - L100 were not covered by tests

return c.connectToStorageFromConfigToken(ctx, string(tokenData))
}
4 changes: 0 additions & 4 deletions cli/command_repository_create.go
Expand Up @@ -57,10 +57,6 @@ func (c *commandRepositoryCreate) setup(svc advancedAppServices, parent commandP
c.out.setup(svc)

for _, prov := range svc.storageProviders() {
if prov.Name == "from-config" {
continue
}

// Set up 'create' subcommand
f := prov.NewFlags()
cc := cmd.Command(prov.Name, "Create repository in "+prov.Description)
Expand Down
63 changes: 63 additions & 0 deletions cli/command_repository_create_test.go
@@ -0,0 +1,63 @@
package cli_test

import (
"os"
"path"
"strings"
"testing"

"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/blob"
"github.com/kopia/kopia/repo/blob/filesystem"
"github.com/kopia/kopia/tests/testenv"

"github.com/stretchr/testify/require"
)

func TestRepositoryCreateWithConfigFile(t *testing.T) {
env := testenv.NewCLITest(t, nil, testenv.NewInProcRunner(t))

_, stderr := env.RunAndExpectFailure(t, "repo", "create", "from-config", "--file", path.Join(env.ConfigDir, "does_not_exist.config"))
require.Contains(t, stderr, "can't connect to storage: one of --token-file, --token-stdin or --token must be provided")

_, stderr = env.RunAndExpectFailure(t, "repo", "connect", "from-config")
require.Contains(t, stderr, "can't connect to storage: one of --file, --token-file, --token-stdin or --token must be provided")

_, stderr = env.RunAndExpectFailure(t, "repo", "create", "from-config", "--token", "bad-token")
require.Contains(t, stderr, "can't connect to storage: invalid token: unable to decode token")

storageCfgFName := path.Join(env.ConfigDir, "storage-config.json")
ci := blob.ConnectionInfo{
Type: "filesystem",
Config: filesystem.Options{Path: env.RepoDir},
}
token, err := repo.EncodeToken("12345678", ci)
require.Nil(t, err)

// expect failure before writing to file
_, stderr = env.RunAndExpectFailure(t, "repo", "create", "from-config", "--token-file", storageCfgFName)
require.Contains(t, strings.Join(stderr, "\n"), "can't connect to storage: unable to open token file")

require.Nil(t, os.WriteFile(storageCfgFName, []byte(token), 0o600))

defer os.Remove(storageCfgFName) //nolint:errcheck,gosec

env.RunAndExpectSuccess(t, "repo", "create", "from-config", "--token-file", storageCfgFName)
}

func TestRepositoryCreateWithConfigFromStdin(t *testing.T) {
runner := testenv.NewInProcRunner(t)
env := testenv.NewCLITest(t, nil, runner)

ci := blob.ConnectionInfo{
Type: "filesystem",
Config: filesystem.Options{Path: env.RepoDir},
}
token, err := repo.EncodeToken("12345678", ci)
require.Nil(t, err)

// set stdin
runner.SetNextStdin(strings.NewReader(token))

env.RunAndExpectSuccess(t, "repo", "create", "from-config", "--token-stdin")
}
2 changes: 2 additions & 0 deletions cli/storage_providers.go
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"context"
"io"

"github.com/alecthomas/kingpin/v2"

Expand All @@ -15,6 +16,7 @@ type StorageProviderServices interface {
EnvName(s string) string
setPasswordFromToken(pwd string)
storageProviders() []StorageProvider
stdin() io.Reader
}

// StorageFlags is implemented by cli storage providers which need to support a
Expand Down
8 changes: 7 additions & 1 deletion repo/token.go
Expand Up @@ -18,9 +18,15 @@ type tokenInfo struct {
// Token returns an opaque token that contains repository connection information
// and optionally the provided password.
func (r *directRepository) Token(password string) (string, error) {
return EncodeToken(password, r.blobs.ConnectionInfo())
}

// EncodeToken returns an opaque token that contains the given connection information
// and optionally the provided password.
func EncodeToken(password string, ci blob.ConnectionInfo) (string, error) {
ti := &tokenInfo{
Version: "1",
Storage: r.blobs.ConnectionInfo(),
Storage: ci,
Password: password,
}

Expand Down