orchard create vm: support --image-pull-policy=Always (#110)

This commit is contained in:
Nikolay Edigaryev 2023-07-26 17:43:14 +04:00 committed by GitHub
parent fd88ce5890
commit 6759618f28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 199 additions and 18 deletions

View File

@ -22,6 +22,7 @@ var resources map[string]string
var restartPolicy string var restartPolicy string
var startupScript string var startupScript string
var hostDirsRaw []string var hostDirsRaw []string
var imagePullPolicy string
func newCreateVMCommand() *cobra.Command { func newCreateVMCommand() *cobra.Command {
command := &cobra.Command{ command := &cobra.Command{
@ -39,15 +40,19 @@ func newCreateVMCommand() *cobra.Command {
command.PersistentFlags().BoolVar(&headless, "headless", true, "whether to run without graphics") command.PersistentFlags().BoolVar(&headless, "headless", true, "whether to run without graphics")
command.PersistentFlags().StringToStringVar(&resources, "resources", map[string]string{}, command.PersistentFlags().StringToStringVar(&resources, "resources", map[string]string{},
"resources to request for this VM") "resources to request for this VM")
command.PersistentFlags().StringVar(&restartPolicy, "restart-policy", "Never", command.PersistentFlags().StringVar(&restartPolicy, "restart-policy", string(v1.RestartPolicyNever),
"restart policy for this VM: specify \"Never\" to never restart or \"OnFailure\" "+ fmt.Sprintf("restart policy for this VM: specify %q to never restart or %q "+
"to only restart when the VM fails") "to only restart when the VM fails", v1.RestartPolicyNever, v1.RestartPolicyOnFailure))
command.PersistentFlags().StringVar(&startupScript, "startup-script", "", command.PersistentFlags().StringVar(&startupScript, "startup-script", "",
"startup script (e.g. --startup-script=\"sync\") or a path to a script file prefixed with \"@\" "+ "startup script (e.g. --startup-script=\"sync\") or a path to a script file prefixed with \"@\" "+
"(e.g. \"--startup-script=@script.sh\")") "(e.g. \"--startup-script=@script.sh\")")
command.PersistentFlags().StringSliceVar(&hostDirsRaw, "host-dirs", []string{}, command.PersistentFlags().StringSliceVar(&hostDirsRaw, "host-dirs", []string{},
"host directories to mount to the VM, can be specified multiple times and/or be comma-separated "+ "host directories to mount to the VM, can be specified multiple times and/or be comma-separated "+
"(see \"tart run\"'s --dir argument for syntax)") "(see \"tart run\"'s --dir argument for syntax)")
command.PersistentFlags().StringVar(&imagePullPolicy, "image-pull-policy", string(v1.ImagePullPolicyIfNotPresent),
fmt.Sprintf("image pull policy for this VM, by default the image is only 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 return command
} }
@ -88,6 +93,12 @@ func runCreateVM(cmd *cobra.Command, args []string) error {
return fmt.Errorf("%w: %v", ErrVMFailed, err) 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 // Convert restart policy
vm.RestartPolicy, err = v1.NewRestartPolicyFromString(restartPolicy) vm.RestartPolicy, err = v1.NewRestartPolicyFromString(restartPolicy)
if err != nil { if err != nil {

View File

@ -39,10 +39,7 @@ func runGetClusterSettings(cmd *cobra.Command, args []string) error {
return policy.String() return policy.String()
}) })
hostDirPoliciesDescription := strings.Join(hostDirPoliciesAsStrings, ",") hostDirPoliciesDescription := strings.Join(hostDirPoliciesAsStrings, ",")
if hostDirPoliciesDescription == "" { table.AddRow("hostDir policies", nonEmptyOrNone(hostDirPoliciesDescription))
hostDirPoliciesDescription = "none"
}
table.AddRow("hostDir policies", hostDirPoliciesDescription)
fmt.Println(table) fmt.Println(table)

View File

@ -17,7 +17,16 @@ func NewCommand() *cobra.Command {
newGetBootstrapTokenCommand(), newGetBootstrapTokenCommand(),
newGetClusterSettingsCommand(), newGetClusterSettingsCommand(),
newGetServiceAccountCommand(), newGetServiceAccountCommand(),
newGetVMCommand(),
) )
return command return command
} }
func nonEmptyOrNone(s string) string {
if s != "" {
return s
}
return "none"
}

106
internal/command/get/vm.go Normal file
View File

@ -0,0 +1,106 @@
package get
import (
"fmt"
"github.com/cirruslabs/orchard/internal/structpath"
"github.com/cirruslabs/orchard/pkg/client"
v1 "github.com/cirruslabs/orchard/pkg/resource/v1"
"github.com/dustin/go-humanize"
"github.com/gosuri/uitable"
"github.com/samber/lo"
"github.com/spf13/cobra"
"strings"
"time"
)
func newGetVMCommand() *cobra.Command {
command := &cobra.Command{
Use: "vm NAME",
Short: "Retrieve a VM and it's fields",
RunE: runGetVM,
Args: cobra.ExactArgs(1),
}
return command
}
func runGetVM(cmd *cobra.Command, args []string) error {
name := args[0]
client, err := client.New()
if err != nil {
return err
}
// Ability to retrieve resource fields (e.g. "orchard get vm macos/status")
splits := strings.Split(name, "/")
var path []string
if len(splits) > 1 {
name = splits[0]
path = splits[1:]
}
vm, err := client.VMs().Get(cmd.Context(), name)
if err != nil {
return err
}
// Ability to retrieve resource fields (e.g. "orchard get vm macos/status")
if len(path) != 0 {
result, ok := structpath.Lookup(*vm, path)
if !ok {
return fmt.Errorf("%w: failed to find the specified field \"%s\" or the field is not a string",
ErrGetFailed, strings.Join(path, "/"))
}
fmt.Println(result)
return nil
}
table := uitable.New()
table.AddRow("Name", vm.Name)
createdAtInfo := humanize.RelTime(vm.CreatedAt, time.Now(), "ago", "in the future")
table.AddRow("Created", createdAtInfo)
table.AddRow("Image", vm.Image)
table.AddRow("Image pull policy", vm.ImagePullPolicy)
table.AddRow("CPU", vm.CPU)
table.AddRow("Memory", vm.Memory)
table.AddRow("Softnet enabled", vm.NetSoftnet)
table.AddRow("Bridged networking interface", nonEmptyOrNone(vm.NetBridged))
table.AddRow("Headless mode", vm.Headless)
table.AddRow("Status", vm.Status)
table.AddRow("Status message", vm.StatusMessage)
table.AddRow("Assigned worker", nonEmptyOrNone(vm.Worker))
table.AddRow("Restart policy", vm.RestartPolicy)
restartedAtInfo := "never"
if !vm.RestartedAt.IsZero() {
restartedAtInfo = humanize.RelTime(vm.RestartedAt, time.Now(), "ago", "in the future")
}
table.AddRow("Restarted", restartedAtInfo)
table.AddRow("Restart count", vm.RestartCount)
var resourcesInfo string
if len(vm.Resources) != 0 {
resourceDescriptions := lo.MapToSlice(vm.Resources, func(key string, value uint64) string {
return fmt.Sprintf("%s: %d", key, value)
})
resourcesInfo = strings.Join(resourceDescriptions, "\n")
}
table.AddRow("Resources", nonEmptyOrNone(resourcesInfo))
var hostDirsInfo string
if len(vm.HostDirs) != 0 {
hostDirsDescriptions := lo.Map(vm.HostDirs, func(hostDir v1.HostDir, index int) string {
return hostDir.String()
})
hostDirsInfo = strings.Join(hostDirsDescriptions, "\n")
}
table.AddRow("Host directories", nonEmptyOrNone(hostDirsInfo))
fmt.Println(table)
return nil
}

View File

@ -50,6 +50,16 @@ func (controller *Controller) createVM(ctx *gin.Context) responder.Responder {
vm.Resources[v1.ResourceTartVMs] = 1 vm.Resources[v1.ResourceTartVMs] = 1
} }
// Validate image pull policy and provide a default value if it's missing
if vm.ImagePullPolicy != "" {
if _, err := v1.NewImagePullPolicyFromString(string(vm.ImagePullPolicy)); err != nil {
return responder.JSON(http.StatusPreconditionFailed,
NewErrorResponse("unsupported image pull policy: %q", vm.ImagePullPolicy))
}
} else {
vm.ImagePullPolicy = v1.ImagePullPolicyIfNotPresent
}
// Validate restart policy and provide a default value if it's missing // Validate restart policy and provide a default value if it's missing
if vm.RestartPolicy != "" { if vm.RestartPolicy != "" {
if _, err := v1.NewRestartPolicyFromString(string(vm.RestartPolicy)); err != nil { if _, err := v1.NewRestartPolicyFromString(string(vm.RestartPolicy)); err != nil {

View File

@ -1,6 +1,7 @@
package structpath package structpath
import ( import (
"fmt"
"reflect" "reflect"
"strings" "strings"
) )
@ -23,9 +24,14 @@ func Lookup(target interface{}, path []string) (s string, ok bool) {
} }
result, ok := currentValue.Interface().(string) result, ok := currentValue.Interface().(string)
if !ok { if ok {
return "", false
}
return result, true return result, true
} }
stringerResult, ok := currentValue.Interface().(fmt.Stringer)
if ok {
return stringerResult.String(), true
}
return "", false
}

View File

@ -59,11 +59,22 @@ func NewVM(
wg: &sync.WaitGroup{}, wg: &sync.WaitGroup{},
} }
// Clone the VM so `run` and `ip` are not racing // Optionally pull and clone the VM so that `run` and `ip` will not be racing
vm.logger.Debugf("creating VM") vm.logger.Debugf("creating VM")
if vmResource.ImagePullPolicy == v1.ImagePullPolicyAlways {
_, _, err := tart.Tart(ctx, vm.logger, "pull", vm.Resource.Image)
if err != nil {
vm.setErr(fmt.Errorf("failed to pull the VM: %w", err))
return vm, nil
}
}
if err := vm.cloneAndConfigure(ctx); err != nil { if err := vm.cloneAndConfigure(ctx); err != nil {
return nil, fmt.Errorf("failed to clone the VM: %w", err) vm.setErr(fmt.Errorf("failed to clone the VM: %w", err))
return vm, nil
} }
vm.wg.Add(1) vm.wg.Add(1)

View File

@ -0,0 +1,26 @@
package v1
import (
"errors"
"fmt"
)
var ErrInvalidImagePullPolicy = errors.New("invalid image pull policy")
type ImagePullPolicy string
const (
ImagePullPolicyIfNotPresent ImagePullPolicy = "IfNotPresent"
ImagePullPolicyAlways ImagePullPolicy = "Always"
)
func NewImagePullPolicyFromString(s string) (ImagePullPolicy, error) {
switch s {
case string(ImagePullPolicyIfNotPresent):
return ImagePullPolicyIfNotPresent, nil
case string(ImagePullPolicyAlways):
return ImagePullPolicyAlways, nil
default:
return "", fmt.Errorf("%w %q", ErrInvalidImagePullPolicy, s)
}
}

View File

@ -20,6 +20,7 @@ type Meta struct {
type VM struct { type VM struct {
Image string `json:"image"` Image string `json:"image"`
ImagePullPolicy ImagePullPolicy `json:"imagePullPolicy"`
CPU uint64 `json:"cpu"` CPU uint64 `json:"cpu"`
Memory uint64 `json:"memory"` Memory uint64 `json:"memory"`
NetSoftnet bool `json:"net-softnet"` NetSoftnet bool `json:"net-softnet"`
@ -78,6 +79,10 @@ func (vm VM) TerminalState() bool {
type VMStatus string type VMStatus string
func (vmStatus VMStatus) String() string {
return string(vmStatus)
}
const ( const (
// VMStatusPending is set by the Controller for all newly-created VM resources. // VMStatusPending is set by the Controller for all newly-created VM resources.
VMStatusPending VMStatus = "pending" VMStatusPending VMStatus = "pending"