067c49f5d1
- Actually apply the patch to make 'toolbox' create or fall back to a container if possible - Support logging into a registry if necessary - Resolves: #1977343 Signed-off-by: Jindrich Novy <jnovy@redhat.com>
668 lines
19 KiB
Diff
668 lines
19 KiB
Diff
From 62a022428231df50637495673354d4fdd2541c1a Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= <harrymichal@seznam.cz>
|
|
Date: Mon, 7 Jun 2021 14:11:27 +0200
|
|
Subject: [PATCH 1/7] pkg/podman: Support parsing 'podman pull' spew into a Go
|
|
error
|
|
|
|
This is meant to get a better understanding of a failed 'podman pull'
|
|
invocation to understand whether pulling an image requires logging into
|
|
the registry or not. Currently, 'podman pull' doesn't have a dedicated
|
|
exit code to denote authorization errors, so this is meant to be a
|
|
temporary workaround for that.
|
|
|
|
Parsing the error stream is inherently fragile and tricky because
|
|
there's no guarantee that the structure of the messages won't change,
|
|
and there's no clear definition of how the messages are laid out.
|
|
Therefore, this approach can't be treated as a generic solution for
|
|
getting detailed information about failed Podman invocations.
|
|
|
|
The error stream is used not only for dumping error messages, but also
|
|
for showing progress bars. Therefore, all lines are skipped until one
|
|
that starts with "Error: " is found. This is a heuristic based on how
|
|
Go programs written using the Cobra [1] library tend to report errors.
|
|
|
|
All subsequent lines are taken together and split around the ": "
|
|
sub-string, on the assumption that the ": " sub-string is used when a
|
|
new error message is prefixed to an inner error. Each sub-string
|
|
created from the split is treated as a potential member of the chain of
|
|
errors reported within Podman.
|
|
|
|
Some real world examples of the 'podman pull' error stream in the case
|
|
of authorization errors are:
|
|
|
|
* With Docker Hub (https://hub.docker.com/):
|
|
Trying to pull docker.io/library/foobar:latest...
|
|
Error: Error initializing source docker://foobar:latest: Error reading manifest latest in docker.io/library/foobar: errors:
|
|
denied: requested access to the resource is denied
|
|
unauthorized: authentication required
|
|
|
|
* With registry.redhat.io:
|
|
Trying to pull registry.redhat.io/foobar:latest...
|
|
Error: Error initializing source docker://registry.redhat.io/foobar:latest: unable to retrieve auth token: invalid username/password: unauthorized: Please login to the Red Hat Registry using your Customer Portal credentials. Further instructions can be found here: https://access.redhat.com/RegistryAuthentication
|
|
|
|
[1] https://github.com/spf13/cobra/
|
|
https://pkg.go.dev/github.com/spf13/cobra
|
|
|
|
https://github.com/containers/toolbox/pull/786
|
|
https://github.com/containers/toolbox/pull/787
|
|
---
|
|
src/pkg/podman/error.go | 109 +++++++++++++++++++++++++
|
|
src/pkg/podman/error_test.go | 152 +++++++++++++++++++++++++++++++++++
|
|
2 files changed, 261 insertions(+)
|
|
create mode 100644 src/pkg/podman/error.go
|
|
create mode 100644 src/pkg/podman/error_test.go
|
|
|
|
diff --git a/src/pkg/podman/error.go b/src/pkg/podman/error.go
|
|
new file mode 100644
|
|
index 000000000000..ea35de8d7ed8
|
|
--- /dev/null
|
|
+++ b/src/pkg/podman/error.go
|
|
@@ -0,0 +1,109 @@
|
|
+/*
|
|
+ * Copyright © 2021 Red Hat Inc.
|
|
+ *
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
+ * you may not use this file except in compliance with the License.
|
|
+ * You may obtain a copy of the License at
|
|
+ *
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
+ *
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
+ * See the License for the specific language governing permissions and
|
|
+ * limitations under the License.
|
|
+ */
|
|
+
|
|
+package podman
|
|
+
|
|
+import (
|
|
+ "bufio"
|
|
+ "bytes"
|
|
+ "strings"
|
|
+)
|
|
+
|
|
+// internalError serves for representing errors printed by Podman to stderr
|
|
+type internalError struct {
|
|
+ errors []string
|
|
+}
|
|
+
|
|
+func (e *internalError) Error() string {
|
|
+ if e.errors == nil || len(e.errors) == 0 {
|
|
+ return ""
|
|
+ }
|
|
+
|
|
+ var builder strings.Builder
|
|
+
|
|
+ for i, part := range e.errors {
|
|
+ if i != 0 {
|
|
+ builder.WriteString(": ")
|
|
+ }
|
|
+
|
|
+ builder.WriteString(part)
|
|
+ }
|
|
+
|
|
+ return builder.String()
|
|
+}
|
|
+
|
|
+// Is lexically compares errors
|
|
+//
|
|
+// The comparison is done for every part in the error chain not across.
|
|
+func (e *internalError) Is(target error) bool {
|
|
+ if target == nil {
|
|
+ return false
|
|
+ }
|
|
+
|
|
+ if e.errors == nil || len(e.errors) == 0 {
|
|
+ return false
|
|
+ }
|
|
+
|
|
+ for _, part := range e.errors {
|
|
+ if part == target.Error() {
|
|
+ return true
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false
|
|
+}
|
|
+
|
|
+func (e *internalError) Unwrap() error {
|
|
+ if e.errors == nil || len(e.errors) <= 1 {
|
|
+ return nil
|
|
+ }
|
|
+
|
|
+ return &internalError{e.errors[1:]}
|
|
+}
|
|
+
|
|
+// parseErrorMsg serves for converting error output of Podman into an error
|
|
+// that can be further used in Go
|
|
+func parseErrorMsg(stderr *bytes.Buffer) error {
|
|
+ // Stderr is not used only for error messages but also for things like
|
|
+ // progress bars. We're only interested in the error messages.
|
|
+
|
|
+ var errMsgFound bool
|
|
+ var errMsgParts []string
|
|
+
|
|
+ scanner := bufio.NewScanner(stderr)
|
|
+ for scanner.Scan() {
|
|
+ line := scanner.Text()
|
|
+
|
|
+ if strings.HasPrefix(line, "Error: ") {
|
|
+ line = strings.TrimPrefix(line, "Error: ")
|
|
+ errMsgFound = true
|
|
+ }
|
|
+
|
|
+ if errMsgFound {
|
|
+ line = strings.TrimSpace(line)
|
|
+ line = strings.Trim(line, ":")
|
|
+
|
|
+ parts := strings.Split(line, ": ")
|
|
+ errMsgParts = append(errMsgParts, parts...)
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if !errMsgFound {
|
|
+ return nil
|
|
+ }
|
|
+
|
|
+ return &internalError{errMsgParts}
|
|
+}
|
|
diff --git a/src/pkg/podman/error_test.go b/src/pkg/podman/error_test.go
|
|
new file mode 100644
|
|
index 000000000000..11652c709bdd
|
|
--- /dev/null
|
|
+++ b/src/pkg/podman/error_test.go
|
|
@@ -0,0 +1,152 @@
|
|
+/*
|
|
+ * Copyright © 2021 Red Hat Inc.
|
|
+ *
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
+ * you may not use this file except in compliance with the License.
|
|
+ * You may obtain a copy of the License at
|
|
+ *
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
+ *
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
+ * See the License for the specific language governing permissions and
|
|
+ * limitations under the License.
|
|
+ */
|
|
+
|
|
+package podman
|
|
+
|
|
+import (
|
|
+ "bytes"
|
|
+ "errors"
|
|
+ "testing"
|
|
+
|
|
+ "github.com/stretchr/testify/assert"
|
|
+)
|
|
+
|
|
+func TestInternalError(t *testing.T) {
|
|
+ type expect struct {
|
|
+ IsNil bool
|
|
+ Error string
|
|
+ Search string
|
|
+ Wrap []string
|
|
+ }
|
|
+
|
|
+ testCases := []struct {
|
|
+ name string
|
|
+ input string
|
|
+ expect expect
|
|
+ }{
|
|
+ {
|
|
+ name: "Empty",
|
|
+ input: "",
|
|
+ expect: expect{
|
|
+ IsNil: true,
|
|
+ Error: "",
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ name: "Text with only a prolog and no error message",
|
|
+ input: "There is only a prolog and no error message",
|
|
+ expect: expect{
|
|
+ IsNil: true,
|
|
+ Error: "",
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ name: "Text with only a prolog and no error message",
|
|
+ input: "There is only a prolog Error: not an error message",
|
|
+ expect: expect{
|
|
+ IsNil: true,
|
|
+ Error: "",
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ name: "Text with a prolog before the error message",
|
|
+ input: `There is a prolog
|
|
+Error: an error message`,
|
|
+ expect: expect{
|
|
+ Error: "an error message",
|
|
+ Search: "an error message",
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ name: "Error message with several wrapped errors",
|
|
+ input: "Error: level 1: level 2: level 3: level 4",
|
|
+ expect: expect{
|
|
+ Error: "level 1: level 2: level 3: level 4",
|
|
+ Search: "level 4",
|
|
+ Wrap: []string{"level 1", "level 2", "level 3", "level 4"},
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ name: "Error message with a bullet list",
|
|
+ input: `Error: an error message:
|
|
+ err1
|
|
+ err2
|
|
+ err3`,
|
|
+ expect: expect{
|
|
+ Error: "an error message: err1: err2: err3",
|
|
+ Search: "err2",
|
|
+ Wrap: []string{"an error message", "err1", "err2", "err3"},
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ name: "Error message from 'podman pull' - unauthorized (Docker Hub)",
|
|
+ input: `Trying to pull docker.io/library/foobar:latest...
|
|
+Error: Error initializing source docker://foobar:latest: Error reading manifest latest in docker.io/library/foobar: errors:
|
|
+denied: requested access to the resource is denied
|
|
+unauthorized: authentication required`,
|
|
+ expect: expect{
|
|
+ Error: "Error initializing source docker://foobar:latest: Error reading manifest latest in docker.io/library/foobar: errors: denied: requested access to the resource is denied: unauthorized: authentication required",
|
|
+ },
|
|
+ },
|
|
+ {
|
|
+ name: "Error message from 'podman pull' - unauthorized (Red Hat Registry)",
|
|
+ input: `Trying to pull registry.redhat.io/foobar:latest...
|
|
+Error: Error initializing source docker://registry.redhat.io/foobar:latest: unable to retrieve auth token: invalid username/password: unauthorized: Please login to the Red Hat Registry using your Customer Portal credentials. Further instructions can be found here: https://access.redhat.com/RegistryAuthentication
|
|
+`,
|
|
+ expect: expect{
|
|
+ Error: "Error initializing source docker://registry.redhat.io/foobar:latest: unable to retrieve auth token: invalid username/password: unauthorized: Please login to the Red Hat Registry using your Customer Portal credentials. Further instructions can be found here: https://access.redhat.com/RegistryAuthentication",
|
|
+ },
|
|
+ },
|
|
+ }
|
|
+
|
|
+ for _, tc := range testCases {
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
+ err := parseErrorMsg(bytes.NewBufferString(tc.input))
|
|
+
|
|
+ if tc.expect.IsNil {
|
|
+ assert.Nil(t, err)
|
|
+ return
|
|
+ } else {
|
|
+ assert.NotNil(t, err)
|
|
+ }
|
|
+
|
|
+ errInternal := err.(*internalError)
|
|
+ assert.Equal(t, tc.expect.Error, errInternal.Error())
|
|
+
|
|
+ if tc.expect.Search != "" {
|
|
+ assert.True(t, errInternal.Is(errors.New(tc.expect.Search)))
|
|
+ }
|
|
+
|
|
+ if len(tc.expect.Wrap) != 0 {
|
|
+ for {
|
|
+ assert.Equal(t, len(tc.expect.Wrap), len(errInternal.errors))
|
|
+
|
|
+ for i, part := range tc.expect.Wrap {
|
|
+ assert.Equal(t, part, errInternal.errors[i])
|
|
+ }
|
|
+
|
|
+ err = errInternal.Unwrap()
|
|
+ if err == nil {
|
|
+ assert.Equal(t, len(tc.expect.Wrap), 1)
|
|
+ break
|
|
+ }
|
|
+ errInternal = err.(*internalError)
|
|
+ tc.expect.Wrap = tc.expect.Wrap[1:]
|
|
+ }
|
|
+ }
|
|
+ })
|
|
+ }
|
|
+}
|
|
--
|
|
2.31.1
|
|
|
|
|
|
From 146c93b431941d21b3c686734a7b83a737072004 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= <harrymichal@seznam.cz>
|
|
Date: Sat, 3 Jul 2021 22:06:30 +0200
|
|
Subject: [PATCH 2/7] pkg/shell: Allow extracting the stderr even when it's
|
|
shown to the user
|
|
|
|
This way the standard error stream of the spawned binaries can be
|
|
inspected to get a better understanding of the failure, while still
|
|
being shown to the user when run with the '--verbose' flag.
|
|
|
|
Unfortunately, this breaks the progress bar in 'podman pull' because
|
|
the standard error stream is no longer connected to a file descriptor
|
|
that's a terminal device.
|
|
|
|
https://github.com/containers/toolbox/pull/787
|
|
https://github.com/containers/toolbox/pull/823
|
|
---
|
|
src/pkg/shell/shell.go | 22 +++++++++++++++++-----
|
|
1 file changed, 17 insertions(+), 5 deletions(-)
|
|
|
|
diff --git a/src/pkg/shell/shell.go b/src/pkg/shell/shell.go
|
|
index 272dcc9ca693..c63b85bc8745 100644
|
|
--- a/src/pkg/shell/shell.go
|
|
+++ b/src/pkg/shell/shell.go
|
|
@@ -40,15 +40,12 @@ func Run(name string, stdin io.Reader, stdout, stderr io.Writer, arg ...string)
|
|
}
|
|
|
|
func RunWithExitCode(name string, stdin io.Reader, stdout, stderr io.Writer, arg ...string) (int, error) {
|
|
- logLevel := logrus.GetLevel()
|
|
- if stderr == nil && logLevel >= logrus.DebugLevel {
|
|
- stderr = os.Stderr
|
|
- }
|
|
+ stderrWrapper := getStderrWrapper(stderr)
|
|
|
|
cmd := exec.Command(name, arg...)
|
|
cmd.Stdin = stdin
|
|
cmd.Stdout = stdout
|
|
- cmd.Stderr = stderr
|
|
+ cmd.Stderr = stderrWrapper
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
if errors.Is(err, exec.ErrNotFound) {
|
|
@@ -66,3 +63,18 @@ func RunWithExitCode(name string, stdin io.Reader, stdout, stderr io.Writer, arg
|
|
|
|
return 0, nil
|
|
}
|
|
+
|
|
+func getStderrWrapper(buffer io.Writer) io.Writer {
|
|
+ var stderr io.Writer
|
|
+
|
|
+ logLevel := logrus.GetLevel()
|
|
+ if logLevel < logrus.DebugLevel {
|
|
+ stderr = buffer
|
|
+ } else if buffer == nil {
|
|
+ stderr = os.Stderr
|
|
+ } else {
|
|
+ stderr = io.MultiWriter(buffer, os.Stderr)
|
|
+ }
|
|
+
|
|
+ return stderr
|
|
+}
|
|
--
|
|
2.31.1
|
|
|
|
|
|
From 5c3249033224c6f107a2d39ab84e519303d74072 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= <harrymichal@seznam.cz>
|
|
Date: Sun, 4 Jul 2021 18:15:16 +0200
|
|
Subject: [PATCH 3/7] pkg/podman: Try to parse the error from 'podman pull'
|
|
into a Go error
|
|
|
|
https://github.com/containers/toolbox/pull/787
|
|
---
|
|
src/pkg/podman/podman.go | 5 ++++-
|
|
1 file changed, 4 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/src/pkg/podman/podman.go b/src/pkg/podman/podman.go
|
|
index 9099df1eaf2a..8e7d5068fb97 100644
|
|
--- a/src/pkg/podman/podman.go
|
|
+++ b/src/pkg/podman/podman.go
|
|
@@ -228,10 +228,13 @@ func IsToolboxImage(image string) (bool, error) {
|
|
|
|
// Pull pulls an image
|
|
func Pull(imageName string) error {
|
|
+ var stderr bytes.Buffer
|
|
+
|
|
logLevelString := LogLevel.String()
|
|
args := []string{"--log-level", logLevelString, "pull", imageName}
|
|
|
|
- if err := shell.Run("podman", nil, nil, nil, args...); err != nil {
|
|
+ if err := shell.Run("podman", nil, nil, &stderr, args...); err != nil {
|
|
+ err := parseErrorMsg(&stderr)
|
|
return err
|
|
}
|
|
|
|
--
|
|
2.31.1
|
|
|
|
|
|
From 4b8b007201cd019909829d73afdcf919753943a3 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= <harrymichal@seznam.cz>
|
|
Date: Sun, 4 Jul 2021 18:16:12 +0200
|
|
Subject: [PATCH 4/7] pkg/podman: Add error for 'podman pull' failing due to no
|
|
authorization
|
|
|
|
https://github.com/containers/toolbox/pull/787
|
|
---
|
|
src/pkg/podman/podman.go | 4 +++-
|
|
1 file changed, 3 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/src/pkg/podman/podman.go b/src/pkg/podman/podman.go
|
|
index 8e7d5068fb97..521538c9abfb 100644
|
|
--- a/src/pkg/podman/podman.go
|
|
+++ b/src/pkg/podman/podman.go
|
|
@@ -19,6 +19,7 @@ package podman
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
+ "errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
@@ -32,7 +33,8 @@ var (
|
|
)
|
|
|
|
var (
|
|
- LogLevel = logrus.ErrorLevel
|
|
+ ErrUnauthorized = errors.New("unauthorized")
|
|
+ LogLevel = logrus.ErrorLevel
|
|
)
|
|
|
|
// CheckVersion compares provided version with the version of Podman.
|
|
--
|
|
2.31.1
|
|
|
|
|
|
From ef3bebac89cd833a663dc63fffba8b04ca944dde Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= <harrymichal@seznam.cz>
|
|
Date: Sat, 3 Jul 2021 02:25:49 +0200
|
|
Subject: [PATCH 5/7] pkg/podman: Wrap 'podman login'
|
|
|
|
https://github.com/containers/toolbox/pull/787
|
|
---
|
|
src/pkg/podman/podman.go | 12 ++++++++++++
|
|
1 file changed, 12 insertions(+)
|
|
|
|
diff --git a/src/pkg/podman/podman.go b/src/pkg/podman/podman.go
|
|
index 521538c9abfb..8538bd7c617a 100644
|
|
--- a/src/pkg/podman/podman.go
|
|
+++ b/src/pkg/podman/podman.go
|
|
@@ -228,6 +228,18 @@ func IsToolboxImage(image string) (bool, error) {
|
|
return true, nil
|
|
}
|
|
|
|
+func Login(registry, username, password string) error {
|
|
+ logLevelString := LogLevel.String()
|
|
+ args := []string{"--log-level", logLevelString, "login", registry, "--password-stdin", "--username", username}
|
|
+ stdin := bytes.NewBufferString(password)
|
|
+
|
|
+ if err := shell.Run("podman", stdin, nil, nil, args...); err != nil {
|
|
+ return err
|
|
+ }
|
|
+
|
|
+ return nil
|
|
+}
|
|
+
|
|
// Pull pulls an image
|
|
func Pull(imageName string) error {
|
|
var stderr bytes.Buffer
|
|
--
|
|
2.31.1
|
|
|
|
|
|
From 49cd3c48c26d37c85aba5dbdcf04a995593ae673 Mon Sep 17 00:00:00 2001
|
|
From: Debarshi Ray <rishi@fedoraproject.org>
|
|
Date: Sun, 4 Jul 2021 18:11:04 +0200
|
|
Subject: [PATCH 6/7] cmd/create: Split out the spinner around 'podman pull'
|
|
|
|
A subsequent commit will use this when retrying the 'podman pull'
|
|
after logging the user into the registry for images that require
|
|
authorization.
|
|
|
|
https://github.com/containers/toolbox/pull/787
|
|
---
|
|
src/cmd/create.go | 10 +++++++++-
|
|
1 file changed, 9 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/src/cmd/create.go b/src/cmd/create.go
|
|
index e3245b4c5ae3..6a3005f06041 100644
|
|
--- a/src/cmd/create.go
|
|
+++ b/src/cmd/create.go
|
|
@@ -731,6 +731,14 @@ func pullImage(image, release string) (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
+ if _, err := pullImageWithSpinner(imageFull); err != nil {
|
|
+ return false, fmt.Errorf("failed to pull image %s", imageFull)
|
|
+ }
|
|
+
|
|
+ return true, nil
|
|
+}
|
|
+
|
|
+func pullImageWithSpinner(imageFull string) (bool, error) {
|
|
logrus.Debugf("Pulling image %s", imageFull)
|
|
|
|
stdoutFd := os.Stdout.Fd()
|
|
@@ -744,7 +752,7 @@ func pullImage(image, release string) (bool, error) {
|
|
}
|
|
|
|
if err := podman.Pull(imageFull); err != nil {
|
|
- return false, fmt.Errorf("failed to pull image %s", imageFull)
|
|
+ return false, err
|
|
}
|
|
|
|
return true, nil
|
|
--
|
|
2.31.1
|
|
|
|
|
|
From 19bb0f08118536c32ff2d2f571d92758dff00dcb Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= <harrymichal@seznam.cz>
|
|
Date: Sun, 4 Jul 2021 18:11:28 +0200
|
|
Subject: [PATCH 7/7] cmd/create: Support logging into a registry if necessary
|
|
|
|
Some registries contain private repositories of images and require the
|
|
user to log in first to gain access. With this Toolbox tries to
|
|
recognize errors when pulling images and offers the user the means to
|
|
log in.
|
|
|
|
Some changes by Debarshi Ray.
|
|
|
|
https://github.com/containers/toolbox/pull/787
|
|
---
|
|
src/cmd/create.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++-
|
|
1 file changed, 71 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/src/cmd/create.go b/src/cmd/create.go
|
|
index 6a3005f06041..7cf03e8a4cd2 100644
|
|
--- a/src/cmd/create.go
|
|
+++ b/src/cmd/create.go
|
|
@@ -17,6 +17,7 @@
|
|
package cmd
|
|
|
|
import (
|
|
+ "bufio"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
@@ -668,6 +669,65 @@ func isPathReadWrite(path string) (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
+func logIntoRegistry(imageFull, registry string) (bool, error) {
|
|
+ fmt.Printf("Image %s requires log-in.\n", imageFull)
|
|
+
|
|
+ scanner := bufio.NewScanner(os.Stdin)
|
|
+
|
|
+ stdinFd := os.Stdin.Fd()
|
|
+ stdinFdInt := int(stdinFd)
|
|
+
|
|
+ if terminal.IsTerminal(stdinFdInt) {
|
|
+ fmt.Printf("Username: ")
|
|
+ }
|
|
+
|
|
+ if !scanner.Scan() {
|
|
+ if err := scanner.Err(); err != nil {
|
|
+ logrus.Debugf("Logging into registry: failed to read username: %s", err)
|
|
+ }
|
|
+
|
|
+ return false, errors.New("failed to read username")
|
|
+ }
|
|
+
|
|
+ username := scanner.Text()
|
|
+
|
|
+ var password string
|
|
+
|
|
+ if terminal.IsTerminal(stdinFdInt) {
|
|
+ logrus.Debug("Reading password from a terminal input")
|
|
+
|
|
+ fmt.Printf("Password: ")
|
|
+
|
|
+ passwordBytes, err := terminal.ReadPassword(stdinFdInt)
|
|
+ if err != nil {
|
|
+ logrus.Debugf("Logging into registry: failed to read password: %s", err)
|
|
+ return false, errors.New("failed to read password")
|
|
+ }
|
|
+
|
|
+ password = string(passwordBytes)
|
|
+ fmt.Println("")
|
|
+ } else {
|
|
+ logrus.Debug("Reading password from a non-terminal input")
|
|
+
|
|
+ if !scanner.Scan() {
|
|
+ if err := scanner.Err(); err != nil {
|
|
+ logrus.Debugf("Logging into registry: failed to read password: %s", err)
|
|
+ }
|
|
+
|
|
+ return false, errors.New("failed to read password")
|
|
+ }
|
|
+
|
|
+ password = scanner.Text()
|
|
+ }
|
|
+
|
|
+ if err := podman.Login(registry, username, password); err != nil {
|
|
+ logrus.Debugf("Logging into registry %s failed: %s", registry, err)
|
|
+ return false, fmt.Errorf("failed to log into registry %s", registry)
|
|
+ }
|
|
+
|
|
+ return true, nil
|
|
+}
|
|
+
|
|
func pullImage(image, release string) (bool, error) {
|
|
if _, err := utils.ImageReferenceCanBeID(image); err == nil {
|
|
logrus.Debugf("Looking for image %s", image)
|
|
@@ -732,7 +792,17 @@ func pullImage(image, release string) (bool, error) {
|
|
}
|
|
|
|
if _, err := pullImageWithSpinner(imageFull); err != nil {
|
|
- return false, fmt.Errorf("failed to pull image %s", imageFull)
|
|
+ if !errors.Is(err, podman.ErrUnauthorized) {
|
|
+ return false, fmt.Errorf("failed to pull image %s", imageFull)
|
|
+ }
|
|
+
|
|
+ if _, err := logIntoRegistry(imageFull, domain); err != nil {
|
|
+ return false, err
|
|
+ }
|
|
+
|
|
+ if _, err := pullImageWithSpinner(imageFull); err != nil {
|
|
+ return false, fmt.Errorf("failed to pull image %s", imageFull)
|
|
+ }
|
|
}
|
|
|
|
return true, nil
|
|
--
|
|
2.31.1
|
|
|