Skip to content

Commit 8f7051f

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 unsing rootless podman. Signed-off-by: Alice Frosi <[email protected]>
1 parent 74b8c57 commit 8f7051f

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed

cmd/install.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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+
proxy "github.com/containers/podman-bootc/pkg/vsock"
13+
"github.com/containers/podman/v5/pkg/bindings"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
const (
18+
// TODO: change the image tag with a proper version
19+
defaultImage = "quay.io/containers/bootc-vm:latest"
20+
diskName = "disk.img"
21+
podmanSocket = "/run/user/1000/podman/podman.sock"
22+
)
23+
24+
type installCmd struct {
25+
image string
26+
vmImage string
27+
bootcCmdLine []string
28+
artifactsDir string
29+
diskPath string
30+
ctx context.Context
31+
socket string
32+
outputImage string
33+
containerStorage string
34+
configPath string
35+
outputPath string
36+
installVM *vm.InstallVM
37+
}
38+
39+
func filterCmdlineArgs(args []string) ([]string, error) {
40+
sepIndex := -1
41+
for i, arg := range args {
42+
if arg == "--" {
43+
sepIndex = i
44+
break
45+
}
46+
}
47+
if sepIndex == -1 {
48+
return nil, fmt.Errorf("no command line specified")
49+
}
50+
51+
return args[sepIndex+1:], nil
52+
}
53+
54+
func NewInstallCommand() *cobra.Command {
55+
c := installCmd{}
56+
cmd := &cobra.Command{
57+
Use: "install",
58+
Short: "Install the OS Containers",
59+
Long: "Run bootc install to build the OS Containers. Specify the bootc cmdline after the '--'",
60+
RunE: c.doInstall,
61+
}
62+
cacheDir, err := os.UserCacheDir()
63+
if err != nil {
64+
cacheDir = ""
65+
}
66+
cacheDir = filepath.Join(cacheDir, "bootc")
67+
cmd.PersistentFlags().StringVar(&c.vmImage, "bootc-vm", defaultImage, "bootc-vm container image containing the VM disk image")
68+
cmd.PersistentFlags().StringVar(&c.image, "bootc-image", "", "bootc-vm container image")
69+
cmd.PersistentFlags().StringVar(&c.artifactsDir, "dir", cacheDir, "directory where the artifacts are extracted")
70+
cmd.PersistentFlags().StringVar(&c.outputPath, "output-dir", "", "directory to store the output results")
71+
cmd.PersistentFlags().StringVar(&c.outputImage, "output-image", "", "path of the image to use for the installation")
72+
cmd.PersistentFlags().StringVar(&c.configPath, "config-dir", "", "path where to find the config.toml")
73+
cmd.PersistentFlags().StringVar(&c.containerStorage, "container-storage", podman.DefaultContainerStorage(), "Container storage to use")
74+
cmd.PersistentFlags().StringVar(&c.socket, "podman-socket", podman.DefaultPodmanSocket(), "path to the podman socket")
75+
if args, err := filterCmdlineArgs(os.Args); err == nil {
76+
c.bootcCmdLine = args
77+
}
78+
c.diskPath = filepath.Join(c.artifactsDir, diskName)
79+
80+
return cmd
81+
}
82+
83+
func init() {
84+
RootCmd.AddCommand(NewInstallCommand())
85+
}
86+
87+
func (c *installCmd) validateArgs() error {
88+
if c.image == "" {
89+
return fmt.Errorf("the bootc-image cannot be empty")
90+
}
91+
if c.artifactsDir == "" {
92+
return fmt.Errorf("the artifacts directory path cannot be empty")
93+
}
94+
if c.outputImage == "" {
95+
return fmt.Errorf("the output-image needs to be set")
96+
}
97+
absPath, err := filepath.Abs(c.outputImage)
98+
if err != nil {
99+
return fmt.Errorf("failed to get absolute path for the output image: %v", err)
100+
}
101+
c.outputImage = absPath
102+
if c.outputPath == "" {
103+
return fmt.Errorf("the output-path needs to be set")
104+
}
105+
if c.configPath == "" {
106+
return fmt.Errorf("the config-dir needs to be set")
107+
}
108+
if c.containerStorage == "" {
109+
return fmt.Errorf("the container storage cannot be empty")
110+
}
111+
if c.socket == "" {
112+
return fmt.Errorf("the socket for podman cannot be empty")
113+
}
114+
if len(c.bootcCmdLine) == 0 {
115+
return fmt.Errorf("the bootc commandline needs to be specified after the '--'")
116+
}
117+
c.ctx, err = bindings.NewConnection(context.Background(), "unix://"+c.socket)
118+
if err != nil {
119+
return fmt.Errorf("failed to connect to podman at %s: %v", c.socket, err)
120+
}
121+
122+
return nil
123+
}
124+
125+
func (c *installCmd) installBuildVM() error {
126+
inputPath := filepath.Join(c.artifactsDir, "disk.img")
127+
inputImageFormat, err := domain.GetDiskInfo(inputPath)
128+
if err != nil {
129+
return err
130+
}
131+
outputImageFormat, err := domain.GetDiskInfo(c.outputImage)
132+
if err != nil {
133+
return err
134+
}
135+
c.installVM = vm.NewInstallVM(vm.InstallOptions{
136+
DiskImage: inputPath,
137+
OutputImage: c.outputImage,
138+
InputFormat: inputImageFormat,
139+
OutputFormat: outputImageFormat,
140+
ContainerStoragePath: c.containerStorage,
141+
ConfigPath: c.configPath,
142+
OutputPath: c.outputPath,
143+
Root: false,
144+
})
145+
if err := c.installVM.Run(); err != nil {
146+
return err
147+
}
148+
149+
return nil
150+
}
151+
152+
func (c *installCmd) doInstall(_ *cobra.Command, _ []string) error {
153+
if err := c.validateArgs(); err != nil {
154+
return err
155+
}
156+
157+
if err := podman.ExtractDiskImage(c.socket, c.artifactsDir, c.vmImage); err != nil {
158+
return err
159+
}
160+
if err := c.installBuildVM(); err != nil {
161+
return err
162+
}
163+
defer c.installVM.Stop()
164+
165+
p := proxy.NewProxy(vm.CIDInstallVM, vm.VSOCKPort, filepath.Join(c.artifactsDir, "bootcvm.sock"))
166+
if err := p.Start(); err != nil {
167+
return err
168+
}
169+
defer p.Stop()
170+
171+
if err := podman.RunPodmanCmd(p.GetSocket(), c.image, c.bootcCmdLine); err != nil {
172+
return err
173+
}
174+
175+
return nil
176+
}

0 commit comments

Comments
 (0)