This commit is contained in:
KHanich 2025-08-16 16:28:43 +00:00 committed by GitHub
commit be032d8a02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 199 additions and 10 deletions

View File

@ -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
@ -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

View File

@ -54,6 +54,8 @@ var (
distro string
image string
release string
build string
buildtag string
}
createToolboxShMounts = []struct {
@ -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 {
@ -147,6 +161,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
@ -174,7 +206,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

View File

@ -21,6 +21,7 @@ import (
"fmt"
"os"
"github.com/containers/toolbox/pkg/podman"
"github.com/containers/toolbox/pkg/utils"
"github.com/spf13/cobra"
)
@ -108,7 +109,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

View File

@ -24,6 +24,7 @@ import (
"os"
"strings"
"github.com/containers/toolbox/pkg/podman"
"github.com/containers/toolbox/pkg/utils"
"github.com/spf13/cobra"
)
@ -56,7 +57,7 @@ func rootRunImpl(cmd *cobra.Command, args []string) error {
return &exitError{exitCode, err}
}
container, image, release, err := resolveContainerAndImageNames("", "", "", "", "")
container, image, release, err := resolveContainerAndImageNames("", "", "", "", "", podman.BuildOptions{Context: "", Tag: ""})
if err != nil {
return err
}

View File

@ -145,7 +145,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

View File

@ -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"
@ -418,13 +419,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

View File

@ -23,7 +23,9 @@ import (
"errors"
"fmt"
"io"
"os"
"strconv"
"strings"
"time"
"github.com/HarryMichal/go-version"
@ -39,6 +41,11 @@ type Image struct {
Names []string
}
type BuildOptions struct {
Context string
Tag string
}
type ImageSlice []Image
var (
@ -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
@ -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).

View File

@ -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
}

View File

@ -46,6 +46,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
@ -95,3 +100,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
}

View File

@ -1009,3 +1009,54 @@ teardown() {
assert [ ${#lines[@]} -eq 2 ]
assert [ ${#stderr_lines[@]} -eq 0 ]
}
@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 ]
}