Skip to content

Commit 3d41df5

Browse files
committed
cmd/create: Integrate cross-architecture support into container creation
Add the --arch flag to the 'create' command, allowing users to create Toolbx containers for architectures different from the host (e.g., 'toolbox create --arch arm64'). Utilize the architecture resolution pipeline in create() by using resolveArchitectureID() (added in [1]) to determine the target architecture from the --arch flag and image tags. Validate host support via IsArchSupportedOnCreation() (added in [2]), which checks for the required QEMU emulator and binfmt_misc registration. Pass architecture ID to resolveContainerAndImageNames() (updated in [1]) so that non-native containers get architecture-suffixed names. Update pullImage() to handle cross-architecture image pulling: when the target architecture is non-native, use skopeo.CopyOverrideArch() (added in [3]) instead of podman.Pull(), since Podman does not support pulling foreign architecture images into locally addressable names. The need for this is explained in a discussion in [4]. Add a 'toolbox-arch' label to created containers to record the target architecture in OCI format. Extract the image pull error formatting into createErrorImagePull() in utils.go to avoid duplication between the native and cross-arch pull paths. Update the createContainer() call in run.go to pass the default architecture config via GetArchConfigDefault(), maintaining the existing native-architecture behavior. [1] containers#1786 [2] containers#1783 [3] containers#1784 [4] podman-container-tools/podman#27780 containers#1787 Signed-off-by: Dalibor Kricka <dalidalk@seznam.cz>
1 parent d7c6aef commit 3d41df5

3 files changed

Lines changed: 73 additions & 22 deletions

File tree

