From b93cd721e139d4a6fd33bca0c1311d20d6c7b289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= Date: Mon, 7 Jun 2021 14:11:27 +0200 Subject: [PATCH 1/9] 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/issues/689 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 000000000..ea35de8d7 --- /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 000000000..11652c709 --- /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:] + } + } + }) + } +} From cd3de59171cf93a3b5eccb0dbde6a6648387db25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= Date: Sat, 3 Jul 2021 22:06:30 +0200 Subject: [PATCH 2/9] 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/issues/689 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 272dcc9ca..c63b85bc8 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 +} From ecd8927a6fcbbbc797f98908355830f297cb5eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= Date: Sun, 4 Jul 2021 18:15:16 +0200 Subject: [PATCH 3/9] pkg/podman: Try to parse the error from 'podman pull' into a Go error https://github.com/containers/toolbox/issues/689 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 9099df1ea..8e7d5068f 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 } From 5c7904f6f8b246432bb4d8f541cc9e53aa9fec2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= Date: Sun, 4 Jul 2021 18:16:12 +0200 Subject: [PATCH 4/9] pkg/podman: Add error for 'podman pull' failing due to no authorization https://github.com/containers/toolbox/issues/689 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 8e7d5068f..521538c9a 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. From 674ce39c2da2ce8ae343a3329613bdea2374c6e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= Date: Sat, 3 Jul 2021 02:25:49 +0200 Subject: [PATCH 5/9] pkg/podman: Wrap 'podman login' https://github.com/containers/toolbox/issues/689 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 521538c9a..8538bd7c6 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 From b39cb80d7d057639e670f03a5b9a6d7ad80251d2 Mon Sep 17 00:00:00 2001 From: Debarshi Ray Date: Sun, 4 Jul 2021 18:11:04 +0200 Subject: [PATCH 6/9] 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/issues/689 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 e3245b4c5..6a3005f06 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 From 313bede0573ebaa095ebe9b07f2de38cbd6f21e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= Date: Sun, 4 Jul 2021 18:11:28 +0200 Subject: [PATCH 7/9] 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/issues/689 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 6a3005f06..7cf03e8a4 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 From ece0f9e942a40229f90d2f153918012b2fe98b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= Date: Thu, 1 Jul 2021 12:14:31 +0200 Subject: [PATCH 8/9] test/system: Test logging to registries Testing of this feature is tricky as it relies on network which is inherently flaky. So, as part of the setup two local image registries are created and these features can be tested against them. To prevent the removal of the registries during testing a different root for Podman is used. The only tested registry implementation is Docker[0]. [0] https://hub.docker.com/_/registry/ https://github.com/containers/toolbox/issues/689 https://github.com/containers/toolbox/pull/787 --- playbooks/setup-env.yaml | 2 + test/system/000-setup.bats | 80 +++++++++++++++++++++++++++++++++++ test/system/101-create.bats | 61 ++++++++++++++++++++++++++ test/system/999-teardown.bats | 11 +++++ test/system/README.md | 6 +++ test/system/libs/helpers.bash | 14 ++++-- 6 files changed, 170 insertions(+), 4 deletions(-) diff --git a/playbooks/setup-env.yaml b/playbooks/setup-env.yaml index f5f62164b..99040b277 100644 --- a/playbooks/setup-env.yaml +++ b/playbooks/setup-env.yaml @@ -11,8 +11,10 @@ - flatpak-session-helper - golang - golang-github-cpuguy83-md2man + - httpd-tools - meson - ninja-build + - openssl - podman - skopeo - systemd diff --git a/test/system/000-setup.bats b/test/system/000-setup.bats index 019b03594..d9c112e92 100644 --- a/test/system/000-setup.bats +++ b/test/system/000-setup.bats @@ -1,5 +1,7 @@ #!/usr/bin/env bats +load 'libs/bats-support/load' +load 'libs/bats-assert/load' load 'libs/helpers' @test "test suite: Setup" { @@ -7,5 +9,83 @@ load 'libs/helpers' _pull_and_cache_distro_image $(get_system_id) $(get_system_version) || die # Cache all images that will be needed during the tests _pull_and_cache_distro_image fedora 32 || die + _pull_and_cache_distro_image rhel 8.4 || die _pull_and_cache_distro_image busybox || die + + # Prepare localy hosted image registries + # The registries need to live in a separate instance of Podman to prevent + # them from being removed when cleaning state between test cases. + + # Create certificates for HTTPS + mkdir -p "$CERTS_DIR" + openssl req \ + -newkey rsa:4096 \ + -nodes -sha256 \ + -keyout "$CERTS_DIR"/domain.key \ + -addext "subjectAltName = DNS:localhost" \ + -x509 \ + -days 365 \ + -subj '/' \ + -out "$CERTS_DIR"/domain.crt + assert [ $? -eq 0 ] + + # Add certificate to Podman's trusted certificates (rootless) + mkdir -p ~/.config/containers/certs.d/localhost:50000 + cp "$CERTS_DIR"/domain.crt ~/.config/containers/certs.d/localhost:50000 + mkdir -p ~/.config/containers/certs.d/localhost:50001 + cp "$CERTS_DIR"/domain.crt ~/.config/containers/certs.d/localhost:50001 + + # Create a registry user + # username: user + # password: user + mkdir -p "$AUTH_DIR" + htpasswd -Bbn user user > "$AUTH_DIR"/htpasswd + assert [ $? -eq 0 ] + + # Create separate Podman root + mkdir -p "$PODMAN_REG_ROOT" + + # Create a Docker registry without authentication + run $PODMAN --root "$PODMAN_REG_ROOT" run -d \ + --rm \ + --name docker-registry-noauth \ + --privileged \ + -v "$CERTS_DIR":/certs \ + -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \ + -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \ + -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \ + -p 50000:443 \ + docker.io/library/registry:2 + assert_success + echo "# Podman logs - docker-registry-noauth" + $PODMAN --root "$PODMAN_REG_ROOT" logs docker-registry-noauth + + # Create a Docker registry with authentication + run $PODMAN --root "$PODMAN_REG_ROOT" run -d \ + --rm \ + --name docker-registry-auth \ + --privileged \ + -v "$AUTH_DIR":/auth \ + -e REGISTRY_AUTH=htpasswd \ + -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \ + -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ + -v "$CERTS_DIR":/certs \ + -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \ + -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \ + -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \ + -p 50001:443 \ + docker.io/library/registry:2 + assert_success + echo "# Podman logs - docker-registry-auth" + $PODMAN --root "$PODMAN_REG_ROOT" logs docker-registry-auth + + # Add UBI8 to created registries + run $SKOPEO copy "dir:${IMAGE_CACHE_DIR}/fedora-toolbox-32" "docker://localhost:50000/fedora-toolbox:32" + assert_success + + run $SKOPEO copy \ + --dest-creds user:user \ + "dir:${IMAGE_CACHE_DIR}/fedora-toolbox-32" "docker://localhost:50001/fedora-toolbox:32" + assert_success + } diff --git a/test/system/101-create.bats b/test/system/101-create.bats index dfb4d89e1..14c52ae63 100644 --- a/test/system/101-create.bats +++ b/test/system/101-create.bats @@ -69,3 +69,64 @@ teardown() { assert_output --regexp "Created[[:blank:]]+fedora-toolbox-32" } + +@test "create: Try to create container based on non-existent image" { + run $TOOLBOX -y create -i localhost:50000/unknownimage + + assert_failure + assert_line --index 0 "Error: image localhost:50000/unknownimage does not exist" + assert_line --index 1 "Make sure the image URI is correct." +} + +@test "create: Try to create container based on unresolvable image" { + run $TOOLBOX create -i foobar + + assert_failure + assert_line --index 0 "Error: image foobar not found in local storage and known registries" + assert_line --index 1 "Make sure the image URI is correct" +} + +@test "create: Try to create container based on image from private registry" { + run $TOOLBOX -y create -i localhost:50001/fedora-toolbox:32 + + assert_failure + assert_line --index 0 "Error: Could not pull image localhost:50001/fedora-toolbox:32" + assert_line --index 1 "The registry requires logging in." + assert_line --index 2 "See 'podman login --help' on how to login into a registry." +} + +@test "create: Try to create container based on image from private registry (provide false credentials)" { + run $TOOLBOX -y create -i localhost:50001/fedora-toolbox:32 --creds wrong:wrong + + assert_failure + assert_line --index 0 "Could not pull image localhost:50001/fedora-toolbox:32" + assert_line --index 1 "The registry requires logging in." + assert_line --index 2 "Credentials were provided. Trying to log into localhost:50001" + assert_line --index 3 --partial "Error: error logging into \"localhost:50001\"" +} + +@test "create: Create container based on image from private registry (provide correct credentials)" { + run $TOOLBOX -y create my-fedora -i localhost:50001/fedora-toolbox:32 --creds user:user + + assert_success + assert_line --index 0 "Could not pull image localhost:50001/fedora-toolbox:32" + assert_line --index 1 "The registry requires logging in." + assert_line --index 2 "Credentials were provided. Trying to log into localhost:50001" + assert_line --index 3 "Login Succeeded!" + assert_line --index 4 "Retrying to pull image localhost:50001/fedora-toolbox:32" + assert_line --index 5 "Created container: my-fedora" + assert_line --index 6 "Enter with: toolbox enter my-fedora" +} + +@test "create: Create container based on image from private registry (log in beforehand)" { + + run $PODMAN login localhost:50001 --username user --password user + + assert_success + + run $TOOLBOX -y create my-fedora -i localhost:50001/fedora-toolbox:32 + + assert_success + assert_line --index 0 "Created container: my-fedora" + assert_line --index 1 "Enter with: toolbox enter my-fedora" +} diff --git a/test/system/999-teardown.bats b/test/system/999-teardown.bats index 4ed580c8a..f68dc2c2b 100644 --- a/test/system/999-teardown.bats +++ b/test/system/999-teardown.bats @@ -4,4 +4,15 @@ load 'libs/helpers' @test "test suite: Teardown" { _clean_cached_images + + # Remove containers & with registries + $PODMAN --root "$PODMAN_REG_ROOT" rm -af + $PODMAN --root "$PODMAN_REG_ROOT" rmi -af + + # Remove test cache dir + rm -rf "$CACHE_DIR" + + # Remove certificates + rm -rf ~/.config/containers/certs.d/localhost:50000 + rm -rf ~/.config/containers/certs.d/localhost:50001 } diff --git a/test/system/README.md b/test/system/README.md index 0051aae35..168d0f6b9 100644 --- a/test/system/README.md +++ b/test/system/README.md @@ -11,6 +11,8 @@ tests will clear all podman state (delete all containers, images, etc). ## Dependencies - `bats` +- `htpasswd` +- `openssl` - `podman` - `skopeo` - `toolbox` @@ -42,6 +44,10 @@ Examples: that there are no dependencies between tests and they are really isolated. Use the `setup()` and `teardown()` functions for that purpose. +- Due to lack of native support for test suite wide setup and teardown the suite + uses test files `000-setup.bats` and `999-teardown.bats`. All work related to + the whole suite should be done there. + ## How to run the tests First, make sure you have all the dependencies installed. diff --git a/test/system/libs/helpers.bash b/test/system/libs/helpers.bash index d59d66178..b00f602cf 100644 --- a/test/system/libs/helpers.bash +++ b/test/system/libs/helpers.bash @@ -9,16 +9,22 @@ readonly SKOPEO=$(command -v skopeo) # Helpful globals readonly PROJECT_DIR=${PWD} -readonly IMAGE_CACHE_DIR="${PROJECT_DIR}/image-cache" +readonly CACHE_DIR="${HOME}/.cache/toolbox/system-test" +readonly IMAGE_CACHE_DIR="${CACHE_DIR}/image-cache" +readonly AUTH_DIR="${CACHE_DIR}/auth" +readonly CERTS_DIR="${CACHE_DIR}/certs" +readonly PODMAN_REG_ROOT="${CACHE_DIR}/reg" # Images declare -Ag IMAGES=([busybox]="docker.io/library/busybox" \ - [fedora]="registry.fedoraproject.org/fedora-toolbox" \ - [rhel]="registry.access.redhat.com/ubi8") + [fedora]="registry.fedoraproject.org/fedora-toolbox") function cleanup_all() { - $PODMAN system reset --force >/dev/null + $PODMAN rm --all --force >/dev/null + $PODMAN rmi --all --force >/dev/null + + $PODMAN logout --all >/dev/null } From ba2fc50e152dc6f340aada187841699d2a27b0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20M=C3=ADchal?= Date: Mon, 21 Jun 2021 22:47:21 +0200 Subject: [PATCH 9/9] .zuul: Bump timeout of system tests from 20 to 25 minutes The new additions to the system tests makes them take longer to run, and was causing them to timeout. https://github.com/containers/toolbox/issues/689 https://github.com/containers/toolbox/pull/787 --- .zuul.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 1ec2f5973..27095db42 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -12,7 +12,7 @@ - job: name: system-test-fedora-33 description: Run Toolbox's system tests in Fedora 33 - timeout: 1200 + timeout: 1500 nodeset: nodes: - name: ci-node-33 @@ -23,7 +23,7 @@ - job: name: system-test-fedora-34 description: Run Toolbox's system tests in Fedora 34 - timeout: 1200 + timeout: 1500 nodeset: nodes: - name: ci-node-34 @@ -34,7 +34,7 @@ - job: name: system-test-fedora-rawhide description: Run Toolbox's system tests in Fedora Rawhide - timeout: 1200 + timeout: 1500 nodeset: nodes: - name: ci-node-rawhide