2
0
mirror of https://github.com/linka-cloud/d2vm.git synced 2024-11-29 11:06:24 +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:
Adphi 2022-09-06 18:43:24 +02:00
parent eb36d45c35
commit 480cae12cf
Signed by: adphi
GPG Key ID: 46BE4062DB2397FF
6 changed files with 199 additions and 35 deletions

View File

@ -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")
} }

View File

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

View File

@ -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,7 +45,8 @@ 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 {
d, err := NewDockerfile(r, img, o.password, o.networkManager)
if err != nil { if err != nil {
return err return err
} }
@ -61,13 +66,20 @@ func Convert(ctx context.Context, img string, size int64, password string, outpu
return err return err
} }
defer docker.Remove(ctx, imgUUID) 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("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
View 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
}
}

View File

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