Skip to content

Commit

Permalink
feat: Added login and logout cli commands for container registry
Browse files Browse the repository at this point in the history
* `werf cr login REGISTRY` — to login;
* `werf cr logout REGISTRY` — to logout.

There will be more `werf cr` commands to work with container registries, like getting manifests, listing tags and deleting tags (`cr` stands for "container registry").
  • Loading branch information
distorhead committed Dec 29, 2021
1 parent 77a336a commit 0b7e147
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 21 deletions.
145 changes: 145 additions & 0 deletions cmd/werf/cr/login/login.go
@@ -0,0 +1,145 @@
package login

import (
"context"
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"oras.land/oras-go/pkg/auth"
"oras.land/oras-go/pkg/auth/docker"

"github.com/werf/logboek"
"github.com/werf/werf/cmd/werf/common"
secret_common "github.com/werf/werf/cmd/werf/helm/secret/common"
"github.com/werf/werf/pkg/werf"
"github.com/werf/werf/pkg/werf/global_warnings"
)

var commonCmdData common.CmdData

var cmdData struct {
Username string
Password string
PasswordStdin bool
}

func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "login registry",
Short: "Login into remote registry",
Long: common.GetLongCommandDescription(`Login into remote registry`),
Example: `# Login with username and password from command line
werf cr login -u username -p password registry.example.com
# Login with token from command line
werf cr login -p token registry.example.com
# Login into insecure registry (over http)
werf cr login --insecure-registry registry.example.com`,
DisableFlagsInUseLine: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := common.BackgroundContext()

defer global_warnings.PrintGlobalWarnings(ctx)

if err := common.ProcessLogOptions(&commonCmdData); err != nil {
common.PrintHelp(cmd)
return err
}

if len(args) != 1 {
common.PrintHelp(cmd)
return fmt.Errorf("registry address argument required")
}

return Login(ctx, args[0], LoginOptions{
Username: cmdData.Username,
Password: cmdData.Password,
PasswordStdin: cmdData.PasswordStdin,
DockerConfigDir: *commonCmdData.DockerConfig,
InsecureRegistry: *commonCmdData.InsecureRegistry,
})
},
}

common.SetupDockerConfig(&commonCmdData, cmd, "")
common.SetupInsecureRegistry(&commonCmdData, cmd)
// common.SetupSkipTlsVerifyRegistry(&commonCmdData, cmd)
common.SetupLogOptions(&commonCmdData, cmd)

cmd.Flags().StringVarP(&cmdData.Username, "username", "u", os.Getenv("WERF_USERNAME"), "Use specified username for login (default $WERF_USERNAME)")
cmd.Flags().StringVarP(&cmdData.Password, "password", "p", os.Getenv("WERF_PASSWORD"), "Use specified password for login (default $WERF_PASSWORD)")
cmd.Flags().BoolVarP(&cmdData.PasswordStdin, "password-stdin", "", common.GetBoolEnvironmentDefaultFalse("WERF_PASSWORD_STDIN"), "Read password from stdin for login (default $WERF_PASSWORD_STDIN)")

return cmd
}

type LoginOptions struct {
Username string
Password string
PasswordStdin bool
DockerConfigDir string
InsecureRegistry bool
}

