mirror of
https://github.com/linka-cloud/d2vm.git
synced 2026-01-25 19:15:04 +00:00
Compare commits
6 Commits
v0.1.0-rc4
...
v0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
466d6d40d3
|
|||
|
bf2687a211
|
|||
|
d652bf41f5
|
|||
|
618b5bc861
|
|||
|
8659907d62
|
|||
|
|
c66595115f |
@@ -33,6 +33,7 @@ RUN apt-get update && \
|
||||
util-linux \
|
||||
udev \
|
||||
parted \
|
||||
kpartx \
|
||||
e2fsprogs \
|
||||
mount \
|
||||
tar \
|
||||
|
||||
2
Makefile
2
Makefile
@@ -68,7 +68,7 @@ tests:
|
||||
|
||||
docs-up-to-date:
|
||||
@$(MAKE) cli-docs
|
||||
@git diff --quiet -- docs ':(exclude)docs/content/reference/d2vm_run_qemu.md' || (git --no-pager diff -- docs ':(exclude)docs/content/reference/d2vm_run_qemu.md'; echo "Please regenerate the documentation with 'make docs'"; exit 1)
|
||||
@git diff --quiet -- docs ':(exclude)docs/content/reference/d2vm_run_qemu.md' || (git --no-pager diff -- docs ':(exclude)docs/content/reference/d2vm_run_qemu.md'; echo "Please regenerate the documentation with 'make cli-docs'"; exit 1)
|
||||
|
||||
check-fmt:
|
||||
@[ "$(gofmt -l $(find . -name '*.go') 2>&1)" = "" ]
|
||||
|
||||
24
README.md
24
README.md
@@ -154,20 +154,21 @@ Usage:
|
||||
|
||||
Flags:
|
||||
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
|
||||
-f, --force Override output qcow2 image
|
||||
--force Override output qcow2 image
|
||||
-h, --help help for convert
|
||||
--network-manager string Network manager to use for the image: none, netplan, ifupdown
|
||||
-o, --output string The output image, the extension determine the image format, raw will be used if none. Supported formats: qcow2 qed raw vdi vhd vmdk (default "disk0.qcow2")
|
||||
-p, --password string The Root user password (default "root")
|
||||
-p, --password string Optional root user password
|
||||
--pull Always pull docker image
|
||||
--push Push the container disk image to the registry
|
||||
--raw Just convert the container to virtual machine image without installing anything more
|
||||
-s, --size string The output image size (default "10G")
|
||||
-t, --tag string Container disk Docker image tag
|
||||
|
||||
Global Flags:
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
|
||||
|
||||
```
|
||||
|
||||
Create an image based on the **ubuntu** official image:
|
||||
@@ -306,16 +307,18 @@ Flags:
|
||||
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
|
||||
--build-arg stringArray Set build-time variables
|
||||
-f, --file string Name of the Dockerfile
|
||||
--force Override output image
|
||||
--force Override output qcow2 image
|
||||
-h, --help help for build
|
||||
--network-manager string Network manager to use for the image: none, netplan, ifupdown
|
||||
-o, --output string The output image, the extension determine the image format, raw will be used if none. Supported formats: qcow2 qed raw vdi vhd vmdk (default "disk0.qcow2")
|
||||
-p, --password string Root user password (default "root")
|
||||
-p, --password string Optional root user password
|
||||
--push Push the container disk image to the registry
|
||||
--raw Just convert the container to virtual machine image without installing anything more
|
||||
-s, --size string The output image size (default "10G")
|
||||
-t, --tag string Container disk Docker image tag
|
||||
|
||||
Global Flags:
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
|
||||
```
|
||||
@@ -330,6 +333,13 @@ Or if you want to create a VirtualBox image:
|
||||
sudo d2vm build -p MyP4Ssw0rd -f ubuntu.Dockerfile -o ubuntu.vdi .
|
||||
```
|
||||
|
||||
### KubeVirt Container Disk Images
|
||||
|
||||
Using the `--tag` flag with the `build` and `convert` commands, you can create a
|
||||
[Container Disk Image](https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk) for [KubeVirt](https://kubevirt.io/).
|
||||
|
||||
The `--push` flag will push the image to the registry.
|
||||
|
||||
### Complete example
|
||||
|
||||
A complete example setting up a ZSH workstation is available in the [examples/full](examples/full/README.md) directory.
|
||||
|
||||
@@ -96,6 +96,8 @@ func sysconfig(osRelease OSRelease) (string, error) {
|
||||
return syslinuxCfgUbuntu, nil
|
||||
case ReleaseDebian:
|
||||
return syslinuxCfgDebian, nil
|
||||
case ReleaseKali:
|
||||
return syslinuxCfgDebian, nil
|
||||
case ReleaseAlpine:
|
||||
return syslinuxCfgAlpine, nil
|
||||
case ReleaseCentOS:
|
||||
@@ -263,10 +265,10 @@ func (b *builder) mountImg(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
b.loDevice = strings.TrimSuffix(o, "\n")
|
||||
if err := exec.Run(ctx, "partprobe", b.loDevice); err != nil {
|
||||
if err := exec.Run(ctx, "kpartx", "-a", b.loDevice); err != nil {
|
||||
return err
|
||||
}
|
||||
b.loPart = fmt.Sprintf("%sp1", b.loDevice)
|
||||
b.loPart = fmt.Sprintf("/dev/mapper/%sp1", filepath.Base(b.loDevice))
|
||||
logrus.Infof("creating raw image file system")
|
||||
if err := exec.Run(ctx, "mkfs.ext4", b.loPart); err != nil {
|
||||
return err
|
||||
@@ -283,6 +285,9 @@ func (b *builder) unmountImg(ctx context.Context) error {
|
||||
if err := exec.Run(ctx, "umount", b.mntPoint); err != nil {
|
||||
merr = multierr.Append(merr, err)
|
||||
}
|
||||
if err := exec.Run(ctx, "kpartx", "-d", b.loDevice); err != nil {
|
||||
merr = multierr.Append(merr, err)
|
||||
}
|
||||
if err := exec.Run(ctx, "losetup", "-d", b.loDevice); err != nil {
|
||||
merr = multierr.Append(merr, err)
|
||||
}
|
||||
|
||||
@@ -115,6 +115,18 @@ func TestSyslinuxCfg(t *testing.T) {
|
||||
initrd: "/initrd.img",
|
||||
sysconfig: syslinuxCfgDebian,
|
||||
},
|
||||
{
|
||||
image: "kalilinux/kali-rolling:latest",
|
||||
kernel: "/vmlinuz",
|
||||
initrd: "/initrd.img",
|
||||
sysconfig: syslinuxCfgDebian,
|
||||
},
|
||||
{
|
||||
image: "alpine:3.16",
|
||||
kernel: "/boot/vmlinuz-virt",
|
||||
initrd: "/boot/initramfs-virt",
|
||||
sysconfig: syslinuxCfgAlpine,
|
||||
},
|
||||
{
|
||||
image: "alpine",
|
||||
kernel: "/boot/vmlinuz-virt",
|
||||
|
||||
@@ -30,11 +30,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
file = "Dockerfile"
|
||||
tag = "d2vm-" + uuid.New().String()
|
||||
networkManager string
|
||||
buildArgs []string
|
||||
buildCmd = &cobra.Command{
|
||||
file = "Dockerfile"
|
||||
tag = "d2vm-" + uuid.New().String()
|
||||
buildArgs []string
|
||||
buildCmd = &cobra.Command{
|
||||
Use: "build [context directory]",
|
||||
Short: "Build a vm image from Dockerfile",
|
||||
Args: cobra.ExactArgs(1),
|
||||
@@ -87,6 +86,9 @@ var (
|
||||
if file == "" {
|
||||
file = filepath.Join(args[0], "Dockerfile")
|
||||
}
|
||||
if push && tag == "" {
|
||||
return fmt.Errorf("tag is required when pushing container disk image")
|
||||
}
|
||||
if _, err := os.Stat(output); err == nil || !os.IsNotExist(err) {
|
||||
if !force {
|
||||
return fmt.Errorf("%s already exists", output)
|
||||
@@ -108,11 +110,12 @@ var (
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
uid, ok := sudoUser()
|
||||
if !ok {
|
||||
return nil
|
||||
if uid, ok := sudoUser(); ok {
|
||||
if err := os.Chown(output, uid, uid); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return os.Chown(output, uid, uid)
|
||||
return maybeMakeContainerDisk(cmd.Context())
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -123,11 +126,5 @@ func init() {
|
||||
buildCmd.Flags().StringVarP(&file, "file", "f", "", "Name of the Dockerfile")
|
||||
buildCmd.Flags().StringArrayVar(&buildArgs, "build-arg", nil, "Set build-time variables")
|
||||
|
||||
buildCmd.Flags().StringVarP(&output, "output", "o", output, "The output image, the extension determine the image format, raw will be used if none. Supported formats: "+strings.Join(d2vm.OutputFormats(), " "))
|
||||
buildCmd.Flags().StringVarP(&password, "password", "p", "", "Optional root user password")
|
||||
buildCmd.Flags().StringVarP(&size, "size", "s", "10G", "The output image size")
|
||||
buildCmd.Flags().BoolVar(&force, "force", false, "Override output image")
|
||||
buildCmd.Flags().StringVar(&cmdLineExtra, "append-to-cmdline", "", "Extra kernel cmdline arguments to append to the generated one")
|
||||
buildCmd.Flags().StringVar(&networkManager, "network-manager", "", "Network manager to use for the image: none, netplan, ifupdown")
|
||||
buildCmd.Flags().BoolVar(&raw, "raw", false, "Just convert the container to virtual machine image without installing anything more")
|
||||
buildCmd.Flags().AddFlagSet(buildFlags())
|
||||
}
|
||||
|
||||
43
cmd/d2vm/container_disk.go
Normal file
43
cmd/d2vm/container_disk.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2022 Linka Cloud All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"go.linka.cloud/d2vm"
|
||||
"go.linka.cloud/d2vm/pkg/docker"
|
||||
)
|
||||
|
||||
func maybeMakeContainerDisk(ctx context.Context) error {
|
||||
if containerDiskTag == "" {
|
||||
return nil
|
||||
}
|
||||
logrus.Infof("creating container disk image %s", containerDiskTag)
|
||||
if err := d2vm.MakeContainerDisk(ctx, output, containerDiskTag); err != nil {
|
||||
return err
|
||||
}
|
||||
if !push {
|
||||
return nil
|
||||
}
|
||||
logrus.Infof("pushing container disk image %s", containerDiskTag)
|
||||
if err := docker.Push(ctx, containerDiskTag); err != nil {
|
||||
return fmt.Errorf("failed to push container disk: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -30,10 +30,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
raw bool
|
||||
pull = false
|
||||
cmdLineExtra = ""
|
||||
|
||||
convertCmd = &cobra.Command{
|
||||
Use: "convert [docker image]",
|
||||
Short: "Convert Docker image to vm image",
|
||||
@@ -60,10 +56,14 @@ var (
|
||||
if parts := strings.Split(img, ":"); len(parts) > 1 {
|
||||
img, tag = parts[0], parts[1]
|
||||
}
|
||||
img = fmt.Sprintf("%s:%s", img, tag)
|
||||
size, err := parseSize(size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if push && tag == "" {
|
||||
return fmt.Errorf("tag is required when pushing container disk image")
|
||||
}
|
||||
if _, err := os.Stat(output); err == nil || !os.IsNotExist(err) {
|
||||
if !force {
|
||||
return fmt.Errorf("%s already exists", output)
|
||||
@@ -75,9 +75,9 @@ var (
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
found = len(imgs) == 1 && imgs[0] == fmt.Sprintf("%s:%s", img, tag)
|
||||
found = len(imgs) == 1 && imgs[0] == img
|
||||
if found {
|
||||
logrus.Infof("using local image %s:%s", img, tag)
|
||||
logrus.Infof("using local image %s", img)
|
||||
}
|
||||
}
|
||||
if pull || !found {
|
||||
@@ -99,11 +99,12 @@ var (
|
||||
return err
|
||||
}
|
||||
// set user permissions on the output file if the command was run with sudo
|
||||
uid, ok := sudoUser()
|
||||
if !ok {
|
||||
return nil
|
||||
if uid, ok := sudoUser(); ok {
|
||||
if err := os.Chown(output, uid, uid); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return os.Chown(output, uid, uid)
|
||||
return maybeMakeContainerDisk(cmd.Context())
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -118,12 +119,6 @@ func parseSize(s string) (int64, error) {
|
||||
|
||||
func init() {
|
||||
convertCmd.Flags().BoolVar(&pull, "pull", false, "Always pull docker image")
|
||||
convertCmd.Flags().StringVarP(&output, "output", "o", output, "The output image, the extension determine the image format, raw will be used if none. Supported formats: "+strings.Join(d2vm.OutputFormats(), " "))
|
||||
convertCmd.Flags().StringVarP(&password, "password", "p", "", "Optional root user password")
|
||||
convertCmd.Flags().StringVarP(&size, "size", "s", "10G", "The output image size")
|
||||
convertCmd.Flags().BoolVarP(&force, "force", "f", false, "Override output qcow2 image")
|
||||
convertCmd.Flags().StringVar(&cmdLineExtra, "append-to-cmdline", "", "Extra kernel cmdline arguments to append to the generated one")
|
||||
convertCmd.Flags().StringVar(&networkManager, "network-manager", "", "Network manager to use for the image: none, netplan, ifupdown")
|
||||
convertCmd.Flags().BoolVar(&raw, "raw", false, "Just convert the container to virtual machine image without installing anything more")
|
||||
convertCmd.Flags().AddFlagSet(buildFlags())
|
||||
rootCmd.AddCommand(convertCmd)
|
||||
}
|
||||
|
||||
50
cmd/d2vm/flags.go
Normal file
50
cmd/d2vm/flags.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2022 Linka Cloud All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"go.linka.cloud/d2vm"
|
||||
)
|
||||
|
||||
var (
|
||||
output = "disk0.qcow2"
|
||||
size = "1G"
|
||||
password = ""
|
||||
force = false
|
||||
raw bool
|
||||
pull = false
|
||||
cmdLineExtra = ""
|
||||
containerDiskTag = ""
|
||||
push bool
|
||||
networkManager string
|
||||
)
|
||||
|
||||
func buildFlags() *pflag.FlagSet {
|
||||
flags := pflag.NewFlagSet("build", pflag.ExitOnError)
|
||||
flags.StringVarP(&output, "output", "o", output, "The output image, the extension determine the image format, raw will be used if none. Supported formats: "+strings.Join(d2vm.OutputFormats(), " "))
|
||||
flags.StringVarP(&password, "password", "p", "", "Optional root user password")
|
||||
flags.StringVarP(&size, "size", "s", "10G", "The output image size")
|
||||
flags.BoolVar(&force, "force", false, "Override output qcow2 image")
|
||||
flags.StringVar(&cmdLineExtra, "append-to-cmdline", "", "Extra kernel cmdline arguments to append to the generated one")
|
||||
flags.StringVar(&networkManager, "network-manager", "", "Network manager to use for the image: none, netplan, ifupdown")
|
||||
flags.BoolVar(&raw, "raw", false, "Just convert the container to virtual machine image without installing anything more")
|
||||
flags.StringVarP(&containerDiskTag, "tag", "t", "", "Container disk Docker image tag")
|
||||
flags.BoolVar(&push, "push", false, "Push the container disk image to the registry")
|
||||
return flags
|
||||
}
|
||||
@@ -34,10 +34,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
output = "disk0.qcow2"
|
||||
size = "1G"
|
||||
password = ""
|
||||
force = false
|
||||
verbose = false
|
||||
timeFormat = ""
|
||||
format = "qcow2"
|
||||
@@ -86,7 +82,7 @@ func init() {
|
||||
rootCmd.PersistentFlags().BoolVarP(&verbose, "debug", "d", false, "Enable Debug output")
|
||||
rootCmd.PersistentFlags().MarkDeprecated("debug", "use -v instead")
|
||||
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable Verbose output")
|
||||
rootCmd.PersistentFlags().StringVarP(&timeFormat, "time", "t", "none", "Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)'")
|
||||
rootCmd.PersistentFlags().StringVar(&timeFormat, "time", "none", "Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)'")
|
||||
color.NoColor = false
|
||||
logrus.StandardLogger().Formatter = &logfmtFormatter{start: time.Now()}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/svenwiltink/sparsecat"
|
||||
|
||||
"go.linka.cloud/d2vm/pkg/qemu_img"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -77,7 +79,7 @@ func Hetzner(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
func runHetzner(ctx context.Context, imgPath string, stdin io.Reader, stderr io.Writer, stdout io.Writer) error {
|
||||
i, err := QemuImgInfo(ctx, imgPath)
|
||||
i, err := qemu_img.Info(ctx, imgPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -89,11 +91,11 @@ func runHetzner(ctx context.Context, imgPath string, stdin io.Reader, stderr io.
|
||||
}
|
||||
defer os.RemoveAll(rawPath)
|
||||
logrus.Infof("converting image to raw: %s", rawPath)
|
||||
if err := QemuImgConvert(ctx, "raw", imgPath, rawPath); err != nil {
|
||||
if err := qemu_img.Convert(ctx, "raw", imgPath, rawPath); err != nil {
|
||||
return err
|
||||
}
|
||||
imgPath = rawPath
|
||||
i, err = QemuImgInfo(ctx, imgPath)
|
||||
i, err = qemu_img.Info(ctx, imgPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,24 +18,16 @@ package run
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"go.linka.cloud/d2vm"
|
||||
"go.linka.cloud/d2vm/pkg/docker"
|
||||
exec2 "go.linka.cloud/d2vm/pkg/exec"
|
||||
)
|
||||
|
||||
//go:embed sparsecat-linux-amd64
|
||||
@@ -345,86 +337,3 @@ func (p *pw) Progress() int {
|
||||
defer p.mu.RUnlock()
|
||||
return p.total
|
||||
}
|
||||
|
||||
type QemuInfo struct {
|
||||
VirtualSize int `json:"virtual-size"`
|
||||
Filename string `json:"filename"`
|
||||
Format string `json:"format"`
|
||||
ActualSize int `json:"actual-size"`
|
||||
DirtyFlag bool `json:"dirty-flag"`
|
||||
}
|
||||
|
||||
func QemuImgInfo(ctx context.Context, in string) (*QemuInfo, error) {
|
||||
var (
|
||||
o []byte
|
||||
err error
|
||||
)
|
||||
if path, _ := exec.LookPath("qemu-img"); path == "" {
|
||||
inAbs, err := filepath.Abs(in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get absolute path for %q: %v", path, err)
|
||||
}
|
||||
inMount := filepath.Dir(inAbs)
|
||||
in := filepath.Join("/in", filepath.Base(inAbs))
|
||||
o, err = exec2.CommandContext(
|
||||
ctx,
|
||||
"docker",
|
||||
"run",
|
||||
"--rm",
|
||||
"-v",
|
||||
inMount+":/in",
|
||||
"--entrypoint",
|
||||
"qemu-img",
|
||||
fmt.Sprintf("%s:%s", d2vm.Image, d2vm.Version),
|
||||
"info",
|
||||
in,
|
||||
"--output",
|
||||
"json",
|
||||
).CombinedOutput()
|
||||
} else {
|
||||
o, err = exec2.CommandContext(ctx, "qemu-img", "info", path, "--output", "json").CombinedOutput()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %s", err, string(o))
|
||||
}
|
||||
var i QemuInfo
|
||||
if err := json.Unmarshal(o, &i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &i, nil
|
||||
}
|
||||
|
||||
func QemuImgConvert(ctx context.Context, format, in, out string) error {
|
||||
if path, _ := exec.LookPath("qemu-img"); path != "" {
|
||||
return exec2.Run(ctx, "qemu-img", "convert", "-O", format, in, out)
|
||||
}
|
||||
inAbs, err := filepath.Abs(in)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path for %q: %v", in, err)
|
||||
}
|
||||
inMount := filepath.Dir(inAbs)
|
||||
in = filepath.Join("/in", filepath.Base(inAbs))
|
||||
|
||||
outAbs, err := filepath.Abs(out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path for %q: %v", out, err)
|
||||
}
|
||||
outMount := filepath.Dir(outAbs)
|
||||
out = filepath.Join("/out", filepath.Base(outAbs))
|
||||
|
||||
return docker.RunAndRemove(
|
||||
ctx,
|
||||
"-v",
|
||||
fmt.Sprintf("%s:/in", inMount),
|
||||
"-v",
|
||||
fmt.Sprintf("%s:/out", outMount),
|
||||
"--entrypoint",
|
||||
"qemu-img",
|
||||
fmt.Sprintf("%s:%s", d2vm.Image, d2vm.Version),
|
||||
"convert",
|
||||
"-O",
|
||||
format,
|
||||
in,
|
||||
out,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"go.linka.cloud/console"
|
||||
|
||||
"go.linka.cloud/d2vm/pkg/qemu_img"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -72,7 +74,7 @@ func vbox(ctx context.Context, path string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot find management binary %s: %v", vboxmanageFlag, err)
|
||||
}
|
||||
i, err := QemuImgInfo(ctx, path)
|
||||
i, err := qemu_img.Info(ctx, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get image info: %v", err)
|
||||
}
|
||||
@@ -84,7 +86,7 @@ func vbox(ctx context.Context, path string) error {
|
||||
}
|
||||
defer os.RemoveAll(vdi)
|
||||
logrus.Infof("converting image to raw: %s", vdi)
|
||||
if err := QemuImgConvert(ctx, "vdi", path, vdi); err != nil {
|
||||
if err := qemu_img.Convert(ctx, "vdi", path, vdi); err != nil {
|
||||
return err
|
||||
}
|
||||
path = vdi
|
||||
|
||||
67
container_disk.go
Normal file
67
container_disk.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2022 Linka Cloud All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package d2vm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"go.linka.cloud/d2vm/pkg/docker"
|
||||
"go.linka.cloud/d2vm/pkg/qemu_img"
|
||||
)
|
||||
|
||||
const (
|
||||
// https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk-workflow-example
|
||||
uid = 107
|
||||
containerDiskDockerfile = `FROM scratch
|
||||
|
||||
ADD --chown=%[1]d:%[1]d %[2]s /disk/
|
||||
`
|
||||
)
|
||||
|
||||
func MakeContainerDisk(ctx context.Context, path string, tag string) error {
|
||||
tmpPath := filepath.Join(os.TempDir(), "d2vm", uuid.New().String())
|
||||
if err := os.MkdirAll(tmpPath, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(tmpPath); err != nil {
|
||||
logrus.Errorf("failed to remove tmp dir %s: %v", tmpPath, err)
|
||||
}
|
||||
}()
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return err
|
||||
}
|
||||
// convert may not be needed, but this will also copy the file in the tmp dir
|
||||
qcow2 := filepath.Join(tmpPath, "disk.qcow2")
|
||||
if err := qemu_img.Convert(ctx, "qcow2", path, qcow2); err != nil {
|
||||
return err
|
||||
}
|
||||
disk := filepath.Base(qcow2)
|
||||
dockerfileContent := fmt.Sprintf(containerDiskDockerfile, uid, disk)
|
||||
dockerfile := filepath.Join(tmpPath, "Dockerfile")
|
||||
if err := os.WriteFile(dockerfile, []byte(dockerfileContent), os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to write dockerfile: %w", err)
|
||||
}
|
||||
if err := docker.Build(ctx, tag, dockerfile, tmpPath); err != nil {
|
||||
return fmt.Errorf("failed to build container disk: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -78,6 +78,9 @@ func NewDockerfile(release OSRelease, img, password string, networkManager Netwo
|
||||
case ReleaseDebian:
|
||||
d.tmpl = debianDockerfileTemplate
|
||||
net = NetworkManagerIfupdown2
|
||||
case ReleaseKali:
|
||||
d.tmpl = debianDockerfileTemplate
|
||||
net = NetworkManagerIfupdown2
|
||||
case ReleaseUbuntu:
|
||||
d.tmpl = ubuntuDockerfileTemplate
|
||||
net = NetworkManagerNetplan
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
```
|
||||
-h, --help help for d2vm
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -12,19 +12,21 @@ d2vm build [context directory] [flags]
|
||||
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
|
||||
--build-arg stringArray Set build-time variables
|
||||
-f, --file string Name of the Dockerfile
|
||||
--force Override output image
|
||||
--force Override output qcow2 image
|
||||
-h, --help help for build
|
||||
--network-manager string Network manager to use for the image: none, netplan, ifupdown
|
||||
-o, --output string The output image, the extension determine the image format, raw will be used if none. Supported formats: qcow2 qed raw vdi vhd vmdk (default "disk0.qcow2")
|
||||
-p, --password string Optional root user password
|
||||
--push Push the container disk image to the registry
|
||||
--raw Just convert the container to virtual machine image without installing anything more
|
||||
-s, --size string The output image size (default "10G")
|
||||
-t, --tag string Container disk Docker image tag
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ See each sub-command's help for details on how to use the generated script.
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ d2vm completion bash
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ d2vm completion fish [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ d2vm completion powershell [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ d2vm completion zsh [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -10,20 +10,22 @@ d2vm convert [docker image] [flags]
|
||||
|
||||
```
|
||||
--append-to-cmdline string Extra kernel cmdline arguments to append to the generated one
|
||||
-f, --force Override output qcow2 image
|
||||
--force Override output qcow2 image
|
||||
-h, --help help for convert
|
||||
--network-manager string Network manager to use for the image: none, netplan, ifupdown
|
||||
-o, --output string The output image, the extension determine the image format, raw will be used if none. Supported formats: qcow2 qed raw vdi vhd vmdk (default "disk0.qcow2")
|
||||
-p, --password string Optional root user password
|
||||
--pull Always pull docker image
|
||||
--push Push the container disk image to the registry
|
||||
--raw Just convert the container to virtual machine image without installing anything more
|
||||
-s, --size string The output image size (default "10G")
|
||||
-t, --tag string Container disk Docker image tag
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Run the virtual machine image
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ d2vm run hetzner [options] image-path [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ d2vm run qemu [options] [image-path] [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ d2vm run vbox [options] image-path [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ d2vm version [flags]
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
-t, --time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
--time string Enable formated timed output, valide formats: 'relative (rel | r)', 'full (f)' (default "none")
|
||||
-v, --verbose Enable Verbose output
|
||||
```
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -13,6 +13,7 @@ require (
|
||||
github.com/pkg/sftp v1.10.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/svenwiltink/sparsecat v1.0.0
|
||||
go.linka.cloud/console v0.0.0-20220910100646-48f9f2b8843b
|
||||
@@ -55,7 +56,6 @@ require (
|
||||
github.com/prometheus/common v0.26.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
|
||||
|
||||
@@ -34,6 +34,7 @@ const (
|
||||
ReleaseAlpine Release = "alpine"
|
||||
ReleaseCentOS Release = "centos"
|
||||
ReleaseRHEL Release = "rhel"
|
||||
ReleaseKali Release = "kali"
|
||||
)
|
||||
|
||||
type Release string
|
||||
@@ -44,6 +45,8 @@ func (r Release) Supported() bool {
|
||||
return true
|
||||
case ReleaseDebian:
|
||||
return true
|
||||
case ReleaseKali:
|
||||
return true
|
||||
case ReleaseAlpine:
|
||||
return true
|
||||
case ReleaseCentOS:
|
||||
|
||||
@@ -96,6 +96,10 @@ func Pull(ctx context.Context, tag string) error {
|
||||
return Cmd(ctx, "image", "pull", tag)
|
||||
}
|
||||
|
||||
func Push(ctx context.Context, tag string) error {
|
||||
return Cmd(ctx, "image", "push", tag)
|
||||
}
|
||||
|
||||
func RunInteractiveAndRemove(ctx context.Context, args ...string) error {
|
||||
cmd := exec.CommandContext(ctx, "docker", append([]string{"run", "--rm", "-it"}, args...)...)
|
||||
cmd.Stdin = os.Stdin
|
||||
|
||||
114
pkg/qemu_img/qemu_img.go
Normal file
114
pkg/qemu_img/qemu_img.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2022 Linka Cloud All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package qemu_img
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"go.linka.cloud/d2vm/pkg/docker"
|
||||
exec2 "go.linka.cloud/d2vm/pkg/exec"
|
||||
)
|
||||
|
||||
var (
|
||||
DockerImageName string
|
||||
DockerImageVersion string
|
||||
)
|
||||
|
||||
type ImgInfo struct {
|
||||
VirtualSize int `json:"virtual-size"`
|
||||
Filename string `json:"filename"`
|
||||
Format string `json:"format"`
|
||||
ActualSize int `json:"actual-size"`
|
||||
DirtyFlag bool `json:"dirty-flag"`
|
||||
}
|
||||
|
||||
func Info(ctx context.Context, in string) (*ImgInfo, error) {
|
||||
var (
|
||||
o []byte
|
||||
err error
|
||||
)
|
||||
if path, _ := exec.LookPath("qemu-img"); path == "" {
|
||||
inAbs, err := filepath.Abs(in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get absolute path for %q: %v", path, err)
|
||||
}
|
||||
inMount := filepath.Dir(inAbs)
|
||||
in := filepath.Join("/in", filepath.Base(inAbs))
|
||||
o, err = exec2.CommandContext(
|
||||
ctx,
|
||||
"docker",
|
||||
"run",
|
||||
"--rm",
|
||||
"-v",
|
||||
inMount+":/in",
|
||||
"--entrypoint",
|
||||
"qemu-img",
|
||||
fmt.Sprintf("%s:%s", DockerImageName, DockerImageVersion),
|
||||
"info",
|
||||
in,
|
||||
"--output",
|
||||
"json",
|
||||
).CombinedOutput()
|
||||
} else {
|
||||
o, err = exec2.CommandContext(ctx, "qemu-img", "info", in, "--output", "json").CombinedOutput()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %s", err, string(o))
|
||||
}
|
||||
var i ImgInfo
|
||||
if err := json.Unmarshal(o, &i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &i, nil
|
||||
}
|
||||
|
||||
func Convert(ctx context.Context, format, in, out string) error {
|
||||
if path, _ := exec.LookPath("qemu-img"); path != "" {
|
||||
return exec2.Run(ctx, "qemu-img", "convert", "-O", format, in, out)
|
||||
}
|
||||
inAbs, err := filepath.Abs(in)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path for %q: %v", in, err)
|
||||
}
|
||||
inMount := filepath.Dir(inAbs)
|
||||
in = filepath.Join("/in", filepath.Base(inAbs))
|
||||
|
||||
outAbs, err := filepath.Abs(out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path for %q: %v", out, err)
|
||||
}
|
||||
outMount := filepath.Dir(outAbs)
|
||||
out = filepath.Join("/out", filepath.Base(outAbs))
|
||||
|
||||
return docker.RunAndRemove(
|
||||
ctx,
|
||||
"-v",
|
||||
fmt.Sprintf("%s:/in", inMount),
|
||||
"-v",
|
||||
fmt.Sprintf("%s:/out", outMount),
|
||||
"--entrypoint",
|
||||
"qemu-img",
|
||||
fmt.Sprintf("%s:%s", DockerImageName, DockerImageVersion),
|
||||
"convert",
|
||||
"-O",
|
||||
format,
|
||||
in,
|
||||
out,
|
||||
)
|
||||
}
|
||||
@@ -6,7 +6,14 @@ RUN apk update --no-cache && \
|
||||
apk add \
|
||||
util-linux \
|
||||
linux-virt \
|
||||
{{ if ge .Release.VersionID "3.17" }} \
|
||||
busybox-openrc \
|
||||
busybox-mdev-openrc \
|
||||
busybox-extras-openrc \
|
||||
busybox-mdev-openrc \
|
||||
{{ else }}
|
||||
busybox-initscripts \
|
||||
{{ end }}
|
||||
openrc
|
||||
|
||||
RUN for s in bootmisc hostname hwclock modules networking swap sysctl urandom syslog; do rc-update add $s boot; done
|
||||
|
||||
@@ -14,8 +14,17 @@
|
||||
|
||||
package d2vm
|
||||
|
||||
import (
|
||||
"go.linka.cloud/d2vm/pkg/qemu_img"
|
||||
)
|
||||
|
||||
var (
|
||||
Version = ""
|
||||
BuildDate = ""
|
||||
Image = "linkacloud/d2vm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
qemu_img.DockerImageName = Image
|
||||
qemu_img.DockerImageVersion = Version
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user