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