Skip to content

Commit dabff97

Browse files
committed
refactor: added proxyimgutil for unified image utility interface
Signed-off-by: Horiodino <[email protected]> Only fall back to native if qemu-img is not found Signed-off-by: Praful Khanduri <[email protected]>
1 parent 6bc9e97 commit dabff97

File tree

12 files changed

+225
-121
lines changed

12 files changed

+225
-121
lines changed

cmd/limactl/disk.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"github.com/sirupsen/logrus"
1919
"github.com/spf13/cobra"
2020

21-
"github.com/lima-vm/lima/pkg/imgutil"
21+
"github.com/lima-vm/lima/pkg/imgutil/proxyimgutil"
2222
"github.com/lima-vm/lima/pkg/store"
2323
"github.com/lima-vm/lima/pkg/store/filenames"
2424
)
@@ -112,10 +112,7 @@ func diskCreateAction(cmd *cobra.Command, args []string) error {
112112

113113
// qemu may not be available, use it only if needed.
114114
dataDisk := filepath.Join(diskDir, filenames.DataDisk)
115-
diskUtil, _, err := imgutil.NewImageUtil(format)
116-
if err != nil {
117-
return err
118-
}
115+
diskUtil, _ := proxyimgutil.NewProxyImageUtil()
119116
err = diskUtil.CreateDisk(dataDisk, int(diskSize))
120117
if err != nil {
121118
rerr := os.RemoveAll(diskDir)
@@ -409,10 +406,7 @@ func diskResizeAction(cmd *cobra.Command, args []string) error {
409406

410407
// qemu may not be available, use it only if needed.
411408
dataDisk := filepath.Join(disk.Dir, filenames.DataDisk)
412-
diskUtil, _, err := imgutil.NewImageUtil(disk.Format)
413-
if err != nil {
414-
return err
415-
}
409+
diskUtil, _ := proxyimgutil.NewProxyImageUtil()
416410
err = diskUtil.ResizeDisk(dataDisk, int(diskSize))
417411
if err != nil {
418412
return fmt.Errorf("failed to resize disk %q: %w", diskName, err)

pkg/imgutil/factory.go

Lines changed: 0 additions & 16 deletions
This file was deleted.

pkg/imgutil/interface.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package imgutil
5+
6+
import (
7+
"encoding/json"
8+
"os"
9+
)
10+
11+
// ImageDiskManager defines the common operations for disk image utilities.
12+
type ImageDiskManager interface {
13+
// CreateDisk creates a new disk image with the specified size.
14+
CreateDisk(disk string, size int) error
15+
16+
// ResizeDisk resizes an existing disk image to the specified size.
17+
ResizeDisk(disk string, size int) error
18+
19+
// ConvertToRaw converts a disk image to raw format.
20+
ConvertToRaw(source, dest string, size *int64, allowSourceWithBackingFile bool) error
21+
22+
// MakeSparse makes a file sparse, starting from the specified offset.
23+
MakeSparse(f *os.File, offset int64) error
24+
}
25+
26+
type InfoChild struct {
27+
Name string `json:"name,omitempty"` // since QEMU 8.0
28+
Info Info `json:"info,omitempty"` // since QEMU 8.0
29+
}
30+
31+
type InfoFormatSpecific struct {
32+
Type string `json:"type,omitempty"` // since QEMU 1.7
33+
Data json.RawMessage `json:"data,omitempty"` // since QEMU 1.7
34+
}
35+
36+
func (sp *InfoFormatSpecific) Qcow2() *InfoFormatSpecificDataQcow2 {
37+
if sp.Type != "qcow2" {
38+
return nil
39+
}
40+
var x InfoFormatSpecificDataQcow2
41+
if err := json.Unmarshal(sp.Data, &x); err != nil {
42+
panic(err)
43+
}
44+
return &x
45+
}
46+
47+
func (sp *InfoFormatSpecific) Vmdk() *InfoFormatSpecificDataVmdk {
48+
if sp.Type != "vmdk" {
49+
return nil
50+
}
51+
var x InfoFormatSpecificDataVmdk
52+
if err := json.Unmarshal(sp.Data, &x); err != nil {
53+
panic(err)
54+
}
55+
return &x
56+
}
57+
58+
type InfoFormatSpecificDataQcow2 struct {
59+
Compat string `json:"compat,omitempty"` // since QEMU 1.7
60+
LazyRefcounts bool `json:"lazy-refcounts,omitempty"` // since QEMU 1.7
61+
Corrupt bool `json:"corrupt,omitempty"` // since QEMU 2.2
62+
RefcountBits int `json:"refcount-bits,omitempty"` // since QEMU 2.3
63+
CompressionType string `json:"compression-type,omitempty"` // since QEMU 5.1
64+
ExtendedL2 bool `json:"extended-l2,omitempty"` // since QEMU 5.2
65+
}
66+
67+
type InfoFormatSpecificDataVmdk struct {
68+
CreateType string `json:"create-type,omitempty"` // since QEMU 1.7
69+
CID int `json:"cid,omitempty"` // since QEMU 1.7
70+
ParentCID int `json:"parent-cid,omitempty"` // since QEMU 1.7
71+
Extents []InfoFormatSpecificDataVmdkExtent `json:"extents,omitempty"` // since QEMU 1.7
72+
}
73+
74+
type InfoFormatSpecificDataVmdkExtent struct {
75+
Filename string `json:"filename,omitempty"` // since QEMU 1.7
76+
Format string `json:"format,omitempty"` // since QEMU 1.7
77+
VSize int64 `json:"virtual-size,omitempty"` // since QEMU 1.7
78+
ClusterSize int `json:"cluster-size,omitempty"` // since QEMU 1.7
79+
}
80+
81+
// Info corresponds to the output of `qemu-img info --output=json FILE`.
82+
type Info struct {
83+
Filename string `json:"filename,omitempty"` // since QEMU 1.3
84+
Format string `json:"format,omitempty"` // since QEMU 1.3
85+
VSize int64 `json:"virtual-size,omitempty"` // since QEMU 1.3
86+
ActualSize int64 `json:"actual-size,omitempty"` // since QEMU 1.3
87+
DirtyFlag bool `json:"dirty-flag,omitempty"` // since QEMU 5.2
88+
ClusterSize int `json:"cluster-size,omitempty"` // since QEMU 1.3
89+
BackingFilename string `json:"backing-filename,omitempty"` // since QEMU 1.3
90+
FullBackingFilename string `json:"full-backing-filename,omitempty"` // since QEMU 1.3
91+
BackingFilenameFormat string `json:"backing-filename-format,omitempty"` // since QEMU 1.3
92+
FormatSpecific *InfoFormatSpecific `json:"format-specific,omitempty"` // since QEMU 1.7
93+
Children []InfoChild `json:"children,omitempty"` // since QEMU 8.0
94+
}
95+
96+
// InfoProvider defines the interface for obtaining disk image information.
97+
type InfoProvider interface {
98+
// Info retrieves information about a disk image
99+
Info(path string) (*Info, error)
100+
101+
// AcceptableAsBasedisk checks if a disk image is acceptable as a base disk
102+
AcceptableAsBasedisk(*Info) error
103+
}

pkg/imgutil/nativeimgutil/interface.go renamed to pkg/imgutil/nativeimgutil/util.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ package nativeimgutil
66
import (
77
"os"
88

9-
imgutil "github.com/lima-vm/lima/pkg/imgutil/qemuimgutil"
9+
"github.com/lima-vm/lima/pkg/imgutil"
1010
)
1111

12-
// NativeImageUtil is the native implementation of the proxyimgutil Interface.
12+
// NativeImageUtil is the native implementation of the imgutil Interface.
1313
type NativeImageUtil struct{}
1414

1515
// NewNativeImageUtil returns a new NativeImageUtil instance.
@@ -36,17 +36,17 @@ func (n *NativeImageUtil) MakeSparse(f *os.File, offset int64) error {
3636
return makeSparse(f, offset)
3737
}
3838

39-
// NativeInfoProvider is the native implementation of the proxyimgutil.InfoProvider.
39+
// NativeInfoProvider is the native implementation of the imgutil.InfoProvider.
4040
type NativeInfoProvider struct{}
4141

4242
// NewNativeInfoProvider returns a new NativeInfoProvider instance.
4343
func NewNativeInfoProvider() *NativeInfoProvider {
4444
return &NativeInfoProvider{}
4545
}
4646

47-
// GetInfo retrieves information about a disk image
47+
// Info retrieves information about a disk image
4848
// This is a stub implementation as the native package doesn't provide this functionality.
49-
func (n *NativeInfoProvider) GetInfo(_ string) (*imgutil.Info, error) {
49+
func (n *NativeInfoProvider) Info(_ string) (*imgutil.Info, error) {
5050
return nil, nil
5151
}
5252

pkg/imgutil/proxyimgutil/interface.go

Lines changed: 0 additions & 34 deletions
This file was deleted.

pkg/imgutil/proxyimgutil/proxyimgutil.go

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,98 @@
44
package proxyimgutil
55

66
import (
7-
"fmt"
7+
"errors"
8+
"os"
9+
"os/exec"
810

11+
"github.com/lima-vm/lima/pkg/imgutil"
912
"github.com/lima-vm/lima/pkg/imgutil/nativeimgutil"
1013
"github.com/lima-vm/lima/pkg/imgutil/qemuimgutil"
1114
)
1215

13-
// NewProxyImageUtil returns a proxy implementation for raw and qcow2 image formats.
14-
func NewProxyImageUtil(format string) (Interface, InfoProvider, error) {
15-
switch format {
16-
case "raw":
17-
return nativeimgutil.NewNativeImageUtil(), nativeimgutil.NewNativeInfoProvider(), nil
18-
case "qcow2":
19-
return qemuimgutil.NewQemuImageUtil(), qemuimgutil.NewQemuInfoProvider(), nil
20-
default:
21-
return nil, nil, fmt.Errorf("unsupported image format: %s", format)
16+
type ProxyImageDiskManager struct {
17+
qemu imgutil.ImageDiskManager
18+
native imgutil.ImageDiskManager
19+
}
20+
21+
type ProxyInfoProvider struct {
22+
qemu imgutil.InfoProvider
23+
native imgutil.InfoProvider
24+
}
25+
26+
func NewProxyImageUtil() (imgutil.ImageDiskManager, imgutil.InfoProvider) {
27+
return &ProxyImageDiskManager{
28+
qemu: qemuimgutil.NewQemuImageUtil(),
29+
native: nativeimgutil.NewNativeImageUtil(),
30+
}, &ProxyInfoProvider{
31+
qemu: qemuimgutil.NewQemuInfoProvider(),
32+
native: nativeimgutil.NewNativeInfoProvider(),
33+
}
34+
}
35+
36+
func (p *ProxyImageDiskManager) CreateDisk(disk string, size int) error {
37+
if err := p.qemu.CreateDisk(disk, size); err != nil {
38+
var exitError *exec.ExitError
39+
if errors.Is(err, exec.ErrNotFound) || (errors.As(err, &exitError) && exitError.ExitCode() == 127) {
40+
return p.native.CreateDisk(disk, size)
41+
}
42+
return err
43+
}
44+
return nil
45+
}
46+
47+
func (p *ProxyImageDiskManager) ResizeDisk(disk string, size int) error {
48+
if err := p.qemu.ResizeDisk(disk, size); err != nil {
49+
var exitError *exec.ExitError
50+
if errors.Is(err, exec.ErrNotFound) || (errors.As(err, &exitError) && exitError.ExitCode() == 127) {
51+
return p.native.ResizeDisk(disk, size)
52+
}
53+
return err
54+
}
55+
return nil
56+
}
57+
58+
func (p *ProxyImageDiskManager) ConvertToRaw(source, dest string, size *int64, allowSourceWithBackingFile bool) error {
59+
if err := p.qemu.ConvertToRaw(source, dest, size, allowSourceWithBackingFile); err != nil {
60+
var exitError *exec.ExitError
61+
if errors.Is(err, exec.ErrNotFound) || (errors.As(err, &exitError) && exitError.ExitCode() == 127) {
62+
return p.native.ConvertToRaw(source, dest, size, allowSourceWithBackingFile)
63+
}
64+
return err
65+
}
66+
return nil
67+
}
68+
69+
func (p *ProxyImageDiskManager) MakeSparse(f *os.File, offset int64) error {
70+
if err := p.qemu.MakeSparse(f, offset); err != nil {
71+
var exitError *exec.ExitError
72+
if errors.Is(err, exec.ErrNotFound) || (errors.As(err, &exitError) && exitError.ExitCode() == 127) {
73+
return p.native.MakeSparse(f, offset)
74+
}
75+
return err
76+
}
77+
return nil
78+
}
79+
80+
func (p *ProxyInfoProvider) Info(path string) (*imgutil.Info, error) {
81+
info, err := p.qemu.Info(path)
82+
if err != nil {
83+
var exitError *exec.ExitError
84+
if errors.Is(err, exec.ErrNotFound) || (errors.As(err, &exitError) && exitError.ExitCode() == 127) {
85+
return p.native.Info(path)
86+
}
87+
return nil, err
88+
}
89+
return info, nil
90+
}
91+
92+
func (p *ProxyInfoProvider) AcceptableAsBasedisk(info *imgutil.Info) error {
93+
if err := p.qemu.AcceptableAsBasedisk(info); err != nil {
94+
var exitError *exec.ExitError
95+
if errors.Is(err, exec.ErrNotFound) || (errors.As(err, &exitError) && exitError.ExitCode() == 127) {
96+
return p.native.AcceptableAsBasedisk(info)
97+
}
98+
return err
2299
}
100+
return nil
23101
}

pkg/imgutil/qemuimgutil/imgutil.go

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ import (
1414
"strconv"
1515

1616
"github.com/sirupsen/logrus"
17+
18+
"github.com/lima-vm/lima/pkg/imgutil"
1719
)
1820

1921
type InfoChild struct {
20-
Name string `json:"name,omitempty"` // since QEMU 8.0
21-
Info Info `json:"info,omitempty"` // since QEMU 8.0
22+
Name string `json:"name,omitempty"` // since QEMU 8.0
23+
Info imgutil.Info `json:"info,omitempty"` // since QEMU 8.0
2224
}
2325

2426
type InfoFormatSpecific struct {
@@ -94,21 +96,6 @@ type InfoFormatSpecificDataVmdkExtent struct {
9496
ClusterSize int `json:"cluster-size,omitempty"` // since QEMU 1.7
9597
}
9698

97-
// Info corresponds to the output of `qemu-img info --output=json FILE`.
98-
type Info struct {
99-
Filename string `json:"filename,omitempty"` // since QEMU 1.3
100-
Format string `json:"format,omitempty"` // since QEMU 1.3
101-
VSize int64 `json:"virtual-size,omitempty"` // since QEMU 1.3
102-
ActualSize int64 `json:"actual-size,omitempty"` // since QEMU 1.3
103-
DirtyFlag bool `json:"dirty-flag,omitempty"` // since QEMU 5.2
104-
ClusterSize int `json:"cluster-size,omitempty"` // since QEMU 1.3
105-
BackingFilename string `json:"backing-filename,omitempty"` // since QEMU 1.3
106-
FullBackingFilename string `json:"full-backing-filename,omitempty"` // since QEMU 1.3
107-
BackingFilenameFormat string `json:"backing-filename-format,omitempty"` // since QEMU 1.3
108-
FormatSpecific *InfoFormatSpecific `json:"format-specific,omitempty"` // since QEMU 1.7
109-
Children []InfoChild `json:"children,omitempty"` // since QEMU 8.0
110-
}
111-
11299
func convertToRaw(source, dest string) error {
113100
var stdout, stderr bytes.Buffer
114101
cmd := exec.Command("qemu-img", "convert", "-O", "raw", source, dest)
@@ -121,15 +108,15 @@ func convertToRaw(source, dest string) error {
121108
return nil
122109
}
123110

124-
func parseInfo(b []byte) (*Info, error) {
125-
var imgInfo Info
111+
func parseInfo(b []byte) (*imgutil.Info, error) {
112+
var imgInfo imgutil.Info
126113
if err := json.Unmarshal(b, &imgInfo); err != nil {
127114
return nil, err
128115
}
129116
return &imgInfo, nil
130117
}
131118

132-
func getInfo(f string) (*Info, error) {
119+
func getInfo(f string) (*imgutil.Info, error) {
133120
var stdout, stderr bytes.Buffer
134121
cmd := exec.Command("qemu-img", "info", "--output=json", "--force-share", f)
135122
cmd.Stdout = &stdout
@@ -141,7 +128,7 @@ func getInfo(f string) (*Info, error) {
141128
return parseInfo(stdout.Bytes())
142129
}
143130

144-
func acceptableAsBasedisk(info *Info) error {
131+
func acceptableAsBasedisk(info *imgutil.Info) error {
145132
switch info.Format {
146133
case "qcow2", "raw":
147134
// NOP

0 commit comments

Comments
 (0)