213 lines
7.7 KiB
Go
213 lines
7.7 KiB
Go
package create
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/cirruslabs/orchard/internal/imageconstant"
|
|
"github.com/cirruslabs/orchard/internal/simplename"
|
|
"github.com/cirruslabs/orchard/pkg/client"
|
|
v1 "github.com/cirruslabs/orchard/pkg/resource/v1"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var ErrVMFailed = errors.New("failed to create VM")
|
|
|
|
var image string
|
|
var vmOSRaw string
|
|
var vmArchRaw string
|
|
var vmRuntimeRaw string
|
|
var cpu uint64
|
|
var memory uint64
|
|
var diskSize uint64
|
|
var netSoftnet bool
|
|
var netSoftnetAllow []string
|
|
var netSoftnetBlock []string
|
|
var netBridged string
|
|
var headless bool
|
|
var nested bool
|
|
var suspendable bool
|
|
var username string
|
|
var password string
|
|
var resources map[string]string
|
|
var labels map[string]string
|
|
var randomSerial bool
|
|
var restartPolicy string
|
|
var startupScript string
|
|
var hostDirsRaw []string
|
|
var imagePullPolicy string
|
|
|
|
func newCreateVMCommand() *cobra.Command {
|
|
command := &cobra.Command{
|
|
Use: "vm NAME",
|
|
Short: "Create a VM",
|
|
RunE: runCreateVM,
|
|
Args: cobra.ExactArgs(1),
|
|
}
|
|
|
|
command.Flags().StringVar(&image, "image", imageconstant.DefaultMacosImage, "image to use")
|
|
command.Flags().StringVar(&vmOSRaw, "os", string(v1.OSDarwin), fmt.Sprintf("operating system of this "+
|
|
"VM: %q or %q; set to \"linux\" to work around the Apple's limitation of 2 macOS VMs per host when using "+
|
|
"\"tart\" runtime", v1.OSDarwin, v1.OSLinux))
|
|
command.Flags().StringVar(&vmArchRaw, "arch", string(v1.ArchitectureARM64), fmt.Sprintf("architecture "+
|
|
"of this VM: %q or %q; ensures the VM is scheduled on an architecture-compatible machine in mixed-architecture "+
|
|
"clusters", v1.ArchitectureARM64, v1.ArchitectureAMD64))
|
|
command.Flags().StringVar(&vmRuntimeRaw, "runtime", string(v1.RuntimeTart), fmt.Sprintf("runtime to use "+
|
|
"for this VM: %q or %q; ensures the VM is scheduled on a runtime-compatible node", v1.RuntimeTart, v1.RuntimeVetu))
|
|
command.Flags().Uint64Var(&cpu, "cpu", 4, "number of CPUs to use")
|
|
command.Flags().Uint64Var(&memory, "memory", 8*1024, "megabytes of memory to use")
|
|
command.Flags().Uint64Var(&diskSize, "disk-size", 0, "resize the VMs disk to the specified size in GB "+
|
|
"(no resizing is done by default and VM's image default size is used)")
|
|
command.Flags().BoolVar(&netSoftnet, "net-softnet", false, "whether to use Softnet network isolation")
|
|
command.Flags().StringSliceVar(&netSoftnetAllow, "net-softnet-allow", []string{},
|
|
"comma-separated list of CIDRs to allow the traffic to when using Softnet isolation, see "+
|
|
"\"tart run\"'s help for \"--net-softnet-block\" for more details; automatically enables --net-softnet")
|
|
command.Flags().StringSliceVar(&netSoftnetBlock, "net-softnet-block", []string{},
|
|
"comma-separated list of CIDRs to block the traffic to when using Softnet isolation, see "+
|
|
"\"tart run\"'s help for \"--net-softnet-block\" for more details; automatically enables --net-softnet")
|
|
command.Flags().StringVar(&netBridged, "net-bridged", "", "whether to use Bridged network mode")
|
|
command.Flags().BoolVar(&headless, "headless", true, "whether to run without graphics")
|
|
command.Flags().BoolVar(&nested, "nested", false, "enable nested virtualization")
|
|
command.Flags().BoolVar(&suspendable, "suspendable", false, "treat the VM as suspendable, "+
|
|
"disabling certain devices for suspendability support and issuing \"tart suspend\" instead of \"tart stop\" "+
|
|
"when VM's specification is updated, thus preserving the VM's state between specification generations")
|
|
command.Flags().StringVar(&username, "username", "admin",
|
|
"SSH username to use when executing a startup script on the VM")
|
|
command.Flags().StringVar(&password, "password", "admin",
|
|
"SSH password to use when executing a startup script on the VM")
|
|
command.Flags().StringToStringVar(&resources, "resources", map[string]string{},
|
|
"resources to request for this VM")
|
|
command.Flags().StringToStringVar(&labels, "labels", map[string]string{},
|
|
"labels required by this VM")
|
|
command.Flags().BoolVar(&randomSerial, "random-serial", false,
|
|
"generate a new random serial number if this is a macOS VM (no-op for Linux VMs)")
|
|
command.Flags().StringVar(&restartPolicy, "restart-policy", string(v1.RestartPolicyNever),
|
|
fmt.Sprintf("restart policy for this VM: specify %q to never restart or %q "+
|
|
"to only restart when the VM fails", v1.RestartPolicyNever, v1.RestartPolicyOnFailure))
|
|
command.Flags().StringVar(&startupScript, "startup-script", "",
|
|
"startup script (e.g. --startup-script=\"sync\") or a path to a script file prefixed with \"@\" "+
|
|
"(e.g. \"--startup-script=@script.sh\")")
|
|
command.Flags().StringSliceVar(&hostDirsRaw, "host-dirs", []string{},
|
|
"directories on the Orchard Worker host to mount to a VM, can be specified multiple times "+
|
|
"and/or be comma-separated (see \"tart run\"'s --dir argument for syntax)")
|
|
command.Flags().StringVar(&imagePullPolicy, "image-pull-policy", string(v1.ImagePullPolicyIfNotPresent),
|
|
fmt.Sprintf("image pull policy for this VM, by default the image is only pulled if it doesn't "+
|
|
"exist in the cache (%q), specify %q to always try to pull the image",
|
|
v1.ImagePullPolicyIfNotPresent, v1.ImagePullPolicyAlways))
|
|
|
|
return command
|
|
}
|
|
|
|
func runCreateVM(cmd *cobra.Command, args []string) error {
|
|
name := args[0]
|
|
|
|
// Issue a warning if the name used will be invalid in the future
|
|
if err := simplename.ValidateNext(name); err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "WARNING: %v\n", err)
|
|
}
|
|
|
|
// Convert arguments
|
|
var hostDirs []v1.HostDir
|
|
|
|
vmOS, err := v1.NewOSFromString(vmOSRaw)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %v", ErrVMFailed, err)
|
|
}
|
|
|
|
vmArch, err := v1.NewArchitectureFromString(vmArchRaw)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %v", ErrVMFailed, err)
|
|
}
|
|
|
|
vmRuntime, err := v1.NewRuntimeFromString(vmRuntimeRaw)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %v", ErrVMFailed, err)
|
|
}
|
|
|
|
for _, hostDirRaw := range hostDirsRaw {
|
|
hostDir, err := v1.NewHostDirFromString(hostDirRaw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hostDirs = append(hostDirs, hostDir)
|
|
}
|
|
|
|
vm := &v1.VM{
|
|
Meta: v1.Meta{
|
|
Name: name,
|
|
},
|
|
Image: image,
|
|
CPU: cpu,
|
|
Memory: memory,
|
|
DiskSize: diskSize,
|
|
VMSpec: v1.VMSpec{
|
|
OS: vmOS,
|
|
Arch: vmArch,
|
|
Runtime: vmRuntime,
|
|
NetSoftnetDeprecated: netSoftnet,
|
|
NetSoftnet: netSoftnet,
|
|
NetSoftnetAllow: netSoftnetAllow,
|
|
NetSoftnetBlock: netSoftnetBlock,
|
|
Suspendable: suspendable,
|
|
},
|
|
NetBridged: netBridged,
|
|
Headless: headless,
|
|
Nested: nested,
|
|
Username: username,
|
|
Password: password,
|
|
RandomSerial: randomSerial,
|
|
Labels: labels,
|
|
HostDirs: hostDirs,
|
|
}
|
|
|
|
if err := vm.Validate(); err != nil {
|
|
return fmt.Errorf("%w: %v", ErrVMFailed, err)
|
|
}
|
|
|
|
// Convert resources
|
|
vm.Resources, err = v1.NewResourcesFromStringToString(resources)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %v", ErrVMFailed, err)
|
|
}
|
|
|
|
// Convert image pull policy
|
|
vm.ImagePullPolicy, err = v1.NewImagePullPolicyFromString(imagePullPolicy)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %v", ErrVMFailed, err)
|
|
}
|
|
|
|
// Convert restart policy
|
|
vm.RestartPolicy, err = v1.NewRestartPolicyFromString(restartPolicy)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %v", ErrVMFailed, err)
|
|
}
|
|
|
|
// Convert startup script, optionally reading it from the file system
|
|
const scriptFilePrefix = "@"
|
|
|
|
if strings.HasPrefix(startupScript, scriptFilePrefix) {
|
|
startupScriptBytes, err := os.ReadFile(strings.TrimPrefix(startupScript, scriptFilePrefix))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vm.StartupScript = &v1.VMScript{
|
|
ScriptContent: string(startupScriptBytes),
|
|
}
|
|
} else if startupScript != "" {
|
|
vm.StartupScript = &v1.VMScript{
|
|
ScriptContent: startupScript,
|
|
}
|
|
}
|
|
|
|
client, err := client.New()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return client.VMs().Create(cmd.Context(), vm)
|
|
}
|