2
0
mirror of https://github.com/linka-cloud/d2vm.git synced 2026-01-25 19:15:04 +00:00

23 Commits

Author SHA1 Message Date
466d6d40d3 deps: go mod tidy
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-02-15 11:29:40 +01:00
bf2687a211 docs: add container disk support
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-02-15 11:06:52 +01:00
d652bf41f5 run: fix qemu-img convert path typo
build & convert: add kubevirt container disk support

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-02-15 10:43:23 +01:00
618b5bc861 use kpartx instead of partprobe (close #19)
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-02-15 10:36:05 +01:00
8659907d62 fix Alpine 3.17 support (close #16)
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-02-15 10:20:37 +01:00
Neosb
c66595115f kali linux 2022-09-19 09:28:14 +02:00
6ac85912c1 docs: document dependencies and docker install method (close #10)
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-13 15:53:55 +02:00
d18e68b138 run in docker if not root or sudo (fix #5 #9 #11)
set user permissions on image if run with sudo or in docker
run/vbox & run/hetzner: run qemu-img in docker if not available in path

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-13 15:02:38 +02:00
43f2dd5452 Makefile: install: fix missing go generate
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 12:34:34 +02:00
72413b0bac docs: add homebrew install instructions
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 12:05:27 +02:00
fb5f21f1f3 actions: run tests only if go files or templates changed
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 11:47:11 +02:00
8f1ae3a8a4 chore: remove sparsecat binary from repo
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 11:43:16 +02:00
a6163db5b8 actions: publish docs only on tag
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 11:23:50 +02:00
9d2ceb8cba docs: regenerate cli reference
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 11:21:45 +02:00
3940cd8975 BREAKING CHANGE: remove root default password, configure it only if provided (close #7)
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 11:19:51 +02:00
7ad6343e6f goreleaser: add brew support
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 10:50:50 +02:00
2cd50ff38c Makefile: fix completions command
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 10:13:41 +02:00
f855fe9c7a Makefile: add completions generation
goreleaser: fix typo, add completions to release archive

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 10:10:28 +02:00
f1557d104d fix zsh completion not working with source
README.md: add shell completion installation instructions

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 10:01:04 +02:00
7f3b3a859d Makefile: add install
README.md: improve install docs
goreleaser: add README.md to release tarball

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-12 09:23:33 +02:00
7718c533eb add pgp public key
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-11 20:02:04 +02:00
0208a2a134 chore: expose builder interface
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-11 20:01:37 +02:00
238d9a51af templates: do not set root password if empty
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-11 20:00:48 +02:00
45 changed files with 657 additions and 130 deletions

View File

@@ -43,7 +43,7 @@ jobs:
${{ runner.os }}-tests-
- name: Run tests
run: make tests
run: git --no-pager diff --exit-code HEAD~1 HEAD **/**.go templates/ || make tests
docs-up-to-date:
name: Docs up to date

View File

@@ -1,9 +1,7 @@
name: Docs
on:
push:
branches:
- docs
- main
tags: [ "v*" ]
jobs:
deploy:
runs-on: ubuntu-latest

2
.gitignore vendored
View File

@@ -13,3 +13,5 @@ images
.goreleaser.yaml
docs/build
docs-src
/completions
/cmd/d2vm/run/sparsecat-linux-amd64

View File

@@ -15,6 +15,8 @@ project_name: d2vm
before:
hooks:
- go mod tidy
- go generate ./...
- make completions
builds:
- main: ./cmd/d2vm
env:
@@ -37,6 +39,35 @@ snapshot:
name_template: "{{ .Env.VERSION }}"
release:
prerelease: auto
extra_files:
- glob: LICENCE
- glob: pgp.pub
archives:
- name_template: '{{ .ProjectName }}_{{ .Env.VERSION }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
files:
- LICENCE
- README.md
- completions/*
brews:
- name: d2vm
tap:
owner: linka-cloud
name: homebrew-tap
folder: Formula
homepage: https://github.com/linka-cloud/d2vm
description: Build Virtual Machine Image from Dockerfile or Docker image
license: Apache License 2.0
test: |
system "#{bin}/d2vm --version"
dependencies:
- name: go
type: optional
- name: git
install: |-
bin.install "d2vm"
bash_completion.install "completions/d2vm.bash" => "d2vm"
zsh_completion.install "completions/d2vm.zsh" => "_d2vm"
fish_completion.install "completions/d2vm.fish"
changelog:
sort: asc
filters:

View File

@@ -33,6 +33,7 @@ RUN apt-get update && \
util-linux \
udev \
parted \
kpartx \
e2fsprogs \
mount \
tar \

View File

@@ -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)" = "" ]
@@ -78,6 +78,10 @@ vet:
build-dev: docker-build .build
install: docker-build
@go generate ./...
@go install -ldflags "-s -w -X '$(MODULE).Version=$(VERSION)' -X '$(MODULE).BuildDate=$(shell date)'" ./cmd/d2vm
.build:
@go generate ./...
@go build -o d2vm -ldflags "-s -w -X '$(MODULE).Version=$(VERSION)' -X '$(MODULE).BuildDate=$(shell date)'" ./cmd/d2vm
@@ -98,15 +102,23 @@ build: bin
release: bin
@VERSION=$(VERSION) IMAGE=$(DOCKER_IMAGE) goreleaser release --rm-dist --parallelism 8
.PHONY: completions
completions: .build
@rm -rf completions
@mkdir -p completions
@for shell in bash zsh fish powershell; do \
./d2vm completion $$shell > completions/d2vm.$$shell; \
done
.PHONY: examples
examples: build-dev
@mkdir -p examples/build
@for f in $$(find examples -type f -name '*Dockerfile' -maxdepth 1); do \
echo "Building $$f"; \
./d2vm build -o examples/build/$$(basename $$f|cut -d'.' -f1).qcow2 -f $$f examples; \
./d2vm build -o examples/build/$$(basename $$f|cut -d'.' -f1).qcow2 -p root -f $$f examples --force; \
done
@echo "Building examples/full/Dockerfile"
@./d2vm build -o examples/build/full.qcow2 --build-arg=USER=adphi --build-arg=PASSWORD=adphi examples/full
@./d2vm build -o examples/build/full.qcow2 --build-arg=USER=adphi --build-arg=PASSWORD=adphi examples/full --force
cli-docs: .build
@rm -rf $(CLI_REFERENCE_PATH)

120
README.md
View File

@@ -19,7 +19,10 @@ Many thanks to him.
**Only building Linux Virtual Machine images is supported.**
**Starting from v0.1.0, d2vm automatically run build and convert commands inside Docker when not running on linux**.
Starting from v0.1.0, **d2vm** automatically run build and convert commands inside Docker when not running on linux
or when running without *root* privileges.
*Note: windows should be working, but is totally untested.*
## Supported VM Linux distributions:
@@ -37,15 +40,69 @@ Unsupported:
The program uses the `/etc/os-release` file to discover the Linux distribution and install the Kernel,
if the file is missing, the build cannot succeed.
Obviously, **Distroless** images are not supported.
Obviously, **Distroless** images are not supported.
## Prerequisites
### osx
- [Docker](https://docs.docker.com/get-docker/)
- [QEMU](https://www.qemu.org/download/#macos) (optional)
- [VirtualBox](https://www.virtualbox.org/wiki/Downloads) (optional)
### Linux
- [Docker](https://docs.docker.com/get-docker/)
- util-linux
- udev
- parted
- e2fsprogs
- mount
- tar
- extlinux
- qemu-utils
- [QEMU](https://www.qemu.org/download/#linux) (optional)
- [VirtualBox](https://www.virtualbox.org/wiki/Linux_Downloads) (optional)
## Getting started
### Install from release
### Install
Download the latest release for your platform from the [release page](https://github.com/linka-cloud/d2vm/releases/latest)
#### With Docker
### Install from source
*Note: this will only work if both the source context (and Dockerfile) and the output directory are somewhere inside
the directory where you run the command.*
```bash
docker pull linkacloud/d2vm:latest
alias d2vm="docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock --privileged -v \$PWD:/d2vm -w /d2vm linkacloud/d2vm:latest"
```
```bash
wich d2vm
d2vm: aliased to docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock --privileged -v $PWD:/d2vm -w /d2vm linkacloud/d2vm:latest
```
#### With Homebrew
```bash
brew install linka-cloud/tap/d2vm
```
#### From release
Download the latest release for your platform from the [release page](https://github.com/linka-cloud/d2vm/releases/latest).
Extract the tarball, then move the extracted *d2vm* binary to somewhere in your `$PATH` (`/usr/local/bin` for most users).
```bash
VERSION=$(git ls-remote --tags https://github.com/linka-cloud/d2vm |cut -d'/' -f 3|tail -n 1)
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$([ "$(uname -m)" = "x86_64" ] && echo "amd64" || echo "arm64")
curl -sL "https://github.com/linka-cloud/d2vm/releases/download/${VERSION}/d2vm_${VERSION}_${OS}_${ARCH}.tar.gz" | tar -xvz d2vm
sudo mv d2vm /usr/local/bin/
```
#### From source
Clone the git repository:
@@ -56,9 +113,34 @@ git clone https://github.com/linka-cloud/d2vm && cd d2vm
Install using the *make*, *docker* and the Go tool chain:
```bash
make build-dev && sudo cp d2vm /usr/local/bin/
make install
```
The *d2vm* binary is installed in the `$GOBIN` directory.
```bash
which d2vm
/go/bin/d2vm
```
### Generate shell completion
The *d2vm* program supports shell completion for *bash*, *zsh* and *fish*.
It can be enabled by running the following command:
```bash
source <(d2vm completion $(basename $SHELL))
```
Or you can install the completion file in the shell completion directory by following the instructions:
```bash
d2vm completion $(basename $SHELL) --help
```
### Converting an existing Docker Image to VM image:
```bash
@@ -72,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:
@@ -206,8 +289,6 @@ RUN apt update && apt install -y openssh-server && \
```
When building the vm image, *d2vm* will create a root password, so there is no need to configure it now.
Build the vm image:
The *build* command take most of its flags and arguments from the *docker build* command.
@@ -226,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
```
@@ -250,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.

View File

@@ -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:
@@ -105,6 +107,11 @@ func sysconfig(osRelease OSRelease) (string, error) {
}
}
type Builder interface {
Build(ctx context.Context) (err error)
Close() error
}
type builder struct {
osRelease OSRelease
@@ -125,7 +132,7 @@ type builder struct {
cmdLineExtra string
}
func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size int64, osRelease OSRelease, format string, cmdLineExtra string) (*builder, error) {
func NewBuilder(ctx context.Context, workdir, imgTag, disk string, size int64, osRelease OSRelease, format string, cmdLineExtra string) (Builder, error) {
if err := checkDependencies(); err != nil {
return nil, err
}
@@ -258,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
@@ -278,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)
}

View File

@@ -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",

View File

@@ -30,17 +30,16 @@ 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),
RunE: func(cmd *cobra.Command, args []string) error {
// TODO(adphi): resolve context path
if runtime.GOOS != "linux" {
if runtime.GOOS != "linux" || !isRoot() {
ctxAbsPath, err := filepath.Abs(args[0])
if err != nil {
return err
@@ -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)
@@ -96,7 +98,7 @@ var (
if err := docker.Build(cmd.Context(), tag, file, args[0], buildArgs...); err != nil {
return err
}
return d2vm.Convert(
if err := d2vm.Convert(
cmd.Context(),
tag,
d2vm.WithSize(size),
@@ -105,7 +107,15 @@ var (
d2vm.WithCmdLineExtra(cmdLineExtra),
d2vm.WithNetworkManager(d2vm.NetworkManager(networkManager)),
d2vm.WithRaw(raw),
)
); err != nil {
return err
}
if uid, ok := sudoUser(); ok {
if err := os.Chown(output, uid, uid); err != nil {
return err
}
}
return maybeMakeContainerDisk(cmd.Context())
},
}
)
@@ -116,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", "root", "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())
}

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

View File

@@ -30,17 +30,13 @@ import (
)
var (
raw bool
pull = false
cmdLineExtra = ""
convertCmd = &cobra.Command{
Use: "convert [docker image]",
Short: "Convert Docker image to vm image",
Args: cobra.ExactArgs(1),
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if runtime.GOOS != "linux" {
if runtime.GOOS != "linux" || !isRoot() {
abs, err := filepath.Abs(output)
if err != nil {
return err
@@ -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 {
@@ -86,7 +86,7 @@ var (
return err
}
}
return d2vm.Convert(
if err := d2vm.Convert(
cmd.Context(),
img,
d2vm.WithSize(size),
@@ -95,7 +95,16 @@ var (
d2vm.WithCmdLineExtra(cmdLineExtra),
d2vm.WithNetworkManager(d2vm.NetworkManager(networkManager)),
d2vm.WithRaw(raw),
)
); err != nil {
return err
}
// set user permissions on the output file if the command was run with sudo
if uid, ok := sudoUser(); ok {
if err := os.Chown(output, uid, uid); err != nil {
return err
}
}
return maybeMakeContainerDisk(cmd.Context())
},
}
)
@@ -110,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", "root", "The 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
View 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
}

View File

@@ -20,6 +20,8 @@ import (
"fmt"
"os"
"os/signal"
"runtime"
"strconv"
"strings"
"time"
@@ -32,10 +34,6 @@ import (
)
var (
output = "disk0.qcow2"
size = "1G"
password = "root"
force = false
verbose = false
timeFormat = ""
format = "qcow2"
@@ -56,6 +54,12 @@ var (
logrus.SetLevel(logrus.TraceLevel)
}
exec.SetDebug(verbose)
// make the zsh completion work when sourced with `source <(d2vm completion zsh)`
if cmd.Name() == "zsh" && cmd.Parent() != nil && cmd.Parent().Name() == "completion" {
zshHead := fmt.Sprintf("#compdef %[1]s\ncompdef _%[1]s %[1]s\n", cmd.Root().Name())
cmd.OutOrStdout().Write([]byte(zshHead))
}
},
}
)
@@ -78,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()}
}
@@ -127,3 +131,25 @@ func (f *logfmtFormatter) Format(entry *logrus.Entry) ([]byte, error) {
}
return b.Bytes(), nil
}
func isRoot() bool {
return os.Geteuid() == 0
}
func sudoUser() (uid int, sudo bool) {
// if we are not running on linux, docker handle files user's permissions,
// so we don't need to check for sudo here
if runtime.GOOS != "linux" {
return
}
v := os.Getenv("SUDO_UID")
if v == "" {
return 0, false
}
uid, err := strconv.Atoi(v)
if err != nil {
logrus.Errorf("invalid SUDO_UID: %s", v)
return 0, false
}
return uid, true
}

View File

@@ -34,7 +34,7 @@ import (
"github.com/spf13/cobra"
"github.com/svenwiltink/sparsecat"
exec2 "go.linka.cloud/d2vm/pkg/exec"
"go.linka.cloud/d2vm/pkg/qemu_img"
)
const (
@@ -79,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 := ImgInfo(ctx, imgPath)
i, err := qemu_img.Info(ctx, imgPath)
if err != nil {
return err
}
@@ -91,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 := exec2.Run(ctx, "qemu-img", "convert", "-O", "raw", imgPath, rawPath); err != nil {
if err := qemu_img.Convert(ctx, "raw", imgPath, rawPath); err != nil {
return err
}
imgPath = rawPath
i, err = ImgInfo(ctx, imgPath)
i, err = qemu_img.Info(ctx, imgPath)
if err != nil {
return err
}

View File

@@ -71,7 +71,7 @@ func init() {
// Paths and settings for disks
flags.Var(&disks, "disk", "Disk config, may be repeated. [file=]path[,size=1G][,format=qcow2]")
flags.StringVar(&data, "data", "", "String of metadata to pass to VM; error to specify both -data and -data-file")
flags.StringVar(&data, "data", "", "String of metadata to pass to VM")
// VM configuration
flags.StringVar(&accel, "accel", defaultAccel, "Choose acceleration mode. Use 'tcg' to disable it.")
@@ -91,7 +91,7 @@ func init() {
// USB devices
flags.BoolVar(&usbEnabled, "usb", false, "Enable USB controller")
flags.Var(&deviceFlags, "device", "Add USB host device(s). Format driver[,prop=value][,...] -- add device, like -device on the qemu command line.")
flags.Var(&deviceFlags, "device", "Add USB host device(s). Format driver[,prop=value][,...] -- add device, like --device on the qemu command line.")
}

Binary file not shown.

View File

@@ -18,13 +18,10 @@ package run
import (
"bufio"
"context"
_ "embed"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"
"sync"
@@ -340,23 +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 ImgInfo(ctx context.Context, path string) (*QemuInfo, error) {
o, err := exec.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
}

View File

@@ -19,7 +19,7 @@ import (
"github.com/spf13/cobra"
"go.linka.cloud/console"
exec2 "go.linka.cloud/d2vm/pkg/exec"
"go.linka.cloud/d2vm/pkg/qemu_img"
)
var (
@@ -74,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 := ImgInfo(ctx, path)
i, err := qemu_img.Info(ctx, path)
if err != nil {
return fmt.Errorf("failed to get image info: %v", err)
}
@@ -86,14 +86,14 @@ func vbox(ctx context.Context, path string) error {
}
defer os.RemoveAll(vdi)
logrus.Infof("converting image to raw: %s", vdi)
if err := exec2.Run(ctx, "qemu-img", "convert", "-O", "vdi", path, vdi); err != nil {
if err := qemu_img.Convert(ctx, "vdi", path, vdi); err != nil {
return err
}
path = vdi
}
// remove machine in case it already exists
cleanup(vboxmanage, name)
cleanup(vboxmanage, name, false)
_, out, err := manage(vboxmanage, "createvm", "--name", name, "--register")
if err != nil {
@@ -273,22 +273,26 @@ func vbox(ctx context.Context, path string) error {
return <-errs
}
func cleanup(vboxmanage string, name string) {
if _, _, err := manage(vboxmanage, "controlvm", name, "poweroff"); err != nil {
func cleanup(vboxmanage string, name string, logErrs ...bool) {
logErr := true
if len(logErrs) > 0 {
logErr = logErrs[0]
}
if _, _, err := manage(vboxmanage, "controlvm", name, "poweroff"); err != nil && logErr {
log.Errorf("controlvm poweroff error: %v", err)
}
_, out, err := manage(vboxmanage, "storageattach", name, "--storagectl", "IDE Controller", "--port", "1", "--device", "0", "--type", "hdd", "--medium", "emptydrive")
if err != nil {
if err != nil && logErr {
log.Errorf("storageattach error: %v\n%s", err, out)
}
for i := range disks {
id := strconv.Itoa(i)
_, out, err := manage(vboxmanage, "storageattach", name, "--storagectl", "SATA", "--port", "0", "--device", id, "--type", "hdd", "--medium", "emptydrive")
if err != nil {
if err != nil && logErr {
log.Errorf("storageattach error: %v\n%s", err, out)
}
}
if _, out, err = manage(vboxmanage, "unregistervm", name, "--delete"); err != nil {
if _, out, err = manage(vboxmanage, "unregistervm", name, "--delete"); err != nil && logErr {
log.Errorf("unregistervm error: %v\n%s", err, out)
}
}

67
container_disk.go Normal file
View 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
}

View File

@@ -72,15 +72,15 @@ func (d Dockerfile) Render(w io.Writer) error {
}
func NewDockerfile(release OSRelease, img, password string, networkManager NetworkManager) (Dockerfile, error) {
if password == "" {
password = "root"
}
d := Dockerfile{Release: release, Image: img, Password: password, NetworkManager: networkManager}
var net NetworkManager
switch release.ID {
case ReleaseDebian:
d.tmpl = debianDockerfileTemplate
net = NetworkManagerIfupdown2
case ReleaseKali:
d.tmpl = debianDockerfileTemplate
net = NetworkManagerIfupdown2
case ReleaseUbuntu:
d.tmpl = ubuntuDockerfileTemplate
net = NetworkManagerNetplan

View File

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

View File

@@ -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 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
```
### 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
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 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
```
### 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
```

View File

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

View File

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

View File

@@ -12,9 +12,9 @@ d2vm run qemu [options] [image-path] [flags]
--accel string Choose acceleration mode. Use 'tcg' to disable it. (default "hvf:tcg")
--arch string Type of architecture to use, e.g. x86_64, aarch64, s390x (default "x86_64")
--cpus uint Number of CPUs (default 1)
--data string String of metadata to pass to VM; error to specify both -data and -data-file
--data string String of metadata to pass to VM
--detached Set qemu container to run in the background
--device multiple-flag Add USB host device(s). Format driver[,prop=value][,...] -- add device, like -device on the qemu command line. (default A multiple flag is a type of flag that can be repeated any number of times)
--device multiple-flag Add USB host device(s). Format driver[,prop=value][,...] -- add device, like --device on the qemu command line. (default A multiple flag is a type of flag that can be repeated any number of times)
--disk disk Disk config, may be repeated. [file=]path[,size=1G][,format=qcow2] (default [])
--gui Set qemu to use video output instead of stdio
-h, --help help for qemu
@@ -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
```

View File

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

View File

@@ -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
View File

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

View File

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

51
pgp.pub Normal file
View File

@@ -0,0 +1,51 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
=3bbH
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -96,8 +96,11 @@ 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 {
logrus.Tracef("running 'docker run --rm -i -t %s'", strings.Join(args, " "))
cmd := exec.CommandContext(ctx, "docker", append([]string{"run", "--rm", "-it"}, args...)...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
@@ -129,6 +132,9 @@ func RunD2VM(ctx context.Context, image, version, in, out, cmd string, args ...s
}
a := []string{
"--privileged",
"-e",
// yes... it is kind of a dirty hack
fmt.Sprintf("SUDO_UID=%d", os.Getuid()),
"-v",
fmt.Sprintf("%s:/var/run/docker.sock", dockerSocket()),
"-v",

View File

@@ -26,8 +26,6 @@ import (
var (
Run = RunNoOut
CommandContext = exec.CommandContext
)
func SetDebug(debug bool) {
@@ -39,6 +37,11 @@ func SetDebug(debug bool) {
}
}
func CommandContext(ctx context.Context, c string, args ...string) *exec.Cmd {
logrus.Debugf("$ %s %s", c, strings.Join(args, " "))
return exec.CommandContext(ctx, c, args...)
}
func RunDebug(ctx context.Context, c string, args ...string) error {
logrus.Debugf("$ %s %s", c, strings.Join(args, " "))
cmd := exec.CommandContext(ctx, c, args...)

114
pkg/qemu_img/qemu_img.go Normal file
View 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,
)
}

View File

@@ -6,13 +6,20 @@ 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
RUN for s in devfs dmesg hwdrivers mdev; do rc-update add $s sysinit; done
RUN echo "root:{{- if .Password}}{{ .Password}}{{- else}}root{{- end}}" | chpasswd
{{ if .Password }}RUN echo "root:{{ .Password }}" | chpasswd {{ end }}
{{ if eq .NetworkManager "ifupdown"}}
RUN apk add --no-cache ifupdown-ng

View File

@@ -17,4 +17,4 @@ RUN dracut --no-hostonly --regenerate-all --force && \
ln -s $(find . -name 'vmlinuz-*') vmlinuz && \
ln -s $(find . -name 'initramfs-*.img') initrd.img
RUN echo "root:{{- if .Password}}{{ .Password}}{{- else}}root{{- end}}" | chpasswd
{{ if .Password }}RUN echo "root:{{ .Password }}" | chpasswd {{ end }}

View File

@@ -16,7 +16,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
RUN systemctl preset-all
RUN echo "root:{{- if .Password}}{{ .Password}}{{- else}}root{{- end}}" | chpasswd
{{ if .Password }}RUN echo "root:{{ .Password }}" | chpasswd {{ end }}
{{ if eq .NetworkManager "netplan" }}
RUN apt install -y netplan.io

View File

@@ -15,7 +15,7 @@ RUN apt-get update -y && \
RUN systemctl preset-all
RUN echo "root:{{- if .Password}}{{ .Password}}{{- else}}root{{- end}}" | chpasswd
{{ if .Password }}RUN echo "root:{{ .Password }}" | chpasswd {{ end }}
{{ if eq .NetworkManager "netplan" }}
RUN apt install -y netplan.io

View File

@@ -14,8 +14,17 @@
package d2vm
import (
"go.linka.cloud/d2vm/pkg/qemu_img"
)
var (
Version = ""
BuildDate = ""
Image = ""
Image = "linkacloud/d2vm"
)
func init() {
qemu_img.DockerImageName = Image
qemu_img.DockerImageVersion = Version
}