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*] [*--distro DISTRO* | *-d DISTRO*]
[*--image NAME* | *-i NAME*] [*--image NAME* | *-i NAME*]
[*--release RELEASE* | *-r RELEASE*] [*--release RELEASE* | *-r RELEASE*]
[*--build BUILDCONTEXT* | *-b BUILDCONTEXT*]
[*--build-tag TAG* | *-t TAG*]
[*CONTAINER*] [*CONTAINER*]
## DESCRIPTION ## DESCRIPTION
@ -110,6 +112,22 @@ remote registry.
Create a Toolbx container for a different operating system RELEASE than the Create a Toolbx container for a different operating system RELEASE than the
host. Cannot be used with `--image`. 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 ## EXAMPLES
### Create the default Toolbx container matching the host OS ### Create the default Toolbx container matching the host OS

View File

@ -54,6 +54,8 @@ var (
distro string distro string
image string image string
release string release string
build string
buildtag string
} }
createToolboxShMounts = []struct { createToolboxShMounts = []struct {
@ -104,6 +106,18 @@ func init() {
"", "",
"Create a Toolbx container for a different operating system release than the host") "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) createCmd.SetHelpFunc(createHelp)
if err := createCmd.RegisterFlagCompletionFunc("distro", completionDistroNames); err != nil { if err := createCmd.RegisterFlagCompletionFunc("distro", completionDistroNames); err != nil {
@ -147,6 +161,24 @@ func create(cmd *cobra.Command, args []string) error {
return errors.New(errMsg) 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 cmd.Flag("authfile").Changed {
if !utils.PathExists(createFlags.authFile) { if !utils.PathExists(createFlags.authFile) {
var builder strings.Builder var builder strings.Builder
@ -174,7 +206,8 @@ func create(cmd *cobra.Command, args []string) error {
containerArg, containerArg,
createFlags.distro, createFlags.distro,
createFlags.image, createFlags.image,
createFlags.release) createFlags.release,
podman.BuildOptions{Context: createFlags.build, Tag: createFlags.buildtag})
if err != nil { if err != nil {
return err return err

View File

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/containers/toolbox/pkg/podman"
"github.com/containers/toolbox/pkg/utils" "github.com/containers/toolbox/pkg/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -108,7 +109,8 @@ func enter(cmd *cobra.Command, args []string) error {
containerArg, containerArg,
enterFlags.distro, enterFlags.distro,
"", "",
enterFlags.release) enterFlags.release,
podman.BuildOptions{Context: "", Tag: ""})
if err != nil { if err != nil {
return err return err

View File

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

View File

@ -145,7 +145,8 @@ func run(cmd *cobra.Command, args []string) error {
"--container", "--container",
runFlags.distro, runFlags.distro,
"", "",
runFlags.release) runFlags.release,
podman.BuildOptions{Context: "", Tag: ""})
if err != nil { if err != nil {
return err return err

View File

@ -31,6 +31,7 @@ import (
"strings" "strings"
"syscall" "syscall"
"github.com/containers/toolbox/pkg/podman"
"github.com/containers/toolbox/pkg/utils" "github.com/containers/toolbox/pkg/utils"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sys/unix" "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, string, string, string, error,
) { ) {
container, image, release, err := utils.ResolveContainerAndImageNames(container, var image, release string
distroCLI, var err error
imageCLI, if buildCLI.Context == "" {
releaseCLI) 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 { if err != nil {
var errContainer *utils.ContainerError var errContainer *utils.ContainerError

View File

@ -23,7 +23,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/HarryMichal/go-version" "github.com/HarryMichal/go-version"
@ -39,6 +41,11 @@ type Image struct {
Names []string Names []string
} }
type BuildOptions struct {
Context string
Tag string
}
type ImageSlice []Image type ImageSlice []Image
var ( var (
@ -53,6 +60,12 @@ var (
LogLevel = logrus.ErrorLevel 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 { func (image *Image) FlattenNames(fillNameWithID bool) []Image {
var ret []Image var ret []Image
@ -129,6 +142,52 @@ func (images ImageSlice) Swap(i, j int) {
images[i], images[j] = images[j], images[i] 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. // 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). // 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 return err
} }
if exitCode != 0 { if exitCode != 0 {
return fmt.Errorf("failed to invoke %s(1)", name) return fmt.Errorf("failed to invoke %s(%d)", name, exitCode)
} }
return nil return nil
} }

View File

@ -46,6 +46,11 @@ type ParseReleaseError struct {
Hint string Hint string
} }
type BuildError struct {
BuildContext string
Err error
}
func (err *ContainerError) Error() string { func (err *ContainerError) Error() string {
errMsg := fmt.Sprintf("%s: %s", err.Container, err.Err) errMsg := fmt.Sprintf("%s: %s", err.Container, err.Err)
return errMsg return errMsg
@ -95,3 +100,8 @@ func (err *ImageError) Unwrap() error {
func (err *ParseReleaseError) Error() string { func (err *ParseReleaseError) Error() string {
return err.Hint 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 [ ${#lines[@]} -eq 2 ]
assert [ ${#stderr_lines[@]} -eq 0 ] 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 ]
}