mirror of
https://github.com/linka-cloud/d2vm.git
synced 2026-01-25 19:15:04 +00:00
Compare commits
17 Commits
v0.1.0-rc2
...
v0.1.0-rc4
| Author | SHA1 | Date | |
|---|---|---|---|
|
6ac85912c1
|
|||
|
d18e68b138
|
|||
|
43f2dd5452
|
|||
|
72413b0bac
|
|||
|
fb5f21f1f3
|
|||
|
8f1ae3a8a4
|
|||
|
a6163db5b8
|
|||
|
9d2ceb8cba
|
|||
|
3940cd8975
|
|||
|
7ad6343e6f
|
|||
|
2cd50ff38c
|
|||
|
f855fe9c7a
|
|||
|
f1557d104d
|
|||
|
7f3b3a859d
|
|||
|
7718c533eb
|
|||
|
0208a2a134
|
|||
|
238d9a51af
|
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/docs.yaml
vendored
4
.github/workflows/docs.yaml
vendored
@@ -1,9 +1,7 @@
|
||||
name: Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- docs
|
||||
- main
|
||||
tags: [ "v*" ]
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,3 +13,5 @@ images
|
||||
.goreleaser.yaml
|
||||
docs/build
|
||||
docs-src
|
||||
/completions
|
||||
/cmd/d2vm/run/sparsecat-linux-amd64
|
||||
|
||||
@@ -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:
|
||||
|
||||
16
Makefile
16
Makefile
@@ -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)
|
||||
|
||||
96
README.md
96
README.md
@@ -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
|
||||
@@ -206,8 +288,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.
|
||||
|
||||
@@ -105,6 +105,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 +130,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
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ var (
|
||||
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
|
||||
@@ -96,7 +96,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 +105,14 @@ var (
|
||||
d2vm.WithCmdLineExtra(cmdLineExtra),
|
||||
d2vm.WithNetworkManager(d2vm.NetworkManager(networkManager)),
|
||||
d2vm.WithRaw(raw),
|
||||
)
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
uid, ok := sudoUser()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return os.Chown(output, uid, uid)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -117,7 +124,7 @@ func init() {
|
||||
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(&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")
|
||||
|
||||
@@ -40,7 +40,7 @@ var (
|
||||
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
|
||||
@@ -86,7 +86,7 @@ var (
|
||||
return err
|
||||
}
|
||||
}
|
||||
return d2vm.Convert(
|
||||
if err := d2vm.Convert(
|
||||
cmd.Context(),
|
||||
img,
|
||||
d2vm.WithSize(size),
|
||||
@@ -95,7 +95,15 @@ 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
|
||||
uid, ok := sudoUser()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return os.Chown(output, uid, uid)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -111,7 +119,7 @@ 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(&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")
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -34,7 +36,7 @@ import (
|
||||
var (
|
||||
output = "disk0.qcow2"
|
||||
size = "1G"
|
||||
password = "root"
|
||||
password = ""
|
||||
force = false
|
||||
verbose = false
|
||||
timeFormat = ""
|
||||
@@ -56,6 +58,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))
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -127,3 +135,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
|
||||
}
|
||||
|
||||
@@ -33,8 +33,6 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/svenwiltink/sparsecat"
|
||||
|
||||
exec2 "go.linka.cloud/d2vm/pkg/exec"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -79,7 +77,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 := QemuImgInfo(ctx, imgPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -91,11 +89,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 := QemuImgConvert(ctx, "raw", imgPath, rawPath); err != nil {
|
||||
return err
|
||||
}
|
||||
imgPath = rawPath
|
||||
i, err = ImgInfo(ctx, imgPath)
|
||||
i, err = QemuImgInfo(ctx, imgPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -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.
@@ -25,12 +25,17 @@ import (
|
||||
"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
|
||||
@@ -349,8 +354,36 @@ type QemuInfo struct {
|
||||
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()
|
||||
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))
|
||||
}
|
||||
@@ -360,3 +393,38 @@ func ImgInfo(ctx context.Context, path string) (*QemuInfo, error) {
|
||||
}
|
||||
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,8 +18,6 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"go.linka.cloud/console"
|
||||
|
||||
exec2 "go.linka.cloud/d2vm/pkg/exec"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -74,7 +72,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 := QemuImgInfo(ctx, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get image info: %v", err)
|
||||
}
|
||||
@@ -86,14 +84,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 := QemuImgConvert(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 +271,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +72,6 @@ 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 {
|
||||
|
||||
@@ -16,7 +16,7 @@ d2vm build [context directory] [flags]
|
||||
-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
|
||||
--raw Just convert the container to virtual machine image without installing anything more
|
||||
-s, --size string The output image size (default "10G")
|
||||
```
|
||||
|
||||
@@ -14,7 +14,7 @@ d2vm convert [docker image] [flags]
|
||||
-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
|
||||
--raw Just convert the container to virtual machine image without installing anything more
|
||||
-s, --size string The output image size (default "10G")
|
||||
|
||||
@@ -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
|
||||
|
||||
51
pgp.pub
Normal file
51
pgp.pub
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
=3bbH
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -97,7 +97,6 @@ func Pull(ctx context.Context, tag string) error {
|
||||
}
|
||||
|
||||
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 +128,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",
|
||||
|
||||
@@ -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...)
|
||||
|
||||
@@ -12,7 +12,7 @@ RUN apk update --no-cache && \
|
||||
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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,5 +17,5 @@ package d2vm
|
||||
var (
|
||||
Version = ""
|
||||
BuildDate = ""
|
||||
Image = ""
|
||||
Image = "linkacloud/d2vm"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user