mirror of
https://github.com/linka-cloud/d2vm.git
synced 2024-11-26 01:26:25 +00:00
feat: add --raw image creation support
refactor: use Option func pattern fix: build respect the --force flag fix: compute correct in-docker input and outpout mount paths Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
parent
eb36d45c35
commit
480cae12cf
@ -15,6 +15,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -40,7 +41,41 @@ var (
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// TODO(adphi): resolve context path
|
// TODO(adphi): resolve context path
|
||||||
if runtime.GOOS != "linux" {
|
if runtime.GOOS != "linux" {
|
||||||
return docker.RunD2VM(cmd.Context(), d2vm.Image, d2vm.Version, cmd.Name(), os.Args[2:]...)
|
ctxAbsPath, err := filepath.Abs(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dockerFileAbsPath, err := filepath.Abs(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(dockerFileAbsPath, ctxAbsPath) {
|
||||||
|
return fmt.Errorf("Dockerfile must be in the context directory path")
|
||||||
|
}
|
||||||
|
outputPath, err := filepath.Abs(output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
in = ctxAbsPath
|
||||||
|
out = filepath.Dir(outputPath)
|
||||||
|
)
|
||||||
|
dargs := os.Args[2:]
|
||||||
|
for i, v := range dargs {
|
||||||
|
switch v {
|
||||||
|
case file:
|
||||||
|
rel, err := filepath.Rel(in, file)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to construct Dockerfile container paths: %w", err)
|
||||||
|
}
|
||||||
|
dargs[i] = filepath.Join("/in", rel)
|
||||||
|
case output:
|
||||||
|
dargs[i] = filepath.Join("/out", output)
|
||||||
|
case args[0]:
|
||||||
|
dargs[i] = "/in"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return docker.RunD2VM(cmd.Context(), d2vm.Image, d2vm.Version, in, out, cmd.Name(), os.Args[2:]...)
|
||||||
}
|
}
|
||||||
size, err := parseSize(size)
|
size, err := parseSize(size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -49,11 +84,25 @@ var (
|
|||||||
if file == "" {
|
if file == "" {
|
||||||
file = filepath.Join(args[0], "Dockerfile")
|
file = filepath.Join(args[0], "Dockerfile")
|
||||||
}
|
}
|
||||||
|
if _, err := os.Stat(output); err == nil || !os.IsNotExist(err) {
|
||||||
|
if !force {
|
||||||
|
return fmt.Errorf("%s already exists", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
logrus.Infof("building docker image from %s", file)
|
logrus.Infof("building docker image from %s", file)
|
||||||
if err := docker.Build(cmd.Context(), tag, file, args[0], buildArgs...); err != nil {
|
if err := docker.Build(cmd.Context(), tag, file, args[0], buildArgs...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return d2vm.Convert(cmd.Context(), tag, size, password, output, cmdLineExtra, d2vm.NetworkManager(networkManager))
|
return d2vm.Convert(
|
||||||
|
cmd.Context(),
|
||||||
|
tag,
|
||||||
|
d2vm.WithSize(size),
|
||||||
|
d2vm.WithPassword(password),
|
||||||
|
d2vm.WithOutput(output),
|
||||||
|
d2vm.WithCmdLineExtra(cmdLineExtra),
|
||||||
|
d2vm.WithNetworkManager(d2vm.NetworkManager(networkManager)),
|
||||||
|
d2vm.WithRaw(raw),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -70,4 +119,5 @@ func init() {
|
|||||||
buildCmd.Flags().BoolVar(&force, "force", false, "Override output image")
|
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(&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().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")
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
raw bool
|
||||||
pull = false
|
pull = false
|
||||||
cmdLineExtra = ""
|
cmdLineExtra = ""
|
||||||
|
|
||||||
@ -39,7 +41,19 @@ var (
|
|||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if runtime.GOOS != "linux" {
|
if runtime.GOOS != "linux" {
|
||||||
return docker.RunD2VM(cmd.Context(), d2vm.Image, d2vm.Version, cmd.Name(), os.Args[2:]...)
|
abs, err := filepath.Abs(output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out := filepath.Dir(abs)
|
||||||
|
dargs := os.Args[2:]
|
||||||
|
for i, v := range dargs {
|
||||||
|
if v == output {
|
||||||
|
dargs[i] = filepath.Join("/out", filepath.Base(output))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return docker.RunD2VM(cmd.Context(), d2vm.Image, d2vm.Version, out, out, cmd.Name(), dargs...)
|
||||||
}
|
}
|
||||||
img := args[0]
|
img := args[0]
|
||||||
tag := "latest"
|
tag := "latest"
|
||||||
@ -55,11 +69,6 @@ var (
|
|||||||
return fmt.Errorf("%s already exists", output)
|
return fmt.Errorf("%s already exists", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(output); err == nil || !os.IsNotExist(err) {
|
|
||||||
if !force {
|
|
||||||
return fmt.Errorf("%s already exists", output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
found := false
|
found := false
|
||||||
if !pull {
|
if !pull {
|
||||||
imgs, err := docker.ImageList(cmd.Context(), img)
|
imgs, err := docker.ImageList(cmd.Context(), img)
|
||||||
@ -77,7 +86,16 @@ var (
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return d2vm.Convert(cmd.Context(), img, size, password, output, cmdLineExtra, d2vm.NetworkManager(networkManager))
|
return d2vm.Convert(
|
||||||
|
cmd.Context(),
|
||||||
|
img,
|
||||||
|
d2vm.WithSize(size),
|
||||||
|
d2vm.WithPassword(password),
|
||||||
|
d2vm.WithOutput(output),
|
||||||
|
d2vm.WithCmdLineExtra(cmdLineExtra),
|
||||||
|
d2vm.WithNetworkManager(d2vm.NetworkManager(networkManager)),
|
||||||
|
d2vm.WithRaw(raw),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -98,5 +116,6 @@ func init() {
|
|||||||
convertCmd.Flags().BoolVarP(&force, "force", "f", false, "Override output qcow2 image")
|
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(&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().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)
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
60
convert.go
60
convert.go
@ -28,7 +28,11 @@ import (
|
|||||||
"go.linka.cloud/d2vm/pkg/docker"
|
"go.linka.cloud/d2vm/pkg/docker"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Convert(ctx context.Context, img string, size int64, password string, output string, cmdLineExtra string, networkManager NetworkManager) error {
|
func Convert(ctx context.Context, img string, opts ...ConvertOption) error {
|
||||||
|
o := &convertOptions{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(o)
|
||||||
|
}
|
||||||
imgUUID := uuid.New().String()
|
imgUUID := uuid.New().String()
|
||||||
tmpPath := filepath.Join(os.TempDir(), "d2vm", imgUUID)
|
tmpPath := filepath.Join(os.TempDir(), "d2vm", imgUUID)
|
||||||
if err := os.MkdirAll(tmpPath, os.ModePerm); err != nil {
|
if err := os.MkdirAll(tmpPath, os.ModePerm); err != nil {
|
||||||
@ -41,33 +45,41 @@ func Convert(ctx context.Context, img string, size int64, password string, outpu
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d, err := NewDockerfile(r, img, password, networkManager)
|
if !o.raw {
|
||||||
if err != nil {
|
d, err := NewDockerfile(r, img, o.password, o.networkManager)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Infof("docker image based on %s", d.Release.Name)
|
||||||
|
p := filepath.Join(tmpPath, docker.FormatImgName(img))
|
||||||
|
dir := filepath.Dir(p)
|
||||||
|
f, err := os.Create(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if err := d.Render(f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Infof("building kernel enabled image")
|
||||||
|
if err := docker.Build(ctx, imgUUID, p, dir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer docker.Remove(ctx, imgUUID)
|
||||||
|
} else {
|
||||||
|
// for raw images, we just tag the image with the uuid
|
||||||
|
if err := docker.Tag(ctx, img, imgUUID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer docker.Remove(ctx, imgUUID)
|
||||||
}
|
}
|
||||||
logrus.Infof("docker image based on %s", d.Release.Name)
|
|
||||||
p := filepath.Join(tmpPath, docker.FormatImgName(img))
|
|
||||||
dir := filepath.Dir(p)
|
|
||||||
f, err := os.Create(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if err := d.Render(f); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logrus.Infof("building kernel enabled image")
|
|
||||||
if err := docker.Build(ctx, imgUUID, p, dir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer docker.Remove(ctx, imgUUID)
|
|
||||||
|
|
||||||
logrus.Infof("creating vm image")
|
logrus.Infof("creating vm image")
|
||||||
format := strings.TrimPrefix(filepath.Ext(output), ".")
|
format := strings.TrimPrefix(filepath.Ext(o.output), ".")
|
||||||
if format == "" {
|
if format == "" {
|
||||||
format = "raw"
|
format = "raw"
|
||||||
}
|
}
|
||||||
b, err := NewBuilder(ctx, tmpPath, imgUUID, "", size, r, format, cmdLineExtra)
|
b, err := NewBuilder(ctx, tmpPath, imgUUID, "", o.size, r, format, o.cmdLineExtra)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -75,10 +87,10 @@ func Convert(ctx context.Context, img string, size int64, password string, outpu
|
|||||||
if err := b.Build(ctx); err != nil {
|
if err := b.Build(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := os.RemoveAll(output); err != nil {
|
if err := os.RemoveAll(o.output); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := MoveFile(filepath.Join(tmpPath, "disk0."+format), output); err != nil {
|
if err := MoveFile(filepath.Join(tmpPath, "disk0."+format), o.output); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
62
convert_options.go
Normal file
62
convert_options.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
type ConvertOption func(o *convertOptions)
|
||||||
|
|
||||||
|
type convertOptions struct {
|
||||||
|
size int64
|
||||||
|
password string
|
||||||
|
output string
|
||||||
|
cmdLineExtra string
|
||||||
|
networkManager NetworkManager
|
||||||
|
raw bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSize(size int64) ConvertOption {
|
||||||
|
return func(o *convertOptions) {
|
||||||
|
o.size = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithPassword(password string) ConvertOption {
|
||||||
|
return func(o *convertOptions) {
|
||||||
|
o.password = password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithOutput(output string) ConvertOption {
|
||||||
|
return func(o *convertOptions) {
|
||||||
|
o.output = output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithCmdLineExtra(cmdLineExtra string) ConvertOption {
|
||||||
|
return func(o *convertOptions) {
|
||||||
|
o.cmdLineExtra = cmdLineExtra
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNetworkManager(networkManager NetworkManager) ConvertOption {
|
||||||
|
return func(o *convertOptions) {
|
||||||
|
o.networkManager = networkManager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithRaw(raw bool) ConvertOption {
|
||||||
|
return func(o *convertOptions) {
|
||||||
|
o.raw = raw
|
||||||
|
}
|
||||||
|
}
|
@ -62,6 +62,19 @@ func Build(ctx context.Context, tag, dockerfile, dir string, buildArgs ...string
|
|||||||
return Cmd(ctx, args...)
|
return Cmd(ctx, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Tag(ctx context.Context, img string, tags ...string) error {
|
||||||
|
if len(tags) == 0 {
|
||||||
|
return fmt.Errorf("no tags specified")
|
||||||
|
}
|
||||||
|
args := []string{"image", "tag"}
|
||||||
|
for _, tag := range tags {
|
||||||
|
if err := Cmd(ctx, append(args, img, tag)...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func Remove(ctx context.Context, tag string) error {
|
func Remove(ctx context.Context, tag string) error {
|
||||||
return Cmd(ctx, "image", "rm", tag)
|
return Cmd(ctx, "image", "rm", tag)
|
||||||
}
|
}
|
||||||
@ -97,11 +110,17 @@ func RunAndRemove(ctx context.Context, args ...string) error {
|
|||||||
return Cmd(ctx, append([]string{"run", "--rm"}, args...)...)
|
return Cmd(ctx, append([]string{"run", "--rm"}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunD2VM(ctx context.Context, image, version, cmd string, args ...string) error {
|
func RunD2VM(ctx context.Context, image, version, in, out, cmd string, args ...string) error {
|
||||||
pwd, err := os.Getwd()
|
pwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if in == "" {
|
||||||
|
in = pwd
|
||||||
|
}
|
||||||
|
if out == "" {
|
||||||
|
out = pwd
|
||||||
|
}
|
||||||
if image == "" {
|
if image == "" {
|
||||||
image = "linkacloud/d2vm"
|
image = "linkacloud/d2vm"
|
||||||
}
|
}
|
||||||
@ -113,7 +132,9 @@ func RunD2VM(ctx context.Context, image, version, cmd string, args ...string) er
|
|||||||
"-v",
|
"-v",
|
||||||
fmt.Sprintf("%s:/var/run/docker.sock", dockerSocket()),
|
fmt.Sprintf("%s:/var/run/docker.sock", dockerSocket()),
|
||||||
"-v",
|
"-v",
|
||||||
fmt.Sprintf("%s:/d2vm", pwd),
|
fmt.Sprintf("%s:/in", in),
|
||||||
|
"-v",
|
||||||
|
fmt.Sprintf("%s:/out", out),
|
||||||
"-w",
|
"-w",
|
||||||
"/d2vm",
|
"/d2vm",
|
||||||
fmt.Sprintf("%s:%s", image, version),
|
fmt.Sprintf("%s:%s", image, version),
|
||||||
|
Loading…
Reference in New Issue
Block a user