Skip to content

Commit ff78afc

Browse files
committed
disk: Refactor image pull and cache dir creation
This creates two new structs, image and cache, and removes the code to pull the image and create the cache directory from bootc_disk. This is done in order to check if the VM is running before creating the disk to fail early. We need the pulled container image ID before checking if the VM is running. This also gets us closer to separately managing the cache dir which should simplify some of the lock code. This is in preparation for the code to clear the cached disk image when modifying the disk image size or filesystem type. Prior to these changes, the disk could be recreated before checking if the VM is running. Signed-off-by: Chris Kyrouac <[email protected]>
1 parent 47a710d commit ff78afc

File tree

7 files changed

+237
-116
lines changed

7 files changed

+237
-116
lines changed

cmd/run.go

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import (
99
"time"
1010

1111
"gitlab.com/bootc-org/podman-bootc/pkg/bootc"
12+
"gitlab.com/bootc-org/podman-bootc/pkg/cache"
1213
"gitlab.com/bootc-org/podman-bootc/pkg/config"
14+
"gitlab.com/bootc-org/podman-bootc/pkg/container"
1315
"gitlab.com/bootc-org/podman-bootc/pkg/user"
1416
"gitlab.com/bootc-org/podman-bootc/pkg/utils"
1517
"gitlab.com/bootc-org/podman-bootc/pkg/vm"
@@ -100,24 +102,23 @@ func doRun(flags *cobra.Command, args []string) error {
100102
return err
101103
}
102104

103-
// create the disk image
104-
idOrName := args[0]
105-
bootcDisk := bootc.NewBootcDisk(idOrName, ctx, user)
106-
err = bootcDisk.Install(vmConfig.Quiet, diskImageConfigInstance)
107-
105+
// pull the container image
106+
containerImage := container.NewContainerImage(args[0], ctx)
107+
err = containerImage.Pull()
108108
if err != nil {
109-
return fmt.Errorf("unable to install bootc image: %w", err)
109+
return fmt.Errorf("unable to pull container image: %w", err)
110110
}
111111

112-
//start the VM
113-
println("Booting the VM...")
114-
sshPort, err := utils.GetFreeLocalTcpPort()
112+
// create the cache directory
113+
cache := cache.NewCache(containerImage.GetId(), user)
114+
err = cache.Create()
115115
if err != nil {
116-
return fmt.Errorf("unable to get free port for SSH: %w", err)
116+
return fmt.Errorf("unable to create cache: %w", err)
117117
}
118118

119+
// check if the vm is already running
119120
bootcVM, err := vm.NewVM(vm.NewVMParameters{
120-
ImageID: bootcDisk.GetImageId(),
121+
ImageID: containerImage.GetId(),
121122
User: user,
122123
LibvirtUri: config.LibvirtUri,
123124
Locking: utils.Shared,
@@ -127,11 +128,34 @@ func doRun(flags *cobra.Command, args []string) error {
127128
return fmt.Errorf("unable to initialize VM: %w", err)
128129
}
129130

131+
isRunning, err := bootcVM.IsRunning()
132+
if err != nil {
133+
return fmt.Errorf("unable to check if VM is running: %w", err)
134+
}
135+
if isRunning {
136+
return fmt.Errorf("VM already running, use the ssh command to connect to it")
137+
}
138+
139+
// create the disk image
140+
bootcDisk := bootc.NewBootcDisk(containerImage, ctx, user, cache)
141+
err = bootcDisk.Install(vmConfig.Quiet, diskImageConfigInstance)
142+
143+
if err != nil {
144+
return fmt.Errorf("unable to install bootc image: %w", err)
145+
}
146+
147+
//start the VM
148+
println("Booting the VM...")
149+
sshPort, err := utils.GetFreeLocalTcpPort()
150+
if err != nil {
151+
return fmt.Errorf("unable to get free port for SSH: %w", err)
152+
}
153+
130154
// Let's be explicit instead of relying on the defer exec order
131155
defer func() {
132156
bootcVM.CloseConnection()
133157
if err := bootcVM.Unlock(); err != nil {
134-
logrus.Warningf("unable to unlock VM %s: %v", bootcDisk.GetImageId(), err)
158+
logrus.Warningf("unable to unlock VM %s: %v", containerImage.GetId(), err)
135159
}
136160
}()
137161

@@ -153,7 +177,7 @@ func doRun(flags *cobra.Command, args []string) error {
153177
}
154178

155179
// write down the config file
156-
if err = bootcVM.WriteConfig(*bootcDisk); err != nil {
180+
if err = bootcVM.WriteConfig(*bootcDisk, containerImage); err != nil {
157181
return err
158182
}
159183

pkg/bootc/bootc_disk.go

Lines changed: 28 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@ import (
1313
"syscall"
1414
"time"
1515

16-
"gitlab.com/bootc-org/podman-bootc/pkg/config"
16+
"gitlab.com/bootc-org/podman-bootc/pkg/cache"
17+
"gitlab.com/bootc-org/podman-bootc/pkg/container"
1718
"gitlab.com/bootc-org/podman-bootc/pkg/user"
18-
"gitlab.com/bootc-org/podman-bootc/pkg/utils"
1919

2020
"github.com/containers/podman/v5/pkg/bindings/containers"
21-
"github.com/containers/podman/v5/pkg/bindings/images"
2221
"github.com/containers/podman/v5/pkg/domain/entities/types"
2322
"github.com/containers/podman/v5/pkg/specgen"
2423
"github.com/docker/go-units"
@@ -62,14 +61,11 @@ type diskFromContainerMeta struct {
6261
}
6362

6463
type BootcDisk struct {
65-
ImageNameOrId string
64+
ContainerImage container.ContainerImage
65+
Cache cache.Cache
6666
User user.User
6767
Ctx context.Context
68-
ImageId string
69-
imageData *types.ImageInspectReport
70-
RepoTag string
7168
CreatedAt time.Time
72-
Directory string
7369
file *os.File
7470
bootcInstallContainerId string
7571
}
@@ -80,40 +76,34 @@ var (
8076
instanceOnce sync.Once
8177
)
8278

83-
func NewBootcDisk(imageNameOrId string, ctx context.Context, user user.User) *BootcDisk {
79+
// NewBootcDisk creates a new BootcDisk instance
80+
//
81+
// Parameters:
82+
// - imageNameOrId: the name or id of the container image
83+
// - ctx: context for the podman machine connection
84+
// - user: the user who is running the command, determines where the disk image is stored
85+
func NewBootcDisk(containerImage container.ContainerImage, ctx context.Context, user user.User, cache cache.Cache) *BootcDisk {
8486
instanceOnce.Do(func() {
8587
instance = &BootcDisk{
86-
ImageNameOrId: imageNameOrId,
87-
Ctx: ctx,
88-
User: user,
88+
ContainerImage: containerImage,
89+
Ctx: ctx,
90+
User: user,
91+
Cache: cache,
8992
}
9093
})
9194
return instance
9295
}
9396

94-
func (p *BootcDisk) GetDirectory() string {
95-
return p.Directory
96-
}
97-
98-
func (p *BootcDisk) GetImageId() string {
99-
return p.ImageId
100-
}
101-
10297
// GetSize returns the virtual size of the disk in bytes;
10398
// this may be larger than the actual disk usage
10499
func (p *BootcDisk) GetSize() (int64, error) {
105-
st, err := os.Stat(filepath.Join(p.Directory, config.DiskImage))
100+
st, err := os.Stat(p.Cache.GetDiskPath())
106101
if err != nil {
107102
return 0, err
108103
}
109104
return st.Size(), nil
110105
}
111106

112-
// GetRepoTag returns the repository of the container image
113-
func (p *BootcDisk) GetRepoTag() string {
114-
return p.RepoTag
115-
}
116-
117107
// GetCreatedAt returns the creation time of the disk image
118108
func (p *BootcDisk) GetCreatedAt() time.Time {
119109
return p.CreatedAt
@@ -122,32 +112,6 @@ func (p *BootcDisk) GetCreatedAt() time.Time {
122112
func (p *BootcDisk) Install(quiet bool, config DiskImageConfig) (err error) {
123113
p.CreatedAt = time.Now()
124114

125-
err = p.pullImage()
126-
if err != nil {
127-
return
128-
}
129-
130-
// Create VM cache dir; one per oci bootc image
131-
p.Directory = filepath.Join(p.User.CacheDir(), p.ImageId)
132-
lock := utils.NewCacheLock(p.User.RunDir(), p.Directory)
133-
locked, err := lock.TryLock(utils.Exclusive)
134-
if err != nil {
135-
return fmt.Errorf("error locking the VM cache path: %w", err)
136-
}
137-
if !locked {
138-
return fmt.Errorf("unable to lock the VM cache path")
139-
}
140-
141-
defer func() {
142-
if err := lock.Unlock(); err != nil {
143-
logrus.Errorf("unable to unlock VM %s: %v", p.ImageId, err)
144-
}
145-
}()
146-
147-
if err := os.MkdirAll(p.Directory, os.ModePerm); err != nil {
148-
return fmt.Errorf("error while making bootc disk directory: %w", err)
149-
}
150-
151115
err = p.getOrInstallImageToDisk(quiet, config)
152116
if err != nil {
153117
return
@@ -173,7 +137,7 @@ func (p *BootcDisk) Cleanup() (err error) {
173137

174138
// getOrInstallImageToDisk checks if the disk is present and if not, installs the image to a new disk
175139
func (p *BootcDisk) getOrInstallImageToDisk(quiet bool, diskConfig DiskImageConfig) error {
176-
diskPath := filepath.Join(p.Directory, config.DiskImage)
140+
diskPath := p.Cache.GetDiskPath()
177141
f, err := os.Open(diskPath)
178142
if err != nil {
179143
if !errors.Is(err, os.ErrNotExist) {
@@ -199,8 +163,8 @@ func (p *BootcDisk) getOrInstallImageToDisk(quiet bool, diskConfig DiskImageConf
199163
return p.bootcInstallImageToDisk(quiet, diskConfig)
200164
}
201165

202-
logrus.Debugf("previous disk digest: %s current digest: %s", serializedMeta.ImageDigest, p.ImageId)
203-
if serializedMeta.ImageDigest == p.ImageId {
166+
logrus.Debugf("previous disk digest: %s current digest: %s", serializedMeta.ImageDigest, p.ContainerImage.GetId())
167+
if serializedMeta.ImageDigest == p.ContainerImage.GetId() {
204168
return nil
205169
}
206170

@@ -217,12 +181,12 @@ func align(size int64, align int64) int64 {
217181

218182
// bootcInstallImageToDisk creates a disk image from a bootc container
219183
func (p *BootcDisk) bootcInstallImageToDisk(quiet bool, diskConfig DiskImageConfig) (err error) {
220-
fmt.Printf("Executing `bootc install to-disk` from container image %s to create disk image\n", p.RepoTag)
221-
p.file, err = os.CreateTemp(p.Directory, "podman-bootc-tempdisk")
184+
fmt.Printf("Executing `bootc install to-disk` from container image %s to create disk image\n", p.ContainerImage.GetRepoTag())
185+
p.file, err = os.CreateTemp(p.Cache.GetDirectory(), "podman-bootc-tempdisk")
222186
if err != nil {
223187
return err
224188
}
225-
size := p.imageData.Size * containerSizeToDiskSizeMultiplier
189+
size := p.ContainerImage.GetSize() * containerSizeToDiskSizeMultiplier
226190
if size < diskSizeMinimum {
227191
size = diskSizeMinimum
228192
}
@@ -237,7 +201,7 @@ func (p *BootcDisk) bootcInstallImageToDisk(quiet bool, diskConfig DiskImageConf
237201
}
238202
// Round up to 4k; loopback wants at least 512b alignment
239203
size = align(size, 4096)
240-
humanContainerSize := units.HumanSize(float64(p.imageData.Size))
204+
humanContainerSize := units.HumanSize(float64(p.ContainerImage.GetSize()))
241205
humanSize := units.HumanSize(float64(size))
242206
logrus.Infof("container size: %s, disk size: %s", humanContainerSize, humanSize)
243207

@@ -257,7 +221,7 @@ func (p *BootcDisk) bootcInstallImageToDisk(quiet bool, diskConfig DiskImageConf
257221
return fmt.Errorf("failed to create disk image: %w", err)
258222
}
259223
serializedMeta := diskFromContainerMeta{
260-
ImageDigest: p.ImageId,
224+
ImageDigest: p.ContainerImage.GetId(),
261225
}
262226
buf, err := json.Marshal(serializedMeta)
263227
if err != nil {
@@ -266,8 +230,8 @@ func (p *BootcDisk) bootcInstallImageToDisk(quiet bool, diskConfig DiskImageConf
266230
if err := unix.Fsetxattr(int(p.file.Fd()), imageMetaXattr, buf, 0); err != nil {
267231
return fmt.Errorf("failed to set xattr: %w", err)
268232
}
269-
diskPath := filepath.Join(p.Directory, config.DiskImage)
270233

234+
diskPath := p.Cache.GetDiskPath()
271235
if err := os.Rename(p.file.Name(), diskPath); err != nil {
272236
return fmt.Errorf("failed to rename to %s: %w", diskPath, err)
273237
}
@@ -276,39 +240,10 @@ func (p *BootcDisk) bootcInstallImageToDisk(quiet bool, diskConfig DiskImageConf
276240
return nil
277241
}
278242

279-
// pullImage fetches the container image if not present
280-
func (p *BootcDisk) pullImage() (err error) {
281-
pullPolicy := "missing"
282-
ids, err := images.Pull(p.Ctx, p.ImageNameOrId, &images.PullOptions{Policy: &pullPolicy})
283-
if err != nil {
284-
return fmt.Errorf("failed to pull image: %w", err)
285-
}
286-
287-
if len(ids) == 0 {
288-
return fmt.Errorf("no ids returned from image pull")
289-
}
290-
291-
if len(ids) > 1 {
292-
return fmt.Errorf("multiple ids returned from image pull")
293-
}
294-
295-
image, err := images.GetImage(p.Ctx, p.ImageNameOrId, &images.GetOptions{})
296-
if err != nil {
297-
return fmt.Errorf("failed to get image: %w", err)
298-
}
299-
p.imageData = image
300-
301-
imageId := ids[0]
302-
p.ImageId = imageId
303-
p.RepoTag = image.RepoTags[0]
304-
305-
return
306-
}
307-
308243
// runInstallContainer runs the bootc installer in a container to create a disk image
309244
func (p *BootcDisk) runInstallContainer(quiet bool, config DiskImageConfig) (err error) {
310245
// Create a temporary external shell script with the contents of our losetup wrapper
311-
losetupTemp, err := os.CreateTemp(p.Directory, "losetup-wrapper")
246+
losetupTemp, err := os.CreateTemp(p.Cache.GetDirectory(), "losetup-wrapper")
312247
if err != nil {
313248
return fmt.Errorf("temp losetup wrapper: %w", err)
314249
}
@@ -393,7 +328,7 @@ func (p *BootcDisk) createInstallContainer(config DiskImageConfig, tempLosetup s
393328
Terminal: &trueDat,
394329
},
395330
ContainerStorageConfig: specgen.ContainerStorageConfig{
396-
Image: p.ImageNameOrId,
331+
Image: p.ContainerImage.ImageNameOrId,
397332
Mounts: []specs.Mount{
398333
{
399334
Source: "/var/lib/containers",
@@ -406,7 +341,7 @@ func (p *BootcDisk) createInstallContainer(config DiskImageConfig, tempLosetup s
406341
Type: "bind",
407342
},
408343
{
409-
Source: p.Directory,
344+
Source: p.Cache.GetDirectory(),
410345
Destination: "/output",
411346
Type: "bind",
412347
},

pkg/cache/cache.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package cache
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/sirupsen/logrus"
9+
"gitlab.com/bootc-org/podman-bootc/pkg/user"
10+
"gitlab.com/bootc-org/podman-bootc/pkg/utils"
11+
)
12+
13+
func NewCache(imageId string, user user.User) Cache {
14+
return Cache{
15+
ImageId: imageId,
16+
User: user,
17+
}
18+
}
19+
20+
type Cache struct {
21+
User user.User
22+
ImageId string
23+
Directory string
24+
Created bool
25+
}
26+
27+
// Create VM cache dir; one per oci bootc image
28+
func (p *Cache) Create() (err error) {
29+
p.Directory = filepath.Join(p.User.CacheDir(), p.ImageId)
30+
lock := utils.NewCacheLock(p.User.RunDir(), p.Directory)
31+
locked, err := lock.TryLock(utils.Exclusive)
32+
if err != nil {
33+
return fmt.Errorf("error locking the VM cache path: %w", err)
34+
}
35+
if !locked {
36+
return fmt.Errorf("unable to lock the VM cache path")
37+
}
38+
39+
defer func() {
40+
if err := lock.Unlock(); err != nil {
41+
logrus.Errorf("unable to unlock VM %s: %v", p.ImageId, err)
42+
}
43+
}()
44+
45+
if err := os.MkdirAll(p.Directory, os.ModePerm); err != nil {
46+
return fmt.Errorf("error while making bootc disk directory: %w", err)
47+
}
48+
49+
p.Created = true
50+
51+
return
52+
}
53+
54+
func (p *Cache) GetDirectory() string {
55+
if !p.Created {
56+
panic("cache not created")
57+
}
58+
return p.Directory
59+
}
60+
61+
func (p *Cache) GetDiskPath() string {
62+
if !p.Created {
63+
panic("cache not created")
64+
}
65+
return filepath.Join(p.GetDirectory(), "disk.raw")
66+
}

0 commit comments

Comments
 (0)