Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support logging into a registry if necessary #787

6 changes: 3 additions & 3 deletions .zuul.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
- job:
name: system-test-fedora-33
description: Run Toolbox's system tests in Fedora 33
timeout: 1200
timeout: 1500
HarryMichal marked this conversation as resolved.
Show resolved Hide resolved
nodeset:
nodes:
- name: ci-node-33
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions playbooks/setup-env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
- flatpak-session-helper
- golang
- golang-github-cpuguy83-md2man
- httpd-tools
- meson
- ninja-build
- openssl
- podman
- skopeo
- systemd
Expand Down
80 changes: 79 additions & 1 deletion src/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package cmd

import (
"bufio"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -731,6 +791,24 @@ func pullImage(image, release string) (bool, error) {
return false, nil
}

if _, err := pullImageWithSpinner(imageFull); err != nil {
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
}

func pullImageWithSpinner(imageFull string) (bool, error) {
logrus.Debugf("Pulling image %s", imageFull)

stdoutFd := os.Stdout.Fd()
Expand All @@ -744,7 +822,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
Expand Down
109 changes: 109 additions & 0 deletions src/pkg/podman/error.go
Original file line number Diff line number Diff line change
@@ -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}
}
Loading