Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions doc/toolbox-create.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ toolbox\-create - Create a new Toolbx container
[*--distro DISTRO* | *-d DISTRO*]
[*--image NAME* | *-i NAME*]
[*--release RELEASE* | *-r RELEASE*]
[*--build BUILDCONTEXT* | *-b BUILDCONTEXT*]
[*--build-tag TAG* | *-t TAG*]
[*CONTAINER*]

## DESCRIPTION
Expand Down Expand Up @@ -110,6 +112,22 @@ remote registry.
Create a Toolbx container for a different operating system RELEASE than the
host. Cannot be used with `--image`.

**--build** BUILDCONTEXT, **-b** BUILDCONTEXT

Build a toolbx image from the build context found at BUILDCONTEXT by passing it
to `podman build`. Afterwards it sets the tag to `localhost/<name of the image>`
by extracting the name from the image and then creates the container like normal.

You cannot use `--distro`, `--release` or `--image` together with this option.

**--build-tag** TAG, **-t** TAG

Overwrites the tagging behaviour of `--build` by tagging the image with TAG via
`podman build --tag`. If no repository if given or podman doesn't know it,
localhost is used.

Can only be used when `--build` is also used.

## EXAMPLES

### Create the default Toolbx container matching the host OS
Expand Down
35 changes: 34 additions & 1 deletion src/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ var (
distro string
image string
release string
build string
buildtag string
}

createToolboxShMounts = []struct {
Expand Down Expand Up @@ -104,6 +106,18 @@ func init() {
"",
"Create a Toolbx container for a different operating system release than the host")

flags.StringVarP(&createFlags.build,
"build",
"b",
"",
"Build a Toolbx container for use of this container")

flags.StringVarP(&createFlags.buildtag,
"build-tag",
"t",
"",
"Tag the image built")

createCmd.SetHelpFunc(createHelp)

if err := createCmd.RegisterFlagCompletionFunc("distro", completionDistroNames); err != nil {
Expand Down Expand Up @@ -150,6 +164,24 @@ func create(cmd *cobra.Command, args []string) error {
return errors.New(errMsg)
}

if cmd.Flag("build").Changed && (cmd.Flag("image").Changed || cmd.Flag("release").Changed || cmd.Flag("distro").Changed) {
var builder strings.Builder
fmt.Fprintf(&builder, "options --build and --release, --image or -- distro cannot be used together\n")
fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase)

errMsg := builder.String()
return errors.New(errMsg)
}

if cmd.Flag("build-tag").Changed && !cmd.Flag("build").Changed {
var builder strings.Builder
fmt.Fprintf(&builder, "--build-tag must be used together with --build\n")
fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase)

errMsg := builder.String()
return errors.New(errMsg)
}