func Login(ctx context.Context, registry string, opts LoginOptions) error {
var dockerConfigDir string
if opts.DockerConfigDir != "" {
dockerConfigDir = opts.DockerConfigDir
} else {
dockerConfigDir = filepath.Join(os.Getenv("HOME"), ".docker")
}

cli, err := docker.NewClient(filepath.Join(dockerConfigDir, "config.json"))
if err != nil {
return fmt.Errorf("unable to create oras auth client: %s", err)
}

if opts.Username == "" {
return fmt.Errorf("provide --username")
}

var password string
if opts.PasswordStdin {
if opts.Password != "" {
return fmt.Errorf("--password and --password-stdin could not be used at the same time")
}

var bytePassword []byte
if terminal.IsTerminal(int(os.Stdin.Fd())) {
bytePassword, err = secret_common.InputFromInteractiveStdin("Password: ")
if err != nil {
return fmt.Errorf("error reading password from interactive stdin: %s", err)
}
} else {
bytePassword, err = secret_common.InputFromStdin()
if err != nil {
return fmt.Errorf("error reading password from stdin: %s", err)
}
}

password = string(bytePassword)
} else if opts.Password != "" {
password = opts.Password
} else {
return fmt.Errorf("provide --password or --password-stdin")
}

if err := cli.LoginWithOpts(func(settings *auth.LoginSettings) {
settings.Context = ctx
settings.Hostname = registry
settings.Username = opts.Username
settings.Secret = password
settings.Insecure = opts.InsecureRegistry
settings.UserAgent = fmt.Sprintf("werf %s", werf.Version)
}); err != nil {
return fmt.Errorf("unable to login into %q: %s", registry, err)
}

logboek.Context(ctx).Default().LogFHighlight("Successful login\n")

return nil
}
76 changes: 76 additions & 0 deletions cmd/werf/cr/logout/logout.go
@@ -0,0 +1,76 @@
package logout

import (
"context"
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"
"oras.land/oras-go/pkg/auth/docker"

"github.com/werf/logboek"
"github.com/werf/werf/cmd/werf/common"
"github.com/werf/werf/pkg/werf/global_warnings"
)

var commonCmdData common.CmdData

func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "logout registry",
Short: "Logout from a remote registry",
Long: common.GetLongCommandDescription(`Logout from a remote registry`),
DisableFlagsInUseLine: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := common.BackgroundContext()

defer global_warnings.PrintGlobalWarnings(ctx)

if err := common.ProcessLogOptions(&commonCmdData); err != nil {
common.PrintHelp(cmd)
return err
}

if len(args) != 1 {
common.PrintHelp(cmd)
return fmt.Errorf("registry address argument required")
}

return Logout(ctx, args[0], LogoutOptions{
DockerConfigDir: *commonCmdData.DockerConfig,
})
},
}

common.SetupDockerConfig(&commonCmdData, cmd, "")
common.SetupLogOptions(&commonCmdData, cmd)

return cmd
}

type LogoutOptions struct {
DockerConfigDir string
}

func Logout(ctx context.Context, registry string, opts LogoutOptions) error {
var dockerConfigDir string
if opts.DockerConfigDir != "" {
dockerConfigDir = opts.DockerConfigDir
} else {
dockerConfigDir = filepath.Join(os.Getenv("HOME"), ".docker")
}

cli, err := docker.NewClient(filepath.Join(dockerConfigDir, "config.json"))
if err != nil {
return fmt.Errorf("unable to create auth client: %s", err)
}

if err := cli.Logout(ctx, registry); err != nil {
return fmt.Errorf("unable to logout from %q: %s", registry, err)
}

logboek.Context(ctx).Default().LogFHighlight("Successful logout\n")

return nil
}
35 changes: 33 additions & 2 deletions cmd/werf/helm/secret/common/common.go
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path/filepath"

"github.com/moby/term"
"golang.org/x/crypto/ssh/terminal"

"github.com/werf/logboek"
Expand Down Expand Up @@ -39,17 +40,47 @@ func ReadFileData(filePath string) ([]byte, error) {
return fileData, err
}

