Skip to content

Commit 0c3202e

Browse files
committed
cmd: add install command
The install command runs bootc install inside the installation VM. In this way, it is possible to run privileged command using rootless podman. The install command requires to specify: - the bootc image to use for the installation - the configuration directory where to find the config.toml for configuring the output image - the container storage directory, this is passed up to the remote container in order to perform the bootc installation using the container image - the output directory where to locate the build artifacts - the name of the output disk image Example: $ podman-bootc install --bootc-image quay.io/centos-bootc/centos-bootc:stream9 \ --output-dir $(pwd)/output --output-image output.qcow2 --config-dir $(pwd)/config \ -- bootc install to-disk /dev/disk/by-id/virtio-output --wipe Signed-off-by: Alice Frosi <[email protected]>
1 parent 2893086 commit 0c3202e

File tree

1 file changed

+178
-0
lines changed

1 file changed

+178
-0
lines changed

cmd/install.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
filepath "path/filepath"
8+
9+
"github.com/containers/podman-bootc/pkg/podman"
10+
"github.com/containers/podman-bootc/pkg/vm"
11+
"github.com/containers/podman-bootc/pkg/vm/domain"
12+
"github.com/containers/podman/v5/pkg/bindings"
13+
"github.com/spf13/cobra"
14+
log "github.com/sirupsen/logrus"
15+
)
16+
17+
type installCmd struct {
18+
image string
19+
bootcCmdLine []string
20+
artifactsDir string
21+
diskPath string
22+
ctx context.Context
23+
socket string
24+
podmanSocketDir string
25+
libvirtDir string
26+
outputImage string
27+
containerStorage string
28+
configPath string
29+
outputPath string
30+
installVM *vm.InstallVM
31+
}
32+
33+
func filterCmdlineArgs(args []string) ([]string, error) {
34+
sepIndex := -1
35+
for i, arg := range args {
36+
if arg == "--" {
37+
sepIndex = i
38+
break
39+
}
40+
}
41+
if sepIndex == -1 {
42+
return nil, fmt.Errorf("no command line specified")
43+
}
44+
45+
return args[sepIndex+1:], nil
46+
}
47+
48+
func NewInstallCommand() *cobra.Command {
49+
c := installCmd{}
50+
cmd := &cobra.Command{
51+
Use: "install",
52+
Short: "Install the OS Containers",
53+
Long: "Run bootc install to build the OS Containers. Specify the bootc cmdline after the '--'",
54+
RunE: c.doInstall,
55+
}
56+
cacheDir, err := os.UserCacheDir()
57+
if err != nil {
58+
cacheDir = ""
59+
}
60+
cacheDir = filepath.Join(cacheDir, "bootc")
61+
cmd.PersistentFlags().StringVar(&c.image, "bootc-image", "", "bootc-vm container image")
62+
cmd.PersistentFlags().StringVar(&c.artifactsDir, "dir", cacheDir, "directory where the artifacts are extracted")
63+
cmd.PersistentFlags().StringVar(&c.outputPath, "output-dir", "", "directory to store the output results")
64+
cmd.PersistentFlags().StringVar(&c.outputImage, "output-image", "", "path of the image to use for the installation")
65+
cmd.PersistentFlags().StringVar(&c.configPath, "config-dir", "", "path where to find the config.toml")
66+
cmd.PersistentFlags().StringVar(&c.containerStorage, "container-storage", podman.DefaultContainerStorage(), "Container storage to use")
67+
cmd.PersistentFlags().StringVar(&c.socket, "podman-socket", podman.DefaultPodmanSocket(), "path to the podman socket")
68+
if args, err := filterCmdlineArgs(os.Args); err == nil {
69+
c.bootcCmdLine = args
70+
}
71+
72+
return cmd
73+
}
74+
75+
func init() {
76+
RootCmd.AddCommand(NewInstallCommand())
77+
}
78+
79+
func (c *installCmd) validateArgs() error {
80+
if c.image == "" {
81+
return fmt.Errorf("the bootc-image cannot be empty")
82+
}
83+
if c.artifactsDir == "" {
84+
return fmt.Errorf("the artifacts directory path cannot be empty")
85+
}
86+
if c.outputImage == "" {
87+
return fmt.Errorf("the output-image needs to be set")
88+
}
89+
if c.outputPath == "" {
90+
return fmt.Errorf("the output-path needs to be set")
91+
}
92+
if c.configPath == "" {
93+
return fmt.Errorf("the config-dir needs to be set")
94+
}
95+
if c.containerStorage == "" {
96+
return fmt.Errorf("the container storage cannot be empty")
97+
}
98+
if c.socket == "" {
99+
return fmt.Errorf("the socket for podman cannot be empty")
100+
}
101+
if len(c.bootcCmdLine) == 0 {
102+
return fmt.Errorf("the bootc commandline needs to be specified after the '--'")
103+
}
104+
var err error
105+
c.ctx, err = bindings.NewConnection(context.Background(), "unix://"+c.socket)
106+
if err != nil {
107+
return fmt.Errorf("failed to connect to podman at %s: %v", c.socket, err)
108+
}
109+
110+
return nil
111+
}
112+
113+
func (c *installCmd) installBuildVM(kernel, initrd string) error {
114+
image := filepath.Join(c.outputPath, c.outputImage)
115+
outputImageFormat, err := domain.GetDiskInfo(image)
116+
if err != nil {
117+
return err
118+
}
119+
c.installVM = vm.NewInstallVM(filepath.Join(c.libvirtDir, "virtqemud-sock"), vm.InstallOptions{
120+
OutputFormat: outputImageFormat,
121+
OutputImage: filepath.Join(vm.OutputDir, c.outputImage), // Path relative to the container filesystem
122+
Root: false,
123+
Kernel: kernel,
124+
Initrd: initrd,
125+
})
126+
if err := c.installVM.Run(); err != nil {
127+
return err
128+
}
129+
130+
return nil
131+
}
132+
133+
func (c *installCmd) doInstall(_ *cobra.Command, _ []string) error {
134+
if err := c.validateArgs(); err != nil {
135+
return err
136+
}
137+
c.libvirtDir = filepath.Join(c.artifactsDir, "libvirt")
138+
if _, err := os.Stat(c.libvirtDir); os.IsNotExist(err) {
139+
if err := os.Mkdir(c.libvirtDir, 0755); err != nil {
140+
return err
141+
}
142+
}
143+
c.podmanSocketDir = filepath.Join(c.artifactsDir, "podman")
144+
if _, err := os.Stat(c.podmanSocketDir); os.IsNotExist(err) {
145+
if err := os.Mkdir(c.podmanSocketDir, 0755); err != nil {
146+
return err
147+
}
148+
}
149+
remoteSocket := filepath.Join(c.podmanSocketDir, "podman-vm.sock")
150+
vmCont := podman.NewVMContainer(c.image, c.socket, &podman.RunVMContainerOptions{
151+
ContainerStoragePath: c.containerStorage,
152+
ConfigDir: c.configPath,
153+
OutputDir: c.outputPath,
154+
SocketDir: c.podmanSocketDir,
155+
LibvirtSocketDir: c.libvirtDir,
156+
})
157+
if err := vmCont.Run(); err != nil {
158+
return err
159+
}
160+
defer vmCont.Stop()
161+
162+
kernel, initrd, err := vmCont.GetBootArtifacts()
163+
if err != nil {
164+
return err
165+
}
166+
log.Debugf("Boot artifacts kernel: %s and initrd: %s", kernel, initrd)
167+
168+
if err := c.installBuildVM(kernel, initrd); err != nil {
169+
return err
170+
}
171+
defer c.installVM.Stop()
172+
173+
if err := podman.RunPodmanCmd(remoteSocket, c.image, c.bootcCmdLine); err != nil {
174+
return err
175+
}
176+
177+
return nil
178+
}

0 commit comments

Comments
 (0)