Skip to content

Commit 0b7e147

Browse files
committed
feat: Added login and logout cli commands for container registry
* `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").
1 parent 77a336a commit 0b7e147

File tree

8 files changed

+276
-21
lines changed

8 files changed

+276
-21
lines changed

cmd/werf/cr/login/login.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package login
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/spf13/cobra"
10+
"golang.org/x/crypto/ssh/terminal"
11+
"oras.land/oras-go/pkg/auth"
12+
"oras.land/oras-go/pkg/auth/docker"
13+
14+
"github.com/werf/logboek"
15+
"github.com/werf/werf/cmd/werf/common"
16+
secret_common "github.com/werf/werf/cmd/werf/helm/secret/common"
17+
"github.com/werf/werf/pkg/werf"
18+
"github.com/werf/werf/pkg/werf/global_warnings"
19+
)
20+
21+
var commonCmdData common.CmdData
22+
23+
var cmdData struct {
24+
Username string
25+
Password string
26+
PasswordStdin bool
27+
}
28+
29+
func NewCmd() *cobra.Command {
30+
cmd := &cobra.Command{
31+
Use: "login registry",
32+
Short: "Login into remote registry",
33+
Long: common.GetLongCommandDescription(`Login into remote registry`),
34+
Example: `# Login with username and password from command line
35+
werf cr login -u username -p password registry.example.com
36+
37+
# Login with token from command line
38+
werf cr login -p token registry.example.com
39+
40+
# Login into insecure registry (over http)
41+
werf cr login --insecure-registry registry.example.com`,
42+
DisableFlagsInUseLine: true,
43+
RunE: func(cmd *cobra.Command, args []string) error {
44+
ctx := common.BackgroundContext()
45+
46+
defer global_warnings.PrintGlobalWarnings(ctx)
47+
48+
if err := common.ProcessLogOptions(&commonCmdData); err != nil {
49+
common.PrintHelp(cmd)
50+
return err
51+
}
52+
53+
if len(args) != 1 {
54+
common.PrintHelp(cmd)
55+
return fmt.Errorf("registry address argument required")
56+
}
57+
58+
return Login(ctx, args[0], LoginOptions{
59+
Username: cmdData.Username,
60+
Password: cmdData.Password,
61+
PasswordStdin: cmdData.PasswordStdin,
62+
DockerConfigDir: *commonCmdData.DockerConfig,
63+
InsecureRegistry: *commonCmdData.InsecureRegistry,
64+
})
65+
},
66+
}
67+
68+
common.SetupDockerConfig(&commonCmdData, cmd, "")
69+
common.SetupInsecureRegistry(&commonCmdData, cmd)
70+
// common.SetupSkipTlsVerifyRegistry(&commonCmdData, cmd)
71+
common.SetupLogOptions(&commonCmdData, cmd)
72+
73+
cmd.Flags().StringVarP(&cmdData.Username, "username", "u", os.Getenv("WERF_USERNAME"), "Use specified username for login (default $WERF_USERNAME)")
74+
cmd.Flags().StringVarP(&cmdData.Password, "password", "p", os.Getenv("WERF_PASSWORD"), "Use specified password for login (default $WERF_PASSWORD)")
75+
cmd.Flags().BoolVarP(&cmdData.PasswordStdin, "password-stdin", "", common.GetBoolEnvironmentDefaultFalse("WERF_PASSWORD_STDIN"), "Read password from stdin for login (default $WERF_PASSWORD_STDIN)")
76+
77+
return cmd
78+
}
79+
80+
type LoginOptions struct {
81+
Username string
82+
Password string
83+
PasswordStdin bool
84+
DockerConfigDir string
85+
InsecureRegistry bool
86+
}
87+
88+
func Login(ctx context.Context, registry string, opts LoginOptions) error {
89+
var dockerConfigDir string
90+
if opts.DockerConfigDir != "" {
91+
dockerConfigDir = opts.DockerConfigDir
92+
} else {
93+
dockerConfigDir = filepath.Join(os.Getenv("HOME"), ".docker")
94+
}
95+
96+
cli, err := docker.NewClient(filepath.Join(dockerConfigDir, "config.json"))
97+
if err != nil {
98+
return fmt.Errorf("unable to create oras auth client: %s", err)
99+
}
100+
101+
if opts.Username == "" {
102+
return fmt.Errorf("provide --username")
103+
}
104+
105+
var password string
106+
if opts.PasswordStdin {
107+
if opts.Password != "" {
108+
return fmt.Errorf("--password and --password-stdin could not be used at the same time")
109+
}
110+
111+
var bytePassword []byte
112+
if terminal.IsTerminal(int(os.Stdin.Fd())) {
113+
bytePassword, err = secret_common.InputFromInteractiveStdin("Password: ")
114+
if err != nil {
115+
return fmt.Errorf("error reading password from interactive stdin: %s", err)
116+
}
117+
} else {
118+
bytePassword, err = secret_common.InputFromStdin()
119+
if err != nil {
120+
return fmt.Errorf("error reading password from stdin: %s", err)
121+
}
122+
}
123+
124+
password = string(bytePassword)
125+
} else if opts.Password != "" {
126+
password = opts.Password
127+
} else {
128+
return fmt.Errorf("provide --password or --password-stdin")
129+
}
130+
131+
if err := cli.LoginWithOpts(func(settings *auth.LoginSettings) {
132+
settings.Context = ctx
133+
settings.Hostname = registry
134+
settings.Username = opts.Username
135+
settings.Secret = password
136+
settings.Insecure = opts.InsecureRegistry
137+
settings.UserAgent = fmt.Sprintf("werf %s", werf.Version)
138+
}); err != nil {
139+
return fmt.Errorf("unable to login into %q: %s", registry, err)
140+
}
141+
142+
logboek.Context(ctx).Default().LogFHighlight("Successful login\n")
143+
144+
return nil
145+
}

cmd/werf/cr/logout/logout.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package logout
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/spf13/cobra"
10+
"oras.land/oras-go/pkg/auth/docker"
11+
12+
"github.com/werf/logboek"
13+
"github.com/werf/werf/cmd/werf/common"
14+
"github.com/werf/werf/pkg/werf/global_warnings"
15+
)
16+
17+
var commonCmdData common.CmdData
18+
19+
func NewCmd() *cobra.Command {
20+
cmd := &cobra.Command{
21+
Use: "logout registry",
22+
Short: "Logout from a remote registry",
23+
Long: common.GetLongCommandDescription(`Logout from a remote registry`),
24+
DisableFlagsInUseLine: true,
25+
RunE: func(cmd *cobra.Command, args []string) error {
26+
ctx := common.BackgroundContext()
27+
28+
defer global_warnings.PrintGlobalWarnings(ctx)
29+
30+
if err := common.ProcessLogOptions(&commonCmdData); err != nil {
31+
common.PrintHelp(cmd)
32+
return err
33+
}
34+
35+
if len(args) != 1 {
36+
common.PrintHelp(cmd)
37+
return fmt.Errorf("registry address argument required")
38+
}
39+
40+
return Logout(ctx, args[0], LogoutOptions{
41+
DockerConfigDir: *commonCmdData.DockerConfig,
42+
})
43+
},
44+
}
45+
46+
common.SetupDockerConfig(&commonCmdData, cmd, "")
47+
common.SetupLogOptions(&commonCmdData, cmd)
48+
49+
return cmd
50+
}
51+
52+
type LogoutOptions struct {
53+
DockerConfigDir string
54+
}
55+
56+
func Logout(ctx context.Context, registry string, opts LogoutOptions) error {
57+
var dockerConfigDir string
58+
if opts.DockerConfigDir != "" {
59+
dockerConfigDir = opts.DockerConfigDir
60+
} else {
61+
dockerConfigDir = filepath.Join(os.Getenv("HOME"), ".docker")
62+
}
63+
64+
cli, err := docker.NewClient(filepath.Join(dockerConfigDir, "config.json"))
65+
if err != nil {
66+
return fmt.Errorf("unable to create auth client: %s", err)
67+
}
68+
69+
if err := cli.Logout(ctx, registry); err != nil {
70+
return fmt.Errorf("unable to logout from %q: %s", registry, err)
71+
}
72+
73+
logboek.Context(ctx).Default().LogFHighlight("Successful logout\n")
74+
75+
return nil
76+
}

cmd/werf/helm/secret/common/common.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"path/filepath"
88

9+
"github.com/moby/term"
910
"golang.org/x/crypto/ssh/terminal"
1011

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

42-
func InputFromInteractiveStdin() ([]byte, error) {
43+
func InputFromInteractiveStdin(prompt string) ([]byte, error) {
4344
var data []byte
4445
var err error
4546

4647
isStdoutTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
4748
if isStdoutTerminal {
48-
fmt.Printf(logboek.Colorize(style.Highlight(), "Enter secret: "))
49+
fmt.Printf(logboek.Colorize(style.Highlight(), prompt))
4950
}
5051

52+
prepareTerminal := func() (func() error, error) {
53+
state, err := term.SetRawTerminal(os.Stdin.Fd())
54+
if err != nil {
55+
return nil, fmt.Errorf("unable to put terminal into raw mode: %s", err)
56+
}
57+
58+
restored := false
59+
60+
return func() error {
61+
if restored {
62+
return nil
63+
}
64+
if err := term.RestoreTerminal(os.Stdin.Fd(), state); err != nil {
65+
return err
66+
}
67+
restored = true
68+
return nil
69+
}, nil
70+
}
71+
72+
restoreTerminal, err := prepareTerminal()
73+
if err != nil {
74+
return nil, err
75+
}
76+
defer restoreTerminal()
77+
5178
data, err = terminal.ReadPassword(int(os.Stdin.Fd()))
5279

80+
if err := restoreTerminal(); err != nil {
81+
return nil, fmt.Errorf("unable to restore terminal: %s", err)
82+
}
83+
5384
if isStdoutTerminal {
5485
fmt.Println()
5586
}

cmd/werf/helm/secret/decrypt/decrypt.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func secretDecrypt(ctx context.Context, m *secrets_manager.SecretsManager, worki
9797
}
9898

9999
if terminal.IsTerminal(int(os.Stdin.Fd())) {
100-
encodedData, err = secret_common.InputFromInteractiveStdin()
100+
encodedData, err = secret_common.InputFromInteractiveStdin("Enter secret: ")
101101
if err != nil {
102102
return err
103103
}

cmd/werf/helm/secret/encrypt/encrypt.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func secretEncrypt(ctx context.Context, m *secrets_manager.SecretsManager, worki
9696
}
9797

9898
if terminal.IsTerminal(int(os.Stdin.Fd())) {
99-
data, err = secret_common.InputFromInteractiveStdin()
99+
data, err = secret_common.InputFromInteractiveStdin("Enter secret: ")
100100
if err != nil {
101101
return err
102102
}

cmd/werf/main.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
config_list "github.com/werf/werf/cmd/werf/config/list"
2525
config_render "github.com/werf/werf/cmd/werf/config/render"
2626
"github.com/werf/werf/cmd/werf/converge"
27+
cr_login "github.com/werf/werf/cmd/werf/cr/login"
28+
cr_logout "github.com/werf/werf/cmd/werf/cr/logout"
2729
"github.com/werf/werf/cmd/werf/dismiss"
2830
"github.com/werf/werf/cmd/werf/docs"
2931
"github.com/werf/werf/cmd/werf/export"
@@ -118,6 +120,7 @@ Find more information at https://werf.io`),
118120
managedImagesCmd(),
119121
hostCmd(),
120122
helm.NewCmd(),
123+
crCmd(),
121124
},
122125
},
123126
{
@@ -153,6 +156,19 @@ func dockerComposeCmd() *cobra.Command {
153156
return cmd
154157
}
155158

159+
func crCmd() *cobra.Command {
160+
cmd := &cobra.Command{
161+
Use: "cr",
162+
Short: "Work with container registry: authenticate, list and remove images, etc.",
163+
}
164+
cmd.AddCommand(
165+
cr_login.NewCmd(),
166+
cr_logout.NewCmd(),
167+
)
168+
169+
return cmd
170+
}
171+
156172
func bundleCmd() *cobra.Command {
157173
cmd := &cobra.Command{
158174
Use: "bundle",

go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,17 @@ require (
5151
github.com/minio/minio v0.0.0-20210311070216-f92b7a562103
5252
github.com/mitchellh/copystructure v1.1.1
5353
github.com/moby/buildkit v0.8.2
54+
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
5455
github.com/mvdan/xurls v1.1.0 // indirect
5556
github.com/oleiade/reflections v1.0.1 // indirect
5657
github.com/onsi/ginkgo v1.16.4
5758
github.com/onsi/gomega v1.16.0
5859
github.com/opencontainers/go-digest v1.0.0
5960
github.com/opencontainers/image-spec v1.0.2-0.20211123152302-43a7dee1ec31
6061
github.com/opencontainers/runc v1.0.3 // indirect
61-
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect
62+
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
6263
github.com/otiai10/copy v1.0.1
6364
github.com/otiai10/curr v1.0.0 // indirect
64-
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
6565
github.com/pkg/errors v0.9.1
6666
github.com/prashantv/gostub v1.0.0
6767
github.com/rodaine/table v1.0.0
@@ -70,7 +70,6 @@ require (
7070
github.com/spaolacci/murmur3 v1.1.0
7171
github.com/spf13/cobra v1.2.1
7272
github.com/spf13/pflag v1.0.5
73-
github.com/stretchr/testify v1.7.0
7473
github.com/theupdateframework/notary v0.6.1 // indirect
7574
github.com/tonistiigi/go-rosetta v0.0.0-20200727161949-f79598599c5d // indirect
7675
github.com/werf/kubedog v0.6.3-0.20211020172441-2ae4bcd3d36f
@@ -80,6 +79,7 @@ require (
8079
go.mongodb.org/mongo-driver v1.5.1 // indirect
8180
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
8281
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
82+
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
8383
gopkg.in/dancannon/gorethink.v3 v3.0.5 // indirect
8484
gopkg.in/errgo.v2 v2.1.0
8585
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
@@ -98,6 +98,7 @@ require (
9898
k8s.io/klog/v2 v2.9.0
9999
k8s.io/kubectl v0.22.1
100100
mvdan.cc/xurls v1.1.0
101+
oras.land/oras-go v0.4.0
101102
sigs.k8s.io/yaml v1.2.1-0.20210128145534-11e43d4a8b92
102103
)
103104

0 commit comments

Comments
 (0)