mirror of
https://github.com/linka-cloud/d2vm.git
synced 2024-11-25 09:06:24 +00:00
cli: improve flags and commands
docs: add README.md and examples centos/rhel: fix dracut initramfs Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
parent
c240aac13d
commit
5b8cad6176
@ -2,4 +2,4 @@
|
||||
tests
|
||||
disk*
|
||||
qemu.sh
|
||||
*.qcow2
|
||||
**/*.qcow2
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
.idea
|
||||
tests
|
||||
disk*.qcow2
|
||||
*.qcow2
|
||||
|
||||
dist/
|
||||
.goreleaser.yaml
|
||||
|
@ -15,7 +15,6 @@ FROM ubuntu
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
|
||||
linux-image-virtual \
|
||||
util-linux \
|
||||
kpartx \
|
||||
e2fsprogs \
|
||||
@ -29,3 +28,5 @@ RUN apt-get update && \
|
||||
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
||||
|
||||
COPY --from=builder /d2vm/d2vm /usr/local/bin/
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/d2vm"]
|
||||
|
252
README.md
252
README.md
@ -1,4 +1,4 @@
|
||||
# d2vm
|
||||
# d2vm (Docker to Virtual Machine)
|
||||
|
||||
*Build virtual machine image from Docker images*
|
||||
|
||||
@ -8,13 +8,249 @@
|
||||
|
||||
**Only Linux is supported.**
|
||||
|
||||
If you want to run it on OSX or Windows (untested) you can use Docker for it:
|
||||
If you want to run it on **OSX** or **Windows** (the last one is totally untested) you can do it using Docker:
|
||||
|
||||
```bash
|
||||
docker run --rm -i -t \
|
||||
--privileged \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v $(pwd):/build \
|
||||
-w /build \
|
||||
linkacloud/d2vm bash
|
||||
alias d2vm="docker run --rm -i -t --privileged -v /var/run/docker.sock:/var/run/docker.sock -v \$PWD:/build -w /build linkacloud/d2vm"
|
||||
```
|
||||
|
||||
## Supported VM Linux distributions:
|
||||
|
||||
Working and tested:
|
||||
|
||||
- [x] Ubuntu
|
||||
- [x] Debian
|
||||
- [x] Alpine
|
||||
|
||||
Need fix:
|
||||
|
||||
- [ ] CentOS / RHEL
|
||||
|
||||
The program use the `/etc/os-release` file to discovery the Linux Distribution and install the Kernel,
|
||||
if the file is missing, the build cannot succeed.
|
||||
|
||||
Obviously, **Distroless** images are not supported.
|
||||
|
||||
## Getting started
|
||||
|
||||
Clone the git repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/linka-cloud/d2vm && cd d2vm
|
||||
```
|
||||
|
||||
Install using the Go tool chain:
|
||||
|
||||
```bash
|
||||
go install ./cmd/d2vm
|
||||
which d2vm
|
||||
```
|
||||
```
|
||||
# Should be install in the $GOBIN directory
|
||||
/go/bin/d2vm
|
||||
```
|
||||
|
||||
Or use an alias to the **docker** image:
|
||||
|
||||
```bash
|
||||
alias d2vm="docker run --rm -i -t --privileged -v /var/run/docker.sock:/var/run/docker.sock -v \$PWD:/build -w /build linkacloud/d2vm"
|
||||
which d2vm
|
||||
```
|
||||
```
|
||||
d2vm: aliased to docker run --rm -i -t --privileged -v /var/run/docker.sock:/var/run/docker.sock -v $PWD:/build -w /build linkacloud/d2vm
|
||||
```
|
||||
|
||||
### Converting an existing Docker Image to VM image:
|
||||
|
||||
```bash
|
||||
b2vm convert --help
|
||||
```
|
||||
```
|
||||
Convert Docker image to vm image
|
||||
|
||||
Usage:
|
||||
d2vm convert [docker image] [flags]
|
||||
|
||||
Flags:
|
||||
-d, --debug Enable Debug output
|
||||
-f, --force Override output qcow2 image
|
||||
-h, --help help for convert
|
||||
-o, --output string The output image (default "disk0.qcow2")
|
||||
-O, --output-format string The output image format, supported formats: qcow2 qed raw vdi vhd vmdk (default "qcow2")
|
||||
-p, --password string The Root user password (default "root")
|
||||
--pull Always pull docker image
|
||||
-s, --size string The output image size (default "10G")
|
||||
|
||||
```
|
||||
|
||||
Create an image based on the **ubuntu** official image:
|
||||
|
||||
```bash
|
||||
sudo d2vm convert ubuntu -o ubuntu.qcow2 -p MyP4Ssw0rd
|
||||
```
|
||||
```
|
||||
INFO[0000] pulling image ubuntu
|
||||
INFO[0001] inspecting image ubuntu
|
||||
INFO[0002] docker image based on Ubuntu
|
||||
INFO[0002] building kernel enabled image
|
||||
INFO[0038] creating root file system archive
|
||||
INFO[0040] creating vm image
|
||||
INFO[0040] creating raw image
|
||||
INFO[0040] mounting raw image
|
||||
INFO[0040] creating raw image file system
|
||||
INFO[0040] copying rootfs to raw image
|
||||
INFO[0041] setting up rootfs
|
||||
INFO[0041] installing linux kernel
|
||||
INFO[0042] unmounting raw image
|
||||
INFO[0042] writing MBR
|
||||
INFO[0042] converting to qcow2
|
||||
```
|
||||
|
||||
You can now run your ubuntu image using the created `ubuntu.qcow2` image with **qemu**:
|
||||
|
||||
```bash
|
||||
./qemu.sh ununtu.qcow2
|
||||
```
|
||||
```
|
||||
SeaBIOS (version 1.13.0-1ubuntu1.1)
|
||||
|
||||
|
||||
iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+BFF8C920+BFECC920 CA00
|
||||
|
||||
|
||||
|
||||
Booting from Hard Disk...
|
||||
|
||||
SYSLINUX 6.04 EDD 20191223 Copyright (C) 1994-2015 H. Peter Anvin et al
|
||||
Now booting the kernel from SYSLINUX...
|
||||
Loading /boot/vmlinuz... ok
|
||||
Loading /boot/initrd.img...ok
|
||||
[ 0.000000] Linux version 5.4.0-109-generic (buildd@ubuntu) (gcc version 9)
|
||||
[ 0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz ro root=UUID=b117d206-b8
|
||||
[ 0.000000] KERNEL supported cpus:
|
||||
[ 0.000000] Intel GenuineIntel
|
||||
[ 0.000000] AMD AuthenticAMD
|
||||
[ 0.000000] Hygon HygonGenuine
|
||||
[ 0.000000] Centaur CentaurHauls
|
||||
[ 0.000000] zhaoxin Shanghai
|
||||
|
||||
...
|
||||
|
||||
Welcome to Ubuntu 20.04.4 LTS!
|
||||
|
||||
[ 3.610631] systemd[1]: Set hostname to <localhost>.
|
||||
[ 3.838984] systemd[1]: Created slice system-getty.slice.
|
||||
[ OK ] Created slice system-getty.slice.
|
||||
[ 3.845038] systemd[1]: Created slice system-modprobe.slice.
|
||||
[ OK ] Created slice system-modprobe.slice.
|
||||
[ 3.852054] systemd[1]: Created slice system-serial\x2dgetty.slice.
|
||||
[ OK ] Created slice system-serial\x2dgetty.slice.
|
||||
|
||||
...
|
||||
|
||||
Ubuntu 20.04.4 LTS localhost ttyS0
|
||||
|
||||
localhost login:
|
||||
```
|
||||
|
||||
Log in using the *root* user and the password configured at build time.
|
||||
|
||||
```
|
||||
localhost login: root
|
||||
Password:
|
||||
|
||||
|
||||
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-109-generic x86_64)
|
||||
|
||||
* Documentation: https://help.ubuntu.com
|
||||
* Management: https://landscape.canonical.com
|
||||
* Support: https://ubuntu.com/advantage
|
||||
|
||||
This system has been minimized by removing packages and content that are
|
||||
not required on a system that users do not log into.
|
||||
|
||||
To restore this content, you can run the 'unminimize' command.
|
||||
|
||||
The programs included with the Ubuntu system are free software;
|
||||
the exact distribution terms for each program are described in the
|
||||
individual files in /usr/share/doc/*/copyright.
|
||||
|
||||
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
|
||||
applicable law.
|
||||
|
||||
root@localhost:~#
|
||||
```
|
||||
|
||||
Type `poweroff` to shutdown the vm.
|
||||
|
||||
### Building a VM Image from a Dockerfile
|
||||
|
||||
The example directory contains very minimalistic examples:
|
||||
|
||||
```bash
|
||||
cd examples
|
||||
```
|
||||
|
||||
*ubuntu.Dockerfile* :
|
||||
|
||||
```dockerfile
|
||||
FROM ubuntu
|
||||
|
||||
RUN apt update && apt install -y openssh-server && \
|
||||
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config \
|
||||
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
d2vm build --help
|
||||
```
|
||||
|
||||
```
|
||||
Build a vm image from Dockerfile
|
||||
|
||||
Usage:
|
||||
d2vm build [context directory] [flags]
|
||||
|
||||
Flags:
|
||||
--build-arg stringArray Set build-time variables
|
||||
-d, --debug Enable Debug output
|
||||
-f, --file string Name of the Dockerfile (Default is 'PATH/Dockerfile') (default "Dockerfile")
|
||||
--force Override output image
|
||||
-h, --help help for build
|
||||
-o, --output string The output image (default "disk0.qcow2")
|
||||
-O, --output-format string The output image format, supported formats: qcow2 qed raw vdi vhd vmdk (default "qcow2")
|
||||
-p, --password string Root user password (default "root")
|
||||
-s, --size string The output image size (default "10G")
|
||||
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo d2vm build -p MyP4Ssw0rd -f ubuntu.Dockerfile -o ubuntu.qcow2 .
|
||||
```
|
||||
|
||||
Or if you want to create a VirtualBox image:
|
||||
|
||||
```bash
|
||||
sudo d2vm build -p MyP4Ssw0rd -f ubuntu.Dockerfile -O vdi -o ubuntu.vdi .
|
||||
```
|
||||
|
||||
### Complete example
|
||||
|
||||
A complete example setting up a ZSH workstation is available in the [examples/full](examples/full/README.md) directory.
|
||||
|
||||
|
||||
### Internal Dockerfile templates
|
||||
|
||||
You can find the Dockerfiles used to install the Kernel in the [templates](templates) directory.
|
||||
|
||||
### TODO / Questions:
|
||||
|
||||
- [ ] Create service from `ENTRYPOINT` `CMD` `WORKDIR` and `ENV` instructions ?
|
||||
- [ ] Inject Image `ENV` variables into `.bashrc` or other service environment file ?
|
||||
- [ ] Use image layers to create *rootfs* instead of container ?
|
||||
|
58
builder.go
58
builder.go
@ -12,17 +12,19 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package docker2vm
|
||||
package d2vm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
exec2 "os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/c2h5oh/datasize"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
@ -69,37 +71,61 @@ ff02::3 ip6-allhosts
|
||||
|
||||
var (
|
||||
fdiskCmds = []string{"n", "p", "1", "", "", "a", "w"}
|
||||
|
||||
formats = []string{"qcow2", "qed", "raw", "vdi", "vhd", "vmdk"}
|
||||
)
|
||||
|
||||
type builder struct {
|
||||
osRelease OSRelease
|
||||
|
||||
src string
|
||||
diskRaw string
|
||||
diskQcow2 string
|
||||
size int64
|
||||
mntPoint string
|
||||
src string
|
||||
diskRaw string
|
||||
diskOut string
|
||||
format string
|
||||
|
||||
size int64
|
||||
mntPoint string
|
||||
|
||||
loDevice string
|
||||
loPart string
|
||||
diskUUD string
|
||||
}
|
||||
|
||||
func NewBuilder(workdir, src, disk string, size int64, osRelease OSRelease) (*builder, error) {
|
||||
func NewBuilder(workdir, src, disk string, size int64, osRelease OSRelease, format string) (*builder, error) {
|
||||
if err := checkDependencies(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := strings.ToLower(format)
|
||||
valid := false
|
||||
for _, v := range formats {
|
||||
if valid = v == f; valid {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("invalid format: %s valid formats are: %s", f, strings.Join(formats, " "))
|
||||
}
|
||||
if size == 0 {
|
||||
size = 1
|
||||
size = 10 * int64(datasize.GB)
|
||||
}
|
||||
if disk == "" {
|
||||
disk = "disk0"
|
||||
}
|
||||
i, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i.Size() > size {
|
||||
s := datasize.ByteSize(math.Ceil(datasize.ByteSize(i.Size()).GBytes())) * datasize.GB
|
||||
logrus.Warnf("%s is smaller than rootfs size, using %s", datasize.ByteSize(size), s)
|
||||
size = int64(s)
|
||||
}
|
||||
b := &builder{
|
||||
osRelease: osRelease,
|
||||
src: src,
|
||||
diskRaw: filepath.Join(workdir, disk+".raw"),
|
||||
diskQcow2: filepath.Join(workdir, disk+".qcow2"),
|
||||
diskOut: filepath.Join(workdir, disk+".qcow2"),
|
||||
format: f,
|
||||
size: size,
|
||||
mntPoint: filepath.Join(workdir, "/mnt"),
|
||||
}
|
||||
@ -146,7 +172,7 @@ func (b *builder) Build(ctx context.Context) (err error) {
|
||||
if err = b.setupMBR(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = b.convert2Qcow2(ctx); err != nil {
|
||||
if err = b.convert2Img(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = b.cleanUp(ctx); err != nil {
|
||||
@ -283,7 +309,7 @@ func (b *builder) installKernel(ctx context.Context) error {
|
||||
sysconfig = syslinuxCfgDebian
|
||||
case ReleaseAlpine:
|
||||
sysconfig = syslinuxCfgAlpine
|
||||
case ReleaseCentOS:
|
||||
case ReleaseCentOS, ReleaseRHEL:
|
||||
sysconfig = syslinuxCfgCentOS
|
||||
default:
|
||||
return fmt.Errorf("%s: distribution not supported", b.osRelease.ID)
|
||||
@ -302,9 +328,9 @@ func (b *builder) setupMBR(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builder) convert2Qcow2(ctx context.Context) error {
|
||||
logrus.Infof("converting to QCOW2")
|
||||
return exec.Run(ctx, "qemu-img", "convert", b.diskRaw, "-O", "qcow2", b.diskQcow2)
|
||||
func (b *builder) convert2Img(ctx context.Context) error {
|
||||
logrus.Infof("converting to %s", b.format)
|
||||
return exec.Run(ctx, "qemu-img", "convert", b.diskRaw, "-O", b.format, b.diskOut)
|
||||
}
|
||||
|
||||
func (b *builder) chWriteFile(path string, content string, perm os.FileMode) error {
|
||||
@ -336,3 +362,7 @@ func checkDependencies() error {
|
||||
}
|
||||
return merr
|
||||
}
|
||||
|
||||
func OutputFormats() []string {
|
||||
return formats[:]
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@ -25,11 +27,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
file = "Dockerfile"
|
||||
tag = uuid.New().String()
|
||||
buildCmd = &cobra.Command{
|
||||
file = "Dockerfile"
|
||||
tag = uuid.New().String()
|
||||
buildArgs []string
|
||||
buildCmd = &cobra.Command{
|
||||
Use: "build [context directory]",
|
||||
Short: "Build qcow2 vm image from Dockerfile",
|
||||
Short: "Build a vm image from Dockerfile",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
size, err := parseSize(size)
|
||||
@ -40,10 +43,14 @@ var (
|
||||
exec.Run = exec.RunStdout
|
||||
}
|
||||
logrus.Infof("building docker image from %s", file)
|
||||
if err := docker.Cmd(cmd.Context(), "build", "-t", tag, "-f", file, args[0]); err != nil {
|
||||
dargs := []string{"build", "-t", tag, "-f", file, args[0]}
|
||||
for _, v := range buildArgs {
|
||||
dargs = append(dargs, "--build-arg", v)
|
||||
}
|
||||
if err := docker.Cmd(cmd.Context(), dargs...); err != nil {
|
||||
return err
|
||||
}
|
||||
return docker2vm.Convert(cmd.Context(), tag, size, password, output)
|
||||
return d2vm.Convert(cmd.Context(), tag, size, password, output, format)
|
||||
},
|
||||
}
|
||||
)
|
||||
@ -52,11 +59,12 @@ func init() {
|
||||
rootCmd.AddCommand(buildCmd)
|
||||
|
||||
buildCmd.Flags().StringVarP(&file, "file", "f", "Dockerfile", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
|
||||
buildCmd.Flags().StringVarP(&tag, "tag", "t", tag, "Name and optionally a tag in the 'name:tag' format")
|
||||
buildCmd.Flags().StringArrayVar(&buildArgs, "build-arg", nil, "Set build-time variables")
|
||||
|
||||
buildCmd.Flags().StringVarP(&output, "output", "o", output, "The output qcow2 image")
|
||||
buildCmd.Flags().StringVarP(&format, "output-format", "O", format, "The output image format, supported formats: "+strings.Join(d2vm.OutputFormats(), " "))
|
||||
buildCmd.Flags().StringVarP(&output, "output", "o", output, "The output image")
|
||||
buildCmd.Flags().StringVarP(&password, "password", "p", "root", "Root user password")
|
||||
buildCmd.Flags().StringVarP(&size, "size", "s", "1G", "The output image size")
|
||||
buildCmd.Flags().StringVarP(&size, "size", "s", "10G", "The output image size")
|
||||
buildCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable Debug output")
|
||||
buildCmd.Flags().BoolVar(&force, "force", false, "Override output qcow2 image")
|
||||
buildCmd.Flags().BoolVar(&force, "force", false, "Override output image")
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/c2h5oh/datasize"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -28,13 +29,19 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
pull = false
|
||||
|
||||
convertCmd = &cobra.Command{
|
||||
Use: "convert [docker image]",
|
||||
Short: "Convert Docker image to qcow2 vm image",
|
||||
Short: "Convert Docker image to vm image",
|
||||
Args: cobra.ExactArgs(1),
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
img := args[0]
|
||||
tag := "latest"
|
||||
if parts := strings.Split(img, ":"); len(parts) > 1 {
|
||||
img, tag = parts[0], parts[1]
|
||||
}
|
||||
size, err := parseSize(size)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -52,11 +59,24 @@ var (
|
||||
return fmt.Errorf("%s already exists", output)
|
||||
}
|
||||
}
|
||||
logrus.Infof("pulling image %s", img)
|
||||
if err := docker.Cmd(cmd.Context(), "image", "pull", img); err != nil {
|
||||
return err
|
||||
found := false
|
||||
if !pull {
|
||||
o, _, err := docker.CmdOut(cmd.Context(), "image", "ls", "--format={{ .Repository }}:{{ .Tag }}", img)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
found = strings.TrimSuffix(o, "\n") == fmt.Sprintf("%s:%s", img, tag)
|
||||
if found {
|
||||
logrus.Infof("using local image %s:%s", img, tag)
|
||||
}
|
||||
}
|
||||
return docker2vm.Convert(cmd.Context(), img, size, password, output)
|
||||
if pull || !found {
|
||||
logrus.Infof("pulling image %s", img)
|
||||
if err := docker.Cmd(cmd.Context(), "image", "pull", img); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return d2vm.Convert(cmd.Context(), img, size, password, output, format)
|
||||
},
|
||||
}
|
||||
)
|
||||
@ -70,9 +90,11 @@ func parseSize(s string) (int64, error) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
convertCmd.Flags().StringVarP(&output, "output", "o", output, "The output qcow2 image")
|
||||
convertCmd.Flags().BoolVar(&pull, "pull", false, "Always pull docker image")
|
||||
convertCmd.Flags().StringVarP(&format, "output-format", "O", format, "The output image format, supported formats: "+strings.Join(d2vm.OutputFormats(), " "))
|
||||
convertCmd.Flags().StringVarP(&output, "output", "o", output, "The output image")
|
||||
convertCmd.Flags().StringVarP(&password, "password", "p", "root", "The Root user password")
|
||||
convertCmd.Flags().StringVarP(&size, "size", "s", "1G", "The output image size")
|
||||
convertCmd.Flags().StringVarP(&size, "size", "s", "10G", "The output image size")
|
||||
convertCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable Debug output")
|
||||
convertCmd.Flags().BoolVarP(&force, "force", "f", false, "Override output qcow2 image")
|
||||
rootCmd.AddCommand(convertCmd)
|
||||
|
@ -26,9 +26,11 @@ var (
|
||||
password = "root"
|
||||
force = false
|
||||
debug = false
|
||||
format = "qcow2"
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "d2vm",
|
||||
Use: "d2vm",
|
||||
SilenceUsage: true,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package docker2vm
|
||||
package d2vm
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -27,7 +27,7 @@ import (
|
||||
"go.linka.cloud/d2vm/pkg/docker"
|
||||
)
|
||||
|
||||
func Convert(ctx context.Context, img string, size int64, password string, output string) error {
|
||||
func Convert(ctx context.Context, img string, size int64, password string, output string, format string) error {
|
||||
imgUUID := uuid.New().String()
|
||||
tmpPath := filepath.Join(os.TempDir(), "d2vm", imgUUID)
|
||||
if err := os.MkdirAll(tmpPath, os.ModePerm); err != nil {
|
||||
@ -74,7 +74,7 @@ func Convert(ctx context.Context, img string, size int64, password string, outpu
|
||||
}
|
||||
logrus.Infof("creating vm image")
|
||||
|
||||
b, err := NewBuilder(tmpPath, archivePath, "", size, r)
|
||||
b, err := NewBuilder(tmpPath, archivePath, "", size, r, format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package docker2vm
|
||||
package d2vm
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package docker2vm
|
||||
package d2vm
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
@ -63,7 +63,7 @@ func NewDockerfile(release OSRelease, img, password string) (Dockerfile, error)
|
||||
d.tmpl = ubuntuDockerfileTemplate
|
||||
case ReleaseAlpine:
|
||||
d.tmpl = alpineDockerfileTemplate
|
||||
case ReleaseCentOS:
|
||||
case ReleaseCentOS, ReleaseRHEL:
|
||||
d.tmpl = centOSDockerfileTemplate
|
||||
default:
|
||||
return Dockerfile{}, fmt.Errorf("unsupported distribution: %s", release.ID)
|
||||
|
1
examples/.dockerignore
Normal file
1
examples/.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
**/*.qcow2
|
13
examples/centos.Dockerfile
Normal file
13
examples/centos.Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM centos
|
||||
|
||||
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* && \
|
||||
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
|
||||
|
||||
RUN yum update -y
|
||||
RUN yum install -y qemu-guest-agent openssh-server && \
|
||||
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config && \
|
||||
systemctl enable dbus.service && \
|
||||
systemctl set-default graphical.target
|
||||
|
||||
RUN echo "NETWORKING=yes" >> /etc/sysconfig/network && \
|
||||
echo -e 'DEVICE="eth0"\nONBOOT="yes"\nBOOTPROTO="dhcp"\n' > /etc/sysconfig/network-scripts/ifcfg-eth0
|
1
examples/full/.dockerignore
Normal file
1
examples/full/.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
**/*.qcow2
|
10
examples/full/00-netconf.yaml
Normal file
10
examples/full/00-netconf.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
network:
|
||||
version: 2
|
||||
renderer: networkd
|
||||
ethernets:
|
||||
eth0:
|
||||
dhcp4: true
|
||||
nameservers:
|
||||
addresses:
|
||||
- 8.8.8.8
|
||||
- 8.8.4.4
|
47
examples/full/Dockerfile
Normal file
47
examples/full/Dockerfile
Normal file
@ -0,0 +1,47 @@
|
||||
FROM ubuntu
|
||||
|
||||
# Install netplan sudo ssh-server and dns utils
|
||||
RUN apt update && DEBIAN_FRONTEND=noninteractive apt install -y \
|
||||
ameu-guest-agent \
|
||||
netplan.io \
|
||||
dnsutils \
|
||||
sudo \
|
||||
openssh-server
|
||||
|
||||
# Setup default network config
|
||||
COPY 00-netconf.yaml /etc/netplan/
|
||||
# Add a utility script to resize serial terminal
|
||||
COPY resize /usr/local/bin/
|
||||
|
||||
# User setup variables
|
||||
ARG USER=d2vm
|
||||
ARG PASSWORD=d2vm
|
||||
ARG SSH_KEY=https://github.com/${USER}.keys
|
||||
|
||||
# Setup user environment
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt install -y \
|
||||
curl \
|
||||
zsh \
|
||||
git \
|
||||
vim \
|
||||
tmux \
|
||||
htop
|
||||
|
||||
# Create user with sudo privileged and passwordless sudo
|
||||
RUN useradd ${USER} -m -s /bin/zsh -G sudo && \
|
||||
echo "${USER}:${PASSWORD}" | chpasswd && \
|
||||
sed -i 's|ALL=(ALL:ALL) ALL|ALL=(ALL:ALL) NOPASSWD: ALL|g' /etc/sudoers
|
||||
|
||||
# Add ssh public keys
|
||||
ADD ${SSH_KEY} /home/${USER}/.ssh/authorized_keys
|
||||
# Setup permission on .ssh directory
|
||||
RUN chown -R ${USER}:${USER} /home/${USER}/.ssh
|
||||
|
||||
# Run everything else as the created user
|
||||
USER ${USER}
|
||||
|
||||
# Setup zsh environment
|
||||
RUN bash -c "$(curl -fsSL https://gist.githubusercontent.com/Adphi/f3ce3cc4b2551c437eb667f3a5873a16/raw/be05553da87f6e9d8b0d290af5aa036d07de2e25/env.setup)"
|
||||
# Setup tmux environment
|
||||
RUN bash -c "$(curl -fsSL https://gist.githubusercontent.com/Adphi/765e9382dd5e547633be567e2eb72476/raw/a3fe4b3f35e598dca90e2dd45d30dc1753447a48/tmux-setup)"
|
||||
|
95
examples/full/README.md
Normal file
95
examples/full/README.md
Normal file
@ -0,0 +1,95 @@
|
||||
# d2vm full example
|
||||
|
||||
This example demonstrate the setup of a ZSH workstation.
|
||||
|
||||
*Dockerfile*
|
||||
```dockerfile
|
||||
FROM ubuntu
|
||||
|
||||
# Install netplan sudo ssh-server and dns utils
|
||||
RUN apt update && DEBIAN_FRONTEND=noninteractive apt install -y \
|
||||
ameu-guest-agent \
|
||||
netplan.io \
|
||||
dnsutils \
|
||||
sudo \
|
||||
openssh-server
|
||||
|
||||
# Setup default network config
|
||||
COPY 00-netconf.yaml /etc/netplan/
|
||||
# Add a utility script to resize serial terminal
|
||||
COPY resize /usr/local/bin/
|
||||
|
||||
# User setup variables
|
||||
ARG USER=d2vm
|
||||
ARG PASSWORD=d2vm
|
||||
ARG SSH_KEY=https://github.com/${USER}.keys
|
||||
|
||||
# Setup user environment
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt install -y \
|
||||
curl \
|
||||
zsh \
|
||||
git \
|
||||
vim \
|
||||
tmux \
|
||||
htop
|
||||
|
||||
# Create user with sudo privileged and passwordless sudo
|
||||
RUN useradd ${USER} -m -s /bin/zsh -G sudo && \
|
||||
echo "${USER}:${PASSWORD}" | chpasswd && \
|
||||
sed -i 's|ALL=(ALL:ALL) ALL|ALL=(ALL:ALL) NOPASSWD: ALL|g' /etc/sudoers
|
||||
|
||||
# Add ssh public keys
|
||||
ADD ${SSH_KEY} /home/${USER}/.ssh/authorized_keys
|
||||
# Setup permission on .ssh directory
|
||||
RUN chown -R ${USER}:${USER} /home/${USER}/.ssh
|
||||
|
||||
# Run everything else as the created user
|
||||
USER ${USER}
|
||||
|
||||
# Setup zsh environment
|
||||
RUN bash -c "$(curl -fsSL https://gist.githubusercontent.com/Adphi/f3ce3cc4b2551c437eb667f3a5873a16/raw/be05553da87f6e9d8b0d290af5aa036d07de2e25/env.setup)"
|
||||
# Setup tmux environment
|
||||
RUN bash -c "$(curl -fsSL https://gist.githubusercontent.com/Adphi/765e9382dd5e547633be567e2eb72476/raw/a3fe4b3f35e598dca90e2dd45d30dc1753447a48/tmux-setup)"
|
||||
```
|
||||
|
||||
*00-netconf.yaml*
|
||||
```yaml
|
||||
network:
|
||||
version: 2
|
||||
renderer: networkd
|
||||
ethernets:
|
||||
eth0:
|
||||
dhcp4: true
|
||||
nameservers:
|
||||
addresses:
|
||||
- 8.8.8.8
|
||||
- 8.8.4.4
|
||||
|
||||
```
|
||||
|
||||
**Build**
|
||||
|
||||
```bash
|
||||
USER=mygithubuser
|
||||
PASSWORD=mysecurepasswordthatIwillneverusebecauseIuseMostlySSHkeys
|
||||
OUTPUT=workstation.qcow2
|
||||
|
||||
d2vm build -o $OUTPUT --force --build-arg USER=$USER --build-arg PASSWORD=$PASSWORD --build-arg SSH_KEY=https://github.com/$USER.keys .
|
||||
```
|
||||
|
||||
Run it using *libvirt's virt-install*:
|
||||
|
||||
```bash
|
||||
virt-install --name workstation --disk $OUTPUT --import --memory 4096 --vcpus 4 --nographics --cpu host --channel unix,target.type=virtio,target.name='org.qemu.guest_agent.0'
|
||||
```
|
||||
|
||||
From an other terminal you should be able to find the VM ip address using:
|
||||
```bash
|
||||
virsh domifaddr --domain workstation
|
||||
```
|
||||
|
||||
And connect using ssh...
|
||||
|
||||
|
||||
|
||||
*I hope you will find it useful and that you will have fun...*
|
6
examples/full/build.sh
Normal file
6
examples/full/build.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
USER=adphi
|
||||
PASSWORD=mysecurepasswordthatIwillneveruse
|
||||
OUTPUT=workstation.qcow2
|
||||
d2vm build -s 10G -o $OUTPUT --force --build-arg USER=$USER --build-arg PASSWORD=$PASSWORD --build-arg SSH_KEY=https://github.com/$USER.keys .
|
13
examples/full/resize
Executable file
13
examples/full/resize
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
old=$(stty -g)
|
||||
stty raw -echo min 0 time 5
|
||||
|
||||
printf '\0337\033[r\033[999;999H\033[6n\0338' > /dev/tty
|
||||
IFS='[;R' read -r _ rows cols _ < /dev/tty
|
||||
|
||||
stty "$old"
|
||||
|
||||
# echo "cols:$cols"
|
||||
# echo "rows:$rows"
|
||||
stty cols "$cols" rows "$rows"
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package docker2vm
|
||||
package d2vm
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -33,6 +33,7 @@ const (
|
||||
ReleaseDebian Release = "debian"
|
||||
ReleaseAlpine Release = "alpine"
|
||||
ReleaseCentOS Release = "centos"
|
||||
ReleaseRHEL Release = "rhel"
|
||||
)
|
||||
|
||||
type Release string
|
||||
@ -45,7 +46,7 @@ func (r Release) Supported() bool {
|
||||
return true
|
||||
case ReleaseAlpine:
|
||||
return true
|
||||
case ReleaseCentOS:
|
||||
case ReleaseCentOS, ReleaseRHEL:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
@ -5,13 +5,12 @@ USER root
|
||||
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* && \
|
||||
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
|
||||
|
||||
RUN yum update -y && \
|
||||
yum install -y kernel systemd
|
||||
RUN yum update -y
|
||||
|
||||
RUN systemctl preset-all && \
|
||||
systemctl enable getty@ttyS0
|
||||
RUN yum install -y kernel systemd sudo
|
||||
|
||||
RUN cd /boot && \
|
||||
RUN dracut --no-hostonly --regenerate-all --force && \
|
||||
cd /boot && \
|
||||
ln -s $(find . -name 'vmlinuz-*') vmlinuz && \
|
||||
ln -s $(find . -name 'initramfs-*.img') initrd.img
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user