if cmd.Flag("authfile").Changed {
if !utils.PathExists(createFlags.authFile) {
var builder strings.Builder
Expand Down Expand Up @@ -177,7 +209,8 @@ func create(cmd *cobra.Command, args []string) error {
containerArg,
createFlags.distro,
createFlags.image,
createFlags.release)
createFlags.release,
podman.BuildOptions{Context: createFlags.build, Tag: createFlags.buildtag})

if err != nil {
return err
Expand Down
4 changes: 3 additions & 1 deletion src/cmd/enter.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"os"

"github.com/containers/toolbox/pkg/podman"
"github.com/containers/toolbox/pkg/utils"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -111,7 +112,8 @@ func enter(cmd *cobra.Command, args []string) error {
containerArg,
enterFlags.distro,
"",
enterFlags.release)
enterFlags.release,
podman.BuildOptions{Context: "", Tag: ""})

if err != nil {
return err
Expand Down
3 changes: 2 additions & 1 deletion src/cmd/rootMigrationPath.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"os"
"strings"

"github.com/containers/toolbox/pkg/podman"
"github.com/containers/toolbox/pkg/utils"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -60,7 +61,7 @@ func rootRunImpl(cmd *cobra.Command, args []string) error {
return nil
}

container, image, release, err := resolveContainerAndImageNames("", "", "", "", "")
container, image, release, err := resolveContainerAndImageNames("", "", "", "", "", podman.BuildOptions{Context: "", Tag: ""})
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion src/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ func run(cmd *cobra.Command, args []string) error {
"--container",
runFlags.distro,
"",
runFlags.release)
runFlags.release,
podman.BuildOptions{Context: "", Tag: ""})

if err != nil {
return err
Expand Down
24 changes: 19 additions & 5 deletions src/cmd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"strings"
"syscall"

"github.com/containers/toolbox/pkg/podman"
"github.com/containers/toolbox/pkg/utils"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -402,13 +403,26 @@ func poll(pollFn pollFunc, eventFD int32, fds ...int32) error {
}
}

func resolveContainerAndImageNames(container, containerArg, distroCLI, imageCLI, releaseCLI string) (
func resolveContainerAndImageNames(container, containerArg, distroCLI, imageCLI, releaseCLI string, buildCLI podman.BuildOptions) (
string, string, string, error,
) {
container, image, release, err := utils.ResolveContainerAndImageNames(container,
distroCLI,
imageCLI,
releaseCLI)
var image, release string
var err error
if buildCLI.Context == "" {
container, image, release, err = utils.ResolveContainerAndImageNames(container,
distroCLI,
imageCLI,
releaseCLI)
} else {
image, err = podman.BuildImage(buildCLI)
if err != nil {
return "", "", "", err
}
container, image, release, err = utils.ResolveContainerAndImageNames(container,
distroCLI,
image,
releaseCLI)
}

if err != nil {
var errContainer *utils.ContainerError
Expand Down
59 changes: 59 additions & 0 deletions src/pkg/podman/podman.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import (
"errors"
"fmt"
"io"
"os"
"strconv"
"strings"
"time"

"github.com/HarryMichal/go-version"
Expand All @@ -39,6 +41,11 @@ type Image struct {
Names []string
}

type BuildOptions struct {
Context string
Tag string
}

type ImageSlice []Image

var (
Expand All @@ -53,6 +60,12 @@ var (
LogLevel = logrus.ErrorLevel
)

var (
ErrBuildContextDoesNotExist = errors.New("build context does not exist")

ErrBuildContextInvalid = errors.New("build context is not a directory with a Containerfile")
)

func (image *Image) FlattenNames(fillNameWithID bool) []Image {
var ret []Image

Expand Down Expand Up @@ -129,6 +142,52 @@ func (images ImageSlice) Swap(i, j int) {
images[i], images[j] = images[j], images[i]
}

func BuildImage(build BuildOptions) (string, error) {
if !utils.PathExists(build.Context) {
return "", &utils.BuildError{BuildContext: build.Context, Err: ErrBuildContextDoesNotExist}
}
if stat, err := os.Stat(build.Context); err != nil {
return "", err
} else {
if !stat.Mode().IsDir() {
return "", &utils.BuildError{BuildContext: build.Context, Err: ErrBuildContextInvalid}
}
}
if !utils.PathExists(build.Context+"/Containerfile") && !utils.PathExists(build.Context+"/Dockerfile") {
return "", &utils.BuildError{BuildContext: build.Context, Err: ErrBuildContextInvalid}
}
logLevelString := LogLevel.String()
args := []string{"--log-level", logLevelString, "build", build.Context}
if build.Tag != "" {
args = append(args, "--tag", build.Tag)
}

stdout := new(bytes.Buffer)
if err := shell.Run("podman", nil, stdout, nil, args...); err != nil {
return "", err
}
output := strings.TrimRight(stdout.String(), "\n")
imageIdBegin := strings.LastIndex(output, "\n") + 1
imageId := output[imageIdBegin:]

var name string
if build.Tag == "" {
info, err := InspectImage(imageId)
if err != nil {
return "", err
}
name = info["Labels"].(map[string]interface{})["name"].(string)
args = []string{"--log-level", logLevelString, "tag", imageId, name}
if err := shell.Run("podman", nil, nil, nil, args...); err != nil {
return "", err
}
} else {
name = build.Tag
}

return name, nil
}

// CheckVersion compares provided version with the version of Podman.
//
// Takes in one string parameter that should be in the format that is used for versioning (eg. 1.0.0, 2.5.1-dev).
Expand Down
2 changes: 1 addition & 1 deletion src/pkg/shell/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func RunContext(ctx context.Context, name string, stdin io.Reader, stdout, stder
return err
}
if exitCode != 0 {
return fmt.Errorf("failed to invoke %s(1)", name)
return fmt.Errorf("failed to invoke %s(%d)", name, exitCode)
}
return nil
}
Expand Down
10 changes: 10 additions & 0 deletions src/pkg/utils/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ type ParseReleaseError struct {
Hint string
}

type BuildError struct {
BuildContext string
Err error
}

func (err *ContainerError) Error() string {
errMsg := fmt.Sprintf("%s: %s", err.Container, err.Err)
return errMsg
Expand Down Expand Up @@ -70,3 +75,8 @@ func (err *ImageError) Unwrap() error {
func (err *ParseReleaseError) Error() string {
return err.Hint
}

func (err *BuildError) Error() string {
errMsg := fmt.Sprintf("%s: %s", err.BuildContext, err.Err)
return errMsg
}
51 changes: 51 additions & 0 deletions test/system/101-create.bats
Original file line number Diff line number Diff line change
Expand Up @@ -841,3 +841,54 @@ teardown() {
assert_line --index 1 "Enter with: toolbox enter fedora-toolbox-34"
assert [ ${#lines[@]} -eq 2 ]
}

@test "create: Build an image before creating the toolbox" {
local build_context="./images/fedora/f38"

run "$TOOLBX" create --build "$build_context"
if [ "$status" -ne 0 ]
then
echo "$output"
fi

assert_line --index 0 "Created container: fedora-toolbox"
assert_line --index 1 "Enter with: toolbox enter fedora-toolbox"
assert [ ${#lines[@]} -eq 2 ]

run $PODMAN images --filter reference=localhost/fedora-toolbox
assert_success
assert [ ${#lines[@]} -eq 2 ]
}

@test "create: Build an image and tag it before creating the toolbox without repository" {
local build_context="./images/fedora/f38"
local build_tag="testbuild"

run "$TOOLBX" create --build "$build_context" --build-tag "$build_tag"
assert_success

assert_line --index 0 "Created container: $build_tag"
assert_line --index 1 "Enter with: toolbox enter $build_tag"
assert [ ${#lines[@]} -eq 2 ]

run $PODMAN images --filter reference="localhost/$build_tag"
assert_success
assert [ ${#lines[@]} -eq 2 ]
}

@test "create: Build an image and tag it before creating the toolbox with repository" {
local build_context="./images/fedora/f38"
local tag_repository="registry.fedoraproject.org"
local build_tag="testbuild"

run "$TOOLBX" create --build "$build_context" --build-tag "$tag_repository/$build_tag"
assert_success

assert_line --index 0 "Created container: $build_tag"
assert_line --index 1 "Enter with: toolbox enter $build_tag"
assert [ ${#lines[@]} -eq 2 ]

run $PODMAN images --filter reference="$tag_repository/$build_tag"
assert_success
assert [ ${#lines[@]} -eq 2 ]
}