src/cmd/create.go

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const (
5050

5151
var (
5252
createFlags struct {
53+
arch string
5354
authFile string
5455
container string
5556
distro string
@@ -76,6 +77,12 @@ var createCmd = &cobra.Command{
7677
func init() {
7778
flags := createCmd.Flags()
7879

80+
flags.StringVarP(&createFlags.arch,
81+
"arch",
82+
"a",
83+
"",
84+
"Create a Toolbx container for a different architecture than the host")
85+
7986
flags.StringVar(&createFlags.authFile,
8087
"authfile",
8188
"",
@@ -171,25 +178,43 @@ func create(cmd *cobra.Command, args []string) error {
171178
containerArg = "--container"
172179
}
173180

181+
var archConfig architecture.Config
182+
183+
archID, err := resolveArchitectureID(createFlags.arch, createFlags.image)
184+
if err != nil {
185+
return err
186+
}
187+
archConfig.ID = archID
188+
189+
if !architecture.HasContainerNativeArch(archConfig.ID) {
190+
archName := architecture.GetArchNameOCI(archConfig.ID)
191+
qemuEmulatorPath, err := architecture.IsArchSupportedOnCreation(archID)
192+
if err != nil {
193+
errNotSupported := fmt.Errorf("Cannot create container for architecture %s\n%s", archName, err)
194+
return errNotSupported
195+
}
196+
archConfig.QemuEmulatorPath = qemuEmulatorPath
197+
}
198+
174199
container, image, release, err := resolveContainerAndImageNames(container,
175200
containerArg,
176201
createFlags.distro,
177202
createFlags.image,
178203
createFlags.release,
179-
architecture.HostArchID)
204+
archConfig.ID)
180205

181206
if err != nil {
182207
return err
183208
}
184209

185-
if err := createContainer(container, image, release, createFlags.authFile, true); err != nil {
210+
if err := createContainer(container, image, release, createFlags.authFile, archConfig, true); err != nil {
186211
return err
187212
}
188213

189214
return nil
190215
}
191216

192-
func createContainer(container, image, release, authFile string, showCommandToEnter bool) error {
217+
func createContainer(container, image, release, authFile string, archConfig architecture.Config, showCommandToEnter bool) error {
193218
if container == "" {
194219
panic("container not specified")
195220
}
@@ -216,14 +241,19 @@ func createContainer(container, image, release, authFile string, showCommandToEn
216241
return errors.New(errMsg)
217242
}
218243

219-
pulled, err := pullImage(image, release, authFile)
244+
pulled, couldBeNonnativeArch, err := pullImage(image, release, authFile, archConfig.ID)
220245
if err != nil {
221246
return err
222247
}
248+
223249
if !pulled {
224250
return nil
225251
}
226252

253+
if couldBeNonnativeArch {
254+
image = resolveImageNameWithArchitectureSuffix(image, archConfig.ID)
255+
}
256+
227257
imageFull, err := podman.GetFullyQualifiedImageFromRepoTags(image)
228258
if err != nil {
229259
var errImage *podman.ImageError
@@ -448,6 +478,10 @@ func createContainer(container, image, release, authFile string, showCommandToEn
448478
"--label", "com.github.containers.toolbox=true",
449479
}...)
450480

481+
createArgs = append(createArgs, []string{
482+
"--label", "toolbox-arch=" + architecture.GetArchNameOCI(archConfig.ID),
483+
}...)
484+
451485
createArgs = append(createArgs, devPtsMount...)
452486

453487
createArgs = append(createArgs, []string{
@@ -663,11 +697,13 @@ func getServiceSocket(serviceName string, unitName string) (string, error) {
663697
return "", fmt.Errorf("failed to find a SOCK_STREAM socket for %s", unitName)
664698
}
665699

666-
func pullImage(image, release, authFile string) (bool, error) {
700+
func pullImage(image, release, authFile string, archID int) (bool, bool, error) {
701+
isNonNativeArch := !architecture.HasContainerNativeArch(archID)
702+
667703
if ok := utils.ImageReferenceCanBeID(image); ok {
668704
logrus.Debugf("Looking up image %s", image)
669705
if _, err := podman.ImageExists(image); err == nil {
670-
return true, nil
706+
return true, false, nil
671707
}
672708
}
673709

@@ -678,7 +714,7 @@ func pullImage(image, release, authFile string) (bool, error) {
678714
logrus.Debugf("Looking up image %s", imageLocal)
679715

680716
if _, err := podman.ImageExists(imageLocal); err == nil {
681-
return true, nil
717+
return true, false, nil
682718
}
683719
}
684720

@@ -690,13 +726,15 @@ func pullImage(image, release, authFile string) (bool, error) {
690726
var err error
691727
imageFull, err = utils.GetFullyQualifiedImageFromDistros(image, release)
692728
if err != nil {
693-
return false, fmt.Errorf("image %s not found in local storage and known registries", image)
729+
return false, false, fmt.Errorf("image %s not found in local storage and known registries", image)
694730
}
695731
}
696732

697-
logrus.Debugf("Looking up image %s", imageFull)
698-
if _, err := podman.ImageExists(imageFull); err == nil {
699-
return true, nil
733+
imageFullWithArch := resolveImageNameWithArchitectureSuffix(imageFull, archID)
734+
735+
logrus.Debugf("Looking up image %s", imageFullWithArch)
736+
if _, err := podman.ImageExists(imageFullWithArch); err == nil {
737+
return true, isNonNativeArch, nil
700738
}
701739

702740
domain := utils.ImageReferenceGetDomain(imageFull)
@@ -721,32 +759,36 @@ func pullImage(image, release, authFile string) (bool, error) {
721759
fmt.Fprintf(&builder, "Run '%s --help' for usage.", executableBase)
722760

723761
errMsg := builder.String()
724-
return false, errors.New(errMsg)
762+
return false, false, errors.New(errMsg)
725763
}
726764

727765
shouldPullImage = showPromptForDownload(imageFull)
728766
}
729767

730768
if !shouldPullImage {
731-
return false, nil
769+
return false, false, nil
732770
}
733771

734772
logrus.Debugf("Pulling image %s", imageFull)
735773

736774
s := startSpinner(fmt.Sprintf("Pulling %s: ", imageFull))
737775
defer stopSpinner(s)
738776

739-
if err := podman.Pull(imageFull, authFile); err != nil {
740-
var builder strings.Builder
741-
fmt.Fprintf(&builder, "failed to pull image %s\n", imageFull)
742-
fmt.Fprintf(&builder, "If it was a private image, log in with: podman login %s\n", domain)
743-
fmt.Fprintf(&builder, "Use '%s --verbose ...' for further details.", executableBase)
777+
if !isNonNativeArch {
778+
logrus.Debugf("'podman pull' is used for pulling image %s", imageFull)
744779

745-
errMsg := builder.String()
746-
return false, errors.New(errMsg)
780+
if err := podman.Pull(imageFull, authFile); err != nil {
781+
return false, false, createErrorImagePull(imageFull, domain)
782+
}
783+
} else {
784+
logrus.Debugf("'skopeo copy' is used for pulling non-native architecture image %s", imageFull)
785+
786+
if err := skopeo.CopyOverrideArch(imageFull, imageFullWithArch, archID, authFile); err != nil {
787+
return false, false, createErrorImagePull(imageFull, domain)
788+
}
747789
}
748790

749-
return true, nil
791+
return true, isNonNativeArch, nil
750792
}
751793

752794
func createPromptForDownload(imageFull, imageSize string) string {

src/cmd/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ func runCommand(container string,
227227
return nil
228228
}
229229

230-
if err := createContainer(container, image, release, "", false); err != nil {
230+
if err := createContainer(container, image, release, "", architecture.GetArchConfigDefault(), false); err != nil {
231231
return err
232232
}
233233
} else if containersCount == 1 && defaultContainer {

src/cmd/utils.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,15 @@ func createErrorDistroWithoutRelease(distro string) error {
294294
return errors.New(errMsg)
295295
}
296296

297+
func createErrorImagePull(image, domain string) error {
298+
var builder strings.Builder
299+
fmt.Fprintf(&builder, "failed to pull image %s\n", image)
300+
fmt.Fprintf(&builder, "If it was a private image, log in with: podman login %s\n", domain)
301+
fmt.Fprintf(&builder, "Use '%s --verbose ...' for further details.", executableBase)
302+
303+
return errors.New(builder.String())
304+
}
305+
297306
func createErrorInvalidContainer(containerArg string) error {
298307
var builder strings.Builder
299308
fmt.Fprintf(&builder, "invalid argument for '%s'\n", containerArg)

0 commit comments

Comments
 (0)