Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Commit

Permalink
Make it possible to run ignite run/create declaratively. Fixup vari…
Browse files Browse the repository at this point in the history
…ous issues from the first API addition commit
  • Loading branch information
luxas committed Jul 2, 2019
1 parent 87cce1f commit 5733364
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 71 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ WHAT?=ignite
PROJECT = github.com/weaveworks/ignite
APIS_DIR = ${PROJECT}/pkg/apis
API_DIRS = ${APIS_DIR}/ignite/v1alpha1,${APIS_DIR}/meta/v1alpha1
CACHE_DIR = /tmp/go-cache
CACHE_DIR = $(shell pwd)/bin/cache

all: binary
binary:
Expand Down
40 changes: 28 additions & 12 deletions cmd/ignite/cmd/cmdutil/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ func AddNameFlag(fs *pflag.FlagSet, name *string) {
fs.StringVarP(name, "name", "n", *name, "Specify the name")
}

func AddConfigFlag(fs *pflag.FlagSet, configFile *string) {
fs.StringVar(configFile, "config", *configFile, "Specify a path to a file with the API resources you want to pass")
}

func AddInteractiveFlag(fs *pflag.FlagSet, interactive *bool) {
fs.BoolVarP(interactive, "interactive", "i", *interactive, "Attach to the VM after starting")
}
Expand All @@ -22,18 +26,30 @@ func AddImportKernelFlags(fs *pflag.FlagSet, kernelName *string) {
fs.StringVarP(kernelName, "import-kernel", "k", *kernelName, "Import a new kernel from /boot/vmlinux in the image with the specified name")
}

