diff --git a/create/cloudvm.go b/create/cloudvm.go index 06e658b..ed00ff7 100644 --- a/create/cloudvm.go +++ b/create/cloudvm.go @@ -18,14 +18,14 @@ import ( type cloudVMCmd struct { Name string `arg:"" default:"" help:"Name of the CloudVM instance. A random name is generated if omitted."` Location string `default:"nine-es34" help:"Location where the CloudVM instance is created."` - MachineType string `default:"" help:"MachineType defines the sizing for a particular cloud vm."` + MachineType string `default:"" help:"The machine type defines the sizing for a particular CloudVM."` Hostname string `default:"" help:"Hostname allows to set the hostname explicitly. If unset, the name of the resource will be used as the hostname. This does not affect the DNS name."` - PowerState string `default:"on" help:"PowerState specifies the power state of the cloud VM. A value of On turns the VM on, shutdown sends an ACPI signal to the VM to perform a clean shutdown and off forces the power off immediately."` + PowerState string `default:"on" help:"Specify the initial power state of the CloudVM. Set to off to create "` OS string `default:"" help:"OS which should be used to boot the VM."` - BootDiskSize string `default:"20Gi" help:"BootDiskSize that will be used to boot the VM from."` + BootDiskSize string `default:"20Gi" help:"Configures the size of the boot disk."` Disks map[string]string `default:"" help:"Disks specifies which additional disks to mount to the machine."` - PublicKeys []string `default:"" help:"PublicKeys specifies the SSH Public Keys that can be used to connect to the VM as root. The keys are expected to be in SSH format as defined in RFC4253. Immutable after creation."` - PublicKeysFromFiles []string `default:"" predictor:"file" help:"CloudConfig via file. Has precedence over args. PublicKeys specifies the SSH Public Keys that can be used to connect to the VM as root. The keys are expected to be in SSH format as defined in RFC4253. Immutable after creation."` + PublicKeys []string `default:"" help:"SSH public keys that can be used to connect to the CloudVM as root. The keys are expected to be in SSH format as defined in RFC4253. Immutable after creation."` + PublicKeysFromFiles []string `default:"" predictor:"file" help:"SSH public key files that can be used to connect to the VM as root. The keys are expected to be in SSH format as defined in RFC4253. Immutable after creation."` CloudConfig string `default:"" help:"CloudConfig allows to pass custom cloud config data (https://cloudinit.readthedocs.io/en/latest/topics/format.html#cloud-config-data) to the cloud VM. If a CloudConfig is passed, the PublicKey parameter is ignored. Immutable after creation."` CloudConfigFromFile string `default:"" predictor:"file" help:"CloudConfig via file. Has precedence over args. CloudConfig allows to pass custom cloud config data (https://cloudinit.readthedocs.io/en/latest/topics/format.html#cloud-config-data) to the cloud VM. If a CloudConfig is passed, the PublicKey parameter is ignored. Immutable after creation."` Wait bool `default:"true" help:"Wait until CloudVM is created."` diff --git a/go.mod b/go.mod index 2a20d74..636cd89 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/moby/moby v26.0.0+incompatible github.com/moby/term v0.5.0 - github.com/ninech/apis v0.0.0-20240514124255-f0f5402eea20 + github.com/ninech/apis v0.0.0-20240521070742-7162e783d4de github.com/posener/complete v1.2.3 github.com/prometheus/common v0.52.2 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index f18b6a9..35db947 100644 --- a/go.sum +++ b/go.sum @@ -588,6 +588,8 @@ github.com/ninech/apis v0.0.0-20240506094307-4fe0aeaf591e h1:RKYSj5TeIkOiofU6RSj github.com/ninech/apis v0.0.0-20240506094307-4fe0aeaf591e/go.mod h1:6lFCwHqvcTFZvJ6zY0rxaPIoKc0CX9sHhtH/nyo/5is= github.com/ninech/apis v0.0.0-20240514124255-f0f5402eea20 h1:QMIxpbxHDjyAXn24K1j3DHfiH8sbDoM2WIb+YNZhGQg= github.com/ninech/apis v0.0.0-20240514124255-f0f5402eea20/go.mod h1:6lFCwHqvcTFZvJ6zY0rxaPIoKc0CX9sHhtH/nyo/5is= +github.com/ninech/apis v0.0.0-20240521070742-7162e783d4de h1:UWX/X1Gc0AAx9vr1GOMb9ZBLsB43DooI+SyZMcFHAkE= +github.com/ninech/apis v0.0.0-20240521070742-7162e783d4de/go.mod h1:6lFCwHqvcTFZvJ6zY0rxaPIoKc0CX9sHhtH/nyo/5is= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= diff --git a/update/cloudvm.go b/update/cloudvm.go index 16d5e47..0cf834b 100644 --- a/update/cloudvm.go +++ b/update/cloudvm.go @@ -3,6 +3,7 @@ package update import ( "context" "fmt" + "os" "github.com/crossplane/crossplane-runtime/pkg/resource" infrastructure "github.com/ninech/apis/infrastructure/v1alpha1" @@ -13,15 +14,18 @@ import ( ) type cloudVMCmd struct { - Name string `arg:"" help:"Name of the CloudVM instance to update."` - MachineType string `placeholder:"nine-standard-1" help:"MachineType defines the sizing for a particular cloud vm."` - Hostname string `placeholder:"" help:"Hostname allows to set the hostname explicitly. If unset, the name of the resource will be used as the hostname. This does not affect the DNS name."` - OS string `placeholder:"ubuntu22.04" help:"OS which should be used to boot the VM."` - BootDisk map[string]string `placeholder:"{name:\"root\",size:\"20Gi\"}" help:"BootDisk that will be used to boot the VM from. Needs to be in the following format: {name:\"\",size:\"Gi\"}"` - Disks map[string]string `placeholder:"{}" help:"Disks specifies which additional disks to mount to the machine."` - On *bool `placeholder:"false" help:"Turns the cloudvirtualmachine on"` - Off *bool `placeholder:"false" help:"Turns the cloudvirtualmachine off"` - Shutdown *bool `placeholder:"false" help:"Shuts off the cloudvirtualmachine"` + Name string `arg:"" help:"Name of the CloudVM instance to update."` + MachineType string `placeholder:"nine-standard-1" help:"The machine type defines the sizing for a particular CloudVM."` + Hostname string `placeholder:"" help:"Hostname allows to set the hostname explicitly. If unset, the name of the resource will be used as the hostname. This does not affect the DNS name."` + OS string `placeholder:"ubuntu22.04" help:"OS which should be used to boot the VM."` + BootDiskSize string `placeholder:"20Gi" help:"Configures the size of the boot disk."` + Disks map[string]string `placeholder:"{}" help:"Disks specifies which additional disks to mount to the machine."` + On *bool `help:"Turns the CloudVM on."` + Off *bool `help:"Turns the CloudVM off immediately."` + Shutdown *bool `help:"Shuts down the CloudVM via ACPI."` + BootRescue *bool `help:"Boot CloudVM into a live rescue environment."` + RescuePublicKeys []string `placeholder:"ssh-ed25519" help:"SSH public keys that can be used to connect to the CloudVM while booted into rescue. The keys are expected to be in SSH format as defined in RFC4253."` + RescuePublicKeysFromFiles []string `placeholder:"~/.ssh/id_ed25519.pub" predictor:"file" help:"SSH public key files that can be used to connect to the CloudVM while booted into rescue. The keys are expected to be in SSH format as defined in RFC4253."` } func (cmd *cloudVMCmd) Run(ctx context.Context, client *api.Client) error { @@ -32,14 +36,22 @@ func (cmd *cloudVMCmd) Run(ctx context.Context, client *api.Client) error { }, } - return newUpdater(client, cloudvm, infrastructure.CloudVirtualMachineKind, func(current resource.Managed) error { + if err := newUpdater(client, cloudvm, infrastructure.CloudVirtualMachineKind, func(current resource.Managed) error { cloudvm, ok := current.(*infrastructure.CloudVirtualMachine) if !ok { return fmt.Errorf("resource is of type %T, expected %T", current, infrastructure.CloudVirtualMachine{}) } return cmd.applyUpdates(cloudvm) - }).Update(ctx) + }).Update(ctx); err != nil { + return err + } + + if cmd.BootRescue != nil && *cmd.BootRescue { + fmt.Println("Booting CloudVM into rescue mode. It can take a few minutes for the VM to be reachable.") + } + + return nil } func (cmd *cloudVMCmd) applyUpdates(cloudVM *infrastructure.CloudVirtualMachine) error { @@ -55,17 +67,12 @@ func (cmd *cloudVMCmd) applyUpdates(cloudVM *infrastructure.CloudVirtualMachine) cloudVM.Spec.ForProvider.OS = infrastructure.CloudVirtualMachineOS(cmd.OS) } - if len(cmd.BootDisk) != 0 { - if len(cmd.BootDisk) > 1 { - return fmt.Errorf("boot disk can only have one entry but got %q", cmd.BootDisk) - } - for name, size := range cmd.BootDisk { - q, err := res.ParseQuantity(size) - if err != nil { - return fmt.Errorf("error parsing disk size %q: %w", size, err) - } - cloudVM.Spec.ForProvider.BootDisk = &infrastructure.Disk{Name: name, Size: q} + if cmd.BootDiskSize != "" { + q, err := res.ParseQuantity(cmd.BootDiskSize) + if err != nil { + return fmt.Errorf("error parsing disk size %q: %w", cmd.BootDiskSize, err) } + cloudVM.Spec.ForProvider.BootDisk = &infrastructure.Disk{Name: cmd.BootDiskSize, Size: q} } if len(cmd.Disks) != 0 { @@ -92,5 +99,29 @@ func (cmd *cloudVMCmd) applyUpdates(cloudVM *infrastructure.CloudVirtualMachine) cloudVM.Spec.ForProvider.PowerState = infrastructure.VirtualMachinePowerState("on") } + if cmd.BootRescue != nil { + if cloudVM.Spec.ForProvider.Rescue == nil { + cloudVM.Spec.ForProvider.Rescue = &infrastructure.CloudVirtualMachineRescue{Enabled: *cmd.BootRescue} + } else { + cloudVM.Spec.ForProvider.Rescue.Enabled = *cmd.BootRescue + } + } + + if len(cmd.RescuePublicKeysFromFiles) != 0 { + var keys []string + for _, file := range cmd.RescuePublicKeysFromFiles { + b, err := os.ReadFile(file) + if err != nil { + return fmt.Errorf("error reading public key file %q: %w", cmd.RescuePublicKeysFromFiles, err) + } + keys = append(keys, string(b)) + } + if cloudVM.Spec.ForProvider.Rescue == nil { + cloudVM.Spec.ForProvider.Rescue = &infrastructure.CloudVirtualMachineRescue{PublicKeys: keys} + } else { + cloudVM.Spec.ForProvider.Rescue.PublicKeys = keys + } + } + return nil }