Skip to content

Commit 23aca88

Browse files
committed
Refactor: cleanup prompting
Signed-off-by: apostasie <[email protected]>
1 parent 30c63cf commit 23aca88

File tree

4 files changed

+130
-73
lines changed

4 files changed

+130
-73
lines changed

pkg/cmd/login/login.go

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,14 @@
1717
package login
1818

1919
import (
20-
"bufio"
2120
"context"
2221
"errors"
2322
"fmt"
2423
"io"
2524
"net/http"
2625
"net/url"
27-
"os"
28-
"strings"
2926

3027
"golang.org/x/net/context/ctxhttp"
31-
"golang.org/x/term"
3228

3329
"github.com/containerd/containerd/v2/core/remotes/docker"
3430
"github.com/containerd/containerd/v2/core/remotes/docker/config"
@@ -66,7 +62,7 @@ func Login(ctx context.Context, options types.LoginCommandOptions, stdout io.Wri
6662
}
6763

6864
if err != nil || credentials.Username == "" || credentials.Password == "" {
69-
err = configureAuthentication(credentials, options.Username, options.Password)
65+
err = promptUserForAuthentication(credentials, options.Username, options.Password, stdout)
7066
if err != nil {
7167
return err
7268
}
@@ -205,56 +201,3 @@ func tryLoginWithRegHost(ctx context.Context, rh docker.RegistryHost) error {
205201

206202
return errors.New("too many 401 (probably)")
207203
}
208-
209-
func configureAuthentication(credentials *dockerconfigresolver.Credentials, username, password string) error {
210-
if username = strings.TrimSpace(username); username == "" {
211-
username = credentials.Username
212-
}
213-
if username == "" {
214-
fmt.Print("Enter Username: ")
215-
usr, err := readUsername()
216-
if err != nil {
217-
return err
218-
}
219-
username = usr
220-
}
221-
if username == "" {
222-
return fmt.Errorf("error: Username is Required")
223-
}
224-
225-
if password == "" {
226-
fmt.Print("Enter Password: ")
227-
pwd, err := readPassword()
228-
fmt.Println()
229-
if err != nil {
230-
return err
231-
}
232-
password = pwd
233-
}
234-
if password == "" {
235-
return fmt.Errorf("error: Password is Required")
236-
}
237-
238-
credentials.Username = username
239-
credentials.Password = password
240-
241-
return nil
242-
}
243-
244-
func readUsername() (string, error) {
245-
var fd *os.File
246-
if term.IsTerminal(int(os.Stdin.Fd())) {
247-
fd = os.Stdin
248-
} else {
249-
return "", fmt.Errorf("stdin is not a terminal (Hint: use `nerdctl login --username=USERNAME --password-stdin`)")
250-
}
251-
252-
reader := bufio.NewReader(fd)
253-
username, err := reader.ReadString('\n')
254-
if err != nil {
255-
return "", fmt.Errorf("error reading username: %w", err)
256-
}
257-
username = strings.TrimSpace(username)
258-
259-
return username, nil
260-
}

pkg/cmd/login/prompt.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package login
18+
19+
import (
20+
"bufio"
21+
"errors"
22+
"fmt"
23+
"io"
24+
"os"
25+
"strings"
26+
27+
"golang.org/x/term"
28+
29+
"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver"
30+
)
31+
32+
var (
33+
// User did not provide non-empty credentials when prompted for it
34+
ErrUsernameIsRequired = errors.New("username is required")
35+
ErrPasswordIsRequired = errors.New("password is required")
36+
37+
// System errors - not a terminal, failure to read, etc
38+
ErrReadingUsername = errors.New("unable to read username")
39+
ErrReadingPassword = errors.New("unable to read password")
40+
ErrNotATerminal = errors.New("stdin is not a terminal (Hint: use `nerdctl login --username=USERNAME --password-stdin`)")
41+
ErrCannotAllocateTerminal = errors.New("error allocating terminal")
42+
)
43+
44+
// promptUserForAuthentication will prompt the user for credentials if needed
45+
// It might error with any of the errors defined above.
46+
func promptUserForAuthentication(credentials *dockerconfigresolver.Credentials, username, password string, stdout io.Writer) error {
47+
var err error
48+
49+
// If the provided username is empty...
50+
if username = strings.TrimSpace(username); username == "" {
51+
// Use the one we know of (from the store)
52+
username = credentials.Username
53+
// If the one from the store was empty as well, prompt and read the username
54+
if username == "" {
55+
_, _ = fmt.Fprint(stdout, "Enter Username: ")
56+
username, err = readUsername()
57+
if err != nil {
58+
return err
59+
}
60+
61+
username = strings.TrimSpace(username)
62+
// If it still is empty, that is an error
63+
if username == "" {
64+
return ErrUsernameIsRequired
65+
}
66+
}
67+
}
68+
69+
// If password was NOT passed along, ask for it
70+
if password == "" {
71+
_, _ = fmt.Fprint(stdout, "Enter Password: ")
72+
password, err = readPassword()
73+
if err != nil {
74+
return err
75+
}
76+
77+
_, _ = fmt.Fprintln(stdout)
78+
password = strings.TrimSpace(password)
79+
80+
// If nothing was provided, error out
81+
if password == "" {
82+
return ErrPasswordIsRequired
83+
}
84+
}
85+
86+
// Attach non-empty credentials to the auth object and return
87+
credentials.Username = username
88+
credentials.Password = password
89+
90+
return nil
91+
}
92+
93+
// readUsername will try to read from user input
94+
// It might error with:
95+
// - ErrNotATerminal
96+
// - ErrReadingUsername
97+
func readUsername() (string, error) {
98+
fd := os.Stdin
99+
if !term.IsTerminal(int(fd.Fd())) {
100+
return "", ErrNotATerminal
101+
}
102+
103+
username, err := bufio.NewReader(fd).ReadString('\n')
104+
if err != nil {
105+
return "", errors.Join(ErrReadingUsername, err)
106+
}
107+
108+
return strings.TrimSpace(username), nil
109+
}

pkg/cmd/login/login_unix.go renamed to pkg/cmd/login/prompt_unix.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,34 @@
1919
package login
2020

2121
import (
22-
"fmt"
22+
"errors"
2323
"os"
2424
"syscall"
2525

2626
"golang.org/x/term"
27+
28+
"github.com/containerd/log"
2729
)
2830

2931
func readPassword() (string, error) {
30-
var fd int
31-
if term.IsTerminal(syscall.Stdin) {
32-
fd = syscall.Stdin
33-
} else {
32+
fd := syscall.Stdin
33+
if !term.IsTerminal(fd) {
3434
tty, err := os.Open("/dev/tty")
3535
if err != nil {
36-
return "", fmt.Errorf("error allocating terminal: %w", err)
36+
return "", errors.Join(ErrCannotAllocateTerminal, err)
3737
}
38-
defer tty.Close()
38+
defer func() {
39+
err = tty.Close()
40+
if err != nil {
41+
log.L.WithError(err).Error("failed closing tty")
42+
}
43+
}()
3944
fd = int(tty.Fd())
4045
}
46+
4147
bytePassword, err := term.ReadPassword(fd)
4248
if err != nil {
43-
return "", fmt.Errorf("error reading password: %w", err)
49+
return "", errors.Join(ErrReadingPassword, err)
4450
}
4551

4652
return string(bytePassword), nil

pkg/cmd/login/login_windows.go renamed to pkg/cmd/login/prompt_windows.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,21 @@
1717
package login
1818

1919
import (
20-
"fmt"
20+
"errors"
2121
"syscall"
2222

2323
"golang.org/x/term"
2424
)
2525

2626
func readPassword() (string, error) {
27-
var fd int
28-
if term.IsTerminal(int(syscall.Stdin)) {
29-
fd = int(syscall.Stdin)
30-
} else {
31-
return "", fmt.Errorf("error allocating terminal")
27+
fd := int(syscall.Stdin)
28+
if !term.IsTerminal(fd) {
29+
return "", ErrNotATerminal
3230
}
31+
3332
bytePassword, err := term.ReadPassword(fd)
3433
if err != nil {
35-
return "", fmt.Errorf("error reading password: %w", err)
34+
return "", errors.Join(ErrReadingPassword, err)
3635
}
3736

3837
return string(bytePassword), nil

0 commit comments

Comments
 (0)