func SizeVar(fs *pflag.FlagSet, ptr interface{}, flagName string, defaultSizeBytes int64, description string) {
SizeVarP(fs, ptr, flagName, "", defaultSizeBytes, description)
type SizeFlag struct {
value ignitemeta.Size
}

func (sf *SizeFlag) Set(val string) error {
var err error
sf.value, err = ignitemeta.NewSizeFromString(val)
return err
}

func (sf *SizeFlag) String() string {
return sf.value.String()
}

func (sf *SizeFlag) Type() string {
return "size"
}

var _ pflag.Value = &SizeFlag{}

func SizeVar(fs *pflag.FlagSet, ptr *ignitemeta.Size, name string, defVal ignitemeta.Size, usage string) {
SizeVarP(fs, ptr, name, "", defVal, usage)
}

func SizeVarP(fs *pflag.FlagSet, ptr interface{}, flagName, shorthand string, defaultSizeBytes int64, description string) {
size := ignitemeta.NewSizeFromBytes(uint64(defaultSizeBytes))
switch v := ptr.(type) {
case *string:
fs.StringVarP(v, flagName, shorthand, size.String(), description)
case *int64:
fs.Int64VarP(v, flagName, shorthand, size.Int64(), description)
default:
panic("invalid size flag set up")
}
func SizeVarP(fs *pflag.FlagSet, ptr *ignitemeta.Size, name, shorthand string, defVal ignitemeta.Size, usage string) {
fs.VarP(&SizeFlag{value: defVal}, name, shorthand, usage)
}
17 changes: 8 additions & 9 deletions cmd/ignite/cmd/vmcmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (

// NewCmdCreate creates a new VM given an image and a kernel
func NewCmdCreate(out io.Writer) *cobra.Command {
cf := &run.CreateFlags{}
cf := run.NewCreateFlags()

cmd := &cobra.Command{
Use: "create <image>",
Expand All @@ -38,10 +38,10 @@ func NewCmdCreate(out io.Writer) *cobra.Command {
--memory 2048 \
--size 6GB
`),
Args: cobra.ExactArgs(1),
Args: cobra.RangeArgs(0, 1),
Run: func(cmd *cobra.Command, args []string) {
errutils.Check(func() error {
co, err := cf.NewCreateOptions(loader.NewResLoader(), args[0])
co, err := cf.NewCreateOptions(loader.NewResLoader(), args)
if err != nil {
return err
}
Expand All @@ -56,12 +56,11 @@ func NewCmdCreate(out io.Writer) *cobra.Command {
}

func addCreateFlags(fs *pflag.FlagSet, cf *run.CreateFlags) {
cmdutil.AddNameFlag(fs, &cf.Name)
fs.Int64Var(&cf.CPUs, "cpus", constants.VM_DEFAULT_CPUS, "VM vCPU count, 1 or even numbers between 1 and 32")
// TODO: This should allow for string inputs, e.g. 5MB or 12345kB, instead of hardcoding to MBs
// Serialized, the string format shall be preserved, too.
fs.Int64Var(&cf.Memory, "memory", constants.VM_DEFAULT_MEMORY, "Amount of RAM in MiB to allocate for the VM")
cmdutil.SizeVarP(fs, &cf.Size, "size", "s", constants.VM_DEFAULT_SIZE, "VM filesystem size, for example 5GB or 2048MB")
cmdutil.AddNameFlag(fs, &cf.VM.ObjectMeta.Name)
cmdutil.AddConfigFlag(fs, &cf.ConfigFile)
fs.Uint64Var(&cf.VM.Spec.CPUs, "cpus", cf.VM.Spec.CPUs, "VM vCPU count, 1 or even numbers between 1 and 32")
cmdutil.SizeVar(fs, &cf.VM.Spec.Memory, "memory", cf.VM.Spec.Memory, "Amount of RAM to allocate for the VM")
cmdutil.SizeVarP(fs, &cf.VM.Spec.DiskSize, "size", "s", cf.VM.Spec.DiskSize, "VM filesystem size, for example 5GB or 2048MB")
fs.StringSliceVarP(&cf.CopyFiles, "copy-files", "f", nil, "Copy files from the host to the created VM")
fs.StringVarP(&cf.KernelName, "kernel", "k", "", "Specify a kernel to use. By default this equals the image name")
fs.StringVar(&cf.KernelCmd, "kernel-args", constants.VM_DEFAULT_KERNEL_ARGS, "Set the command line for the kernel")
Expand Down
6 changes: 3 additions & 3 deletions cmd/ignite/cmd/vmcmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
// NewCmdRun creates, starts (and attaches to) a VM
func NewCmdRun(out io.Writer) *cobra.Command {
rf := &run.RunFlags{
CreateFlags: &run.CreateFlags{},
CreateFlags: run.NewCreateFlags(),
StartFlags: &run.StartFlags{},
}

Expand All @@ -38,10 +38,10 @@ func NewCmdRun(out io.Writer) *cobra.Command {
--memory 2048 \
--size 10G
`),
Args: cobra.ExactArgs(1),
Args: cobra.RangeArgs(0, 1),
Run: func(cmd *cobra.Command, args []string) {
errutils.Check(func() error {
ro, err := rf.NewRunOptions(loader.NewResLoader(), args[0])
ro, err := rf.NewRunOptions(loader.NewResLoader(), args)
if err != nil {
return err
}
Expand Down
60 changes: 40 additions & 20 deletions cmd/ignite/run/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import (
"path"
"strings"

"github.com/weaveworks/ignite/pkg/metadata/loader"

"github.com/spf13/pflag"

"github.com/c2h5oh/datasize"
"github.com/weaveworks/ignite/pkg/apis/ignite/scheme"
"github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1"
"github.com/weaveworks/ignite/pkg/metadata"
"github.com/weaveworks/ignite/pkg/metadata/imgmd"
"github.com/weaveworks/ignite/pkg/metadata/kernmd"
"github.com/weaveworks/ignite/pkg/metadata/loader"
"github.com/weaveworks/ignite/pkg/metadata/vmmd"
"github.com/weaveworks/ignite/pkg/util"
)
Expand Down Expand Up @@ -56,15 +55,22 @@ var _ pflag.Value = &SSHFlag{}

const vmAuthorizedKeys = "/root/.ssh/authorized_keys"

func NewCreateFlags() *CreateFlags {
cf := &CreateFlags{
VM: &v1alpha1.VM{},
}
scheme.Scheme.Default(cf.VM)
return cf
}

type CreateFlags struct {
Name string
CPUs int64
Memory int64
Size string
// TODO: Also respect CopyFiles, SSH, PortMappings, Networking mode, and kernel stuff from the config file
CopyFiles []string
KernelName string
KernelCmd string
SSH *SSHFlag
ConfigFile string
VM *v1alpha1.VM
}

type createOptions struct {
Expand All @@ -76,20 +82,40 @@ type createOptions struct {
fileMappings map[string]string
}

func (cf *CreateFlags) NewCreateOptions(l *loader.ResLoader, imageMatch string) (*createOptions, error) {
func (cf *CreateFlags) NewCreateOptions(l *loader.ResLoader, args []string) (*createOptions, error) {
if len(args) == 1 {
cf.VM.Spec.Image = &v1alpha1.ImageClaim{
Type: v1alpha1.ImageSourceTypeDocker,
Ref: args[0],
}
}

// Decode the config file if given
if len(cf.ConfigFile) != 0 {
// Marshal into a "clean" object, discard all flag input
cf.VM = &v1alpha1.VM{}
if err := scheme.DecodeFileInto(cf.ConfigFile, cf.VM); err != nil {
return nil, err
}
}

if cf.VM.Spec.Image == nil || len(cf.VM.Spec.Image.Ref) == 0 {
return nil, fmt.Errorf("you must specify an image to run either via CLI args or a config file")
}

var err error
co := &createOptions{CreateFlags: cf}

if allImages, err := l.Images(); err == nil {
if co.image, err = allImages.MatchSingle(imageMatch); err != nil {
if co.image, err = allImages.MatchSingle(cf.VM.Spec.Image.Ref); err != nil {
return nil, err
}
} else {
return nil, err
}

if len(cf.KernelName) == 0 {
cf.KernelName = imageMatch
cf.KernelName = cf.VM.Spec.Image.Ref
}

if allKernels, err := l.Kernels(); err == nil {
Expand All @@ -115,14 +141,14 @@ func (cf *CreateFlags) NewCreateOptions(l *loader.ResLoader, imageMatch string)

func Create(co *createOptions) error {
// Verify the name
name, err := metadata.NewName(co.Name, &co.allVMs)
name, err := metadata.NewName(co.VM.Name, &co.allVMs)
if err != nil {
return err
}

// Create new metadata for the VM
if co.newVM, err = vmmd.NewVMMetadata(nil, name,
vmmd.NewVMObjectData(co.image.ID, co.kernel.ID, co.CPUs, co.Memory, co.KernelCmd)); err != nil {
vmmd.NewVMObjectData(co.image.ID, co.kernel.ID, int64(co.VM.Spec.CPUs), int64(co.VM.Spec.Memory.MBytes()), co.KernelCmd)); err != nil {
return err
}
defer co.newVM.Cleanup(false) // TODO: Handle silent
Expand All @@ -137,14 +163,8 @@ func Create(co *createOptions) error {
return err
}

// Parse the given overlay size
var size datasize.ByteSize
if err := size.UnmarshalText([]byte(co.Size)); err != nil {
return err
}

// Allocate the overlay file
if err := co.newVM.AllocateOverlay(size.Bytes()); err != nil {
if err := co.newVM.AllocateOverlay(co.VM.Spec.DiskSize.Bytes()); err != nil {
return err
}

Expand Down
16 changes: 8 additions & 8 deletions cmd/ignite/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@ type runOptions struct {
*startOptions
}

func (rf *RunFlags) NewRunOptions(l *loader.ResLoader, imageMatch string) (*runOptions, error) {
func (rf *RunFlags) NewRunOptions(l *loader.ResLoader, args []string) (*runOptions, error) {
co, err := rf.NewCreateOptions(l, args)
if err != nil {
return nil, err
}

// Logic to import the image if it doesn't exist
if allImages, err := l.Images(); err == nil {
if _, err := allImages.MatchSingle(imageMatch); err != nil { // TODO: Use this match in create?
if _, err := allImages.MatchSingle(co.VM.Spec.Image.Ref); err != nil { // TODO: Use this match in create?
if _, ok := err.(*metadata.NonexistentError); !ok {
return nil, err
}

io, err := (&ImportFlags{}).NewImportOptions(l, imageMatch)
io, err := (&ImportFlags{}).NewImportOptions(l, co.VM.Spec.Image.Ref)
if err != nil {
return nil, err
}
Expand All @@ -36,11 +41,6 @@ func (rf *RunFlags) NewRunOptions(l *loader.ResLoader, imageMatch string) (*runO
return nil, err
}

co, err := rf.NewCreateOptions(l, imageMatch)
if err != nil {
return nil, err
}

so := &startOptions{
StartFlags: rf.StartFlags,
attachOptions: &attachOptions{
Expand Down
31 changes: 31 additions & 0 deletions docs/declarative-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## Run Ignite VMs declaratively

Flags can be convenient for simple use cases, but have many limitations.
In more advanced use-cases, and to eventually allow GitOps flows, there is
an other way: telling Ignite what to do _declaratively_, using a file containing
an API object.

The first commands to support this feature is `ignite run` and `ignite create`.
An example file as follows:

```yaml
apiVersion: ignite.weave.works/v1alpha1
kind: VM
metadata:
name: test-vm
spec:
image:
ref: weaveworks/ignite-ubuntu
type: Docker
cpus: 2
diskSize: 3GB
memory: 800MB
```
This API object specifies a need for 2 vCPUs, 800MB of RAM and 3GB of disk.
We can tell Ignite to make this happen using simply:
```console
$ ignite run --config test-vm.yaml
```
4 changes: 2 additions & 2 deletions pkg/apis/ignite/v1alpha1/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ func SetDefaults_VMSpec(obj *VMSpec) {
obj.Memory = ignitemeta.NewSizeFromBytes(constants.VM_DEFAULT_MEMORY)
}

if obj.Size == ignitemeta.EmptySize {
obj.Size = ignitemeta.NewSizeFromBytes(constants.VM_DEFAULT_SIZE)
if obj.DiskSize == ignitemeta.EmptySize {
obj.DiskSize = ignitemeta.NewSizeFromBytes(constants.VM_DEFAULT_SIZE)
}
}

Expand Down
21 changes: 14 additions & 7 deletions pkg/apis/ignite/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type ImageSource struct {
ID string `json:"id"`
// Name defines the user-friendly name of the imported source
Name string `json:"name"`
// ignitemeta.Size defines the size of the source in bytes
// Size defines the size of the source in bytes
Size ignitemeta.Size `json:"size"`
}

Expand Down Expand Up @@ -146,19 +146,26 @@ type VM struct {

// VMSpec describes the configuration of a VM
type VMSpec struct {
CPUs uint64 `json:"cpus"`
Memory ignitemeta.Size `json:"memory"`
Size ignitemeta.Size `json:"size"`
Ports []PortMapping `json:"ports"`
Image *ImageClaim `json:"image"`
CPUs uint64 `json:"cpus"`
Memory ignitemeta.Size `json:"memory"`
DiskSize ignitemeta.Size `json:"diskSize"`
Ports []PortMapping `json:"ports,omitempty"`
// This will be done at either "ignite start" or "ignite create" time
// TODO: We might to revisit this later
CopyFiles []FileMapping `json:"copyFiles"`
CopyFiles []FileMapping `json:"copyFiles,omitempty"`
// SSH specifies how the SSH setup should be done
// SSH appends to CopyFiles when active
// nil here means "don't do anything special"
// An empty struct means "generate a new SSH key and copy it in"
// Specifying a path means "use this public key"
SSH *SSH `json:"ssh"`
SSH *SSH `json:"ssh,omitempty"`
}

// ImageClaim specifies a claim to import an image
type ImageClaim struct {
Type ImageSourceType `json:"type"`
Ref string `json:"ref"`
}

// PortMapping defines a port mapping between the VM and the host
Expand Down
Loading

0 comments on commit 5733364

Please sign in to comment.