mirror of
https://github.com/linka-cloud/d2vm.git
synced 2024-11-26 01:26:25 +00:00
run: fix qemu-img convert path typo
build & convert: add kubevirt container disk support Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
parent
618b5bc861
commit
d652bf41f5
@ -32,7 +32,6 @@ import (
|
|||||||
var (
|
var (
|
||||||
file = "Dockerfile"
|
file = "Dockerfile"
|
||||||
tag = "d2vm-" + uuid.New().String()
|
tag = "d2vm-" + uuid.New().String()
|
||||||
networkManager string
|
|
||||||
buildArgs []string
|
buildArgs []string
|
||||||
buildCmd = &cobra.Command{
|
buildCmd = &cobra.Command{
|
||||||
Use: "build [context directory]",
|
Use: "build [context directory]",
|
||||||
@ -87,6 +86,9 @@ var (
|
|||||||
if file == "" {
|
if file == "" {
|
||||||
file = filepath.Join(args[0], "Dockerfile")
|
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 _, err := os.Stat(output); err == nil || !os.IsNotExist(err) {
|
||||||
if !force {
|
if !force {
|
||||||
return fmt.Errorf("%s already exists", output)
|
return fmt.Errorf("%s already exists", output)
|
||||||
@ -108,11 +110,12 @@ var (
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
uid, ok := sudoUser()
|
if uid, ok := sudoUser(); ok {
|
||||||
if !ok {
|
if err := os.Chown(output, uid, uid); err != nil {
|
||||||
return 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().StringVarP(&file, "file", "f", "", "Name of the Dockerfile")
|
||||||
buildCmd.Flags().StringArrayVar(&buildArgs, "build-arg", nil, "Set build-time variables")
|
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().AddFlagSet(buildFlags())
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
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 (
|
var (
|
||||||
raw bool
|
|
||||||
pull = false
|
|
||||||
cmdLineExtra = ""
|
|
||||||
|
|
||||||
convertCmd = &cobra.Command{
|
convertCmd = &cobra.Command{
|
||||||
Use: "convert [docker image]",
|
Use: "convert [docker image]",
|
||||||
Short: "Convert Docker image to vm image",
|
Short: "Convert Docker image to vm image",
|
||||||
@ -65,6 +61,9 @@ var (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 _, err := os.Stat(output); err == nil || !os.IsNotExist(err) {
|
||||||
if !force {
|
if !force {
|
||||||
return fmt.Errorf("%s already exists", output)
|
return fmt.Errorf("%s already exists", output)
|
||||||
@ -100,11 +99,12 @@ var (
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// set user permissions on the output file if the command was run with sudo
|
// set user permissions on the output file if the command was run with sudo
|
||||||
uid, ok := sudoUser()
|
if uid, ok := sudoUser(); ok {
|
||||||
if !ok {
|
if err := os.Chown(output, uid, uid); err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
return os.Chown(output, uid, uid)
|
}
|
||||||
|
return maybeMakeContainerDisk(cmd.Context())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -119,12 +119,6 @@ func parseSize(s string) (int64, error) {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
convertCmd.Flags().BoolVar(&pull, "pull", false, "Always pull docker image")
|
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().AddFlagSet(buildFlags())
|
||||||
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")
|
|
||||||
rootCmd.AddCommand(convertCmd)
|
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 (
|
var (
|
||||||
output = "disk0.qcow2"
|
|
||||||
size = "1G"
|
|
||||||
password = ""
|
|
||||||
force = false
|
|
||||||
verbose = false
|
verbose = false
|
||||||
timeFormat = ""
|
timeFormat = ""
|
||||||
format = "qcow2"
|
format = "qcow2"
|
||||||
@ -86,7 +82,7 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().BoolVarP(&verbose, "debug", "d", false, "Enable Debug output")
|
rootCmd.PersistentFlags().BoolVarP(&verbose, "debug", "d", false, "Enable Debug output")
|
||||||
rootCmd.PersistentFlags().MarkDeprecated("debug", "use -v instead")
|
rootCmd.PersistentFlags().MarkDeprecated("debug", "use -v instead")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable Verbose output")
|
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
|
color.NoColor = false
|
||||||
logrus.StandardLogger().Formatter = &logfmtFormatter{start: time.Now()}
|
logrus.StandardLogger().Formatter = &logfmtFormatter{start: time.Now()}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,8 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/svenwiltink/sparsecat"
|
"github.com/svenwiltink/sparsecat"
|
||||||
|
|
||||||
|
"go.linka.cloud/d2vm/pkg/qemu_img"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -89,11 +91,11 @@ func runHetzner(ctx context.Context, imgPath string, stdin io.Reader, stderr io.
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(rawPath)
|
defer os.RemoveAll(rawPath)
|
||||||
logrus.Infof("converting image to raw: %s", 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
|
return err
|
||||||
}
|
}
|
||||||
imgPath = rawPath
|
imgPath = rawPath
|
||||||
i, err = QemuImgInfo(ctx, imgPath)
|
i, err = qemu_img.Info(ctx, imgPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -18,24 +18,16 @@ package run
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"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
|
//go:embed sparsecat-linux-amd64
|
||||||
@ -345,86 +337,3 @@ func (p *pw) Progress() int {
|
|||||||
defer p.mu.RUnlock()
|
defer p.mu.RUnlock()
|
||||||
return p.total
|
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"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"go.linka.cloud/console"
|
"go.linka.cloud/console"
|
||||||
|
|
||||||
|
"go.linka.cloud/d2vm/pkg/qemu_img"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -72,7 +74,7 @@ func vbox(ctx context.Context, path string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Cannot find management binary %s: %v", vboxmanageFlag, err)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get image info: %v", err)
|
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)
|
defer os.RemoveAll(vdi)
|
||||||
logrus.Infof("converting image to raw: %s", 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
|
return err
|
||||||
}
|
}
|
||||||
path = vdi
|
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
|
||||||
|
}
|
@ -96,6 +96,10 @@ func Pull(ctx context.Context, tag string) error {
|
|||||||
return Cmd(ctx, "image", "pull", tag)
|
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 {
|
func RunInteractiveAndRemove(ctx context.Context, args ...string) error {
|
||||||
cmd := exec.CommandContext(ctx, "docker", append([]string{"run", "--rm", "-it"}, args...)...)
|
cmd := exec.CommandContext(ctx, "docker", append([]string{"run", "--rm", "-it"}, args...)...)
|
||||||
cmd.Stdin = os.Stdin
|
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,
|
||||||
|
)
|
||||||
|
}
|
@ -14,8 +14,17 @@
|
|||||||
|
|
||||||
package d2vm
|
package d2vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.linka.cloud/d2vm/pkg/qemu_img"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version = ""
|
Version = ""
|
||||||
BuildDate = ""
|
BuildDate = ""
|
||||||
Image = "linkacloud/d2vm"
|
Image = "linkacloud/d2vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
qemu_img.DockerImageName = Image
|
||||||
|
qemu_img.DockerImageVersion = Version
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user