func InputFromInteractiveStdin() ([]byte, error) {
func InputFromInteractiveStdin(prompt string) ([]byte, error) {
var data []byte
var err error

isStdoutTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
if isStdoutTerminal {
fmt.Printf(logboek.Colorize(style.Highlight(), "Enter secret: "))
fmt.Printf(logboek.Colorize(style.Highlight(), prompt))
}

prepareTerminal := func() (func() error, error) {
state, err := term.SetRawTerminal(os.Stdin.Fd())
if err != nil {
return nil, fmt.Errorf("unable to put terminal into raw mode: %s", err)
}

restored := false

return func() error {
if restored {
return nil
}
if err := term.RestoreTerminal(os.Stdin.Fd(), state); err != nil {
return err
}
restored = true
return nil
}, nil
}

restoreTerminal, err := prepareTerminal()
if err != nil {
return nil, err
}
defer restoreTerminal()

data, err = terminal.ReadPassword(int(os.Stdin.Fd()))

if err := restoreTerminal(); err != nil {
return nil, fmt.Errorf("unable to restore terminal: %s", err)
}

if isStdoutTerminal {
fmt.Println()
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/werf/helm/secret/decrypt/decrypt.go
Expand Up @@ -97,7 +97,7 @@ func secretDecrypt(ctx context.Context, m *secrets_manager.SecretsManager, worki
}

if terminal.IsTerminal(int(os.Stdin.Fd())) {
encodedData, err = secret_common.InputFromInteractiveStdin()
encodedData, err = secret_common.InputFromInteractiveStdin("Enter secret: ")
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/werf/helm/secret/encrypt/encrypt.go
Expand Up @@ -96,7 +96,7 @@ func secretEncrypt(ctx context.Context, m *secrets_manager.SecretsManager, worki
}

if terminal.IsTerminal(int(os.Stdin.Fd())) {
data, err = secret_common.InputFromInteractiveStdin()
data, err = secret_common.InputFromInteractiveStdin("Enter secret: ")
if err != nil {
return err
}
Expand Down
16 changes: 16 additions & 0 deletions cmd/werf/main.go
Expand Up @@ -24,6 +24,8 @@ import (
config_list "github.com/werf/werf/cmd/werf/config/list"
config_render "github.com/werf/werf/cmd/werf/config/render"
"github.com/werf/werf/cmd/werf/converge"
cr_login "github.com/werf/werf/cmd/werf/cr/login"
cr_logout "github.com/werf/werf/cmd/werf/cr/logout"
"github.com/werf/werf/cmd/werf/dismiss"
"github.com/werf/werf/cmd/werf/docs"
"github.com/werf/werf/cmd/werf/export"
Expand Down Expand Up @@ -118,6 +120,7 @@ Find more information at https://werf.io`),
managedImagesCmd(),
hostCmd(),
helm.NewCmd(),
crCmd(),
},
},
{
Expand Down Expand Up @@ -153,6 +156,19 @@ func dockerComposeCmd() *cobra.Command {
return cmd
}

func crCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "cr",
Short: "Work with container registry: authenticate, list and remove images, etc.",
}
cmd.AddCommand(
cr_login.NewCmd(),
cr_logout.NewCmd(),
)

return cmd
}

func bundleCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "bundle",
Expand Down
7 changes: 4 additions & 3 deletions go.mod
Expand Up @@ -51,17 +51,17 @@ require (
github.com/minio/minio v0.0.0-20210311070216-f92b7a562103
github.com/mitchellh/copystructure v1.1.1
github.com/moby/buildkit v0.8.2
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/mvdan/xurls v1.1.0 // indirect
github.com/oleiade/reflections v1.0.1 // indirect
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.16.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.2-0.20211123152302-43a7dee1ec31
github.com/opencontainers/runc v1.0.3 // indirect
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
github.com/otiai10/copy v1.0.1
github.com/otiai10/curr v1.0.0 // indirect
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/pkg/errors v0.9.1
github.com/prashantv/gostub v1.0.0
github.com/rodaine/table v1.0.0
Expand All @@ -70,7 +70,6 @@ require (
github.com/spaolacci/murmur3 v1.1.0
github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/theupdateframework/notary v0.6.1 // indirect
github.com/tonistiigi/go-rosetta v0.0.0-20200727161949-f79598599c5d // indirect
github.com/werf/kubedog v0.6.3-0.20211020172441-2ae4bcd3d36f
Expand All @@ -80,6 +79,7 @@ require (
go.mongodb.org/mongo-driver v1.5.1 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
gopkg.in/errgo.v2 v2.1.0
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
Expand All @@ -98,6 +98,7 @@ require (
k8s.io/klog/v2 v2.9.0
k8s.io/kubectl v0.22.1
mvdan.cc/xurls v1.1.0
oras.land/oras-go v0.4.0
sigs.k8s.io/yaml v1.2.1-0.20210128145534-11e43d4a8b92
)

Expand Down

0 comments on commit 0b7e147

Please sign in to comment.