diff --git a/Dockerfile b/Dockerfile index cf064bf02..6367e3a21 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,9 @@ RUN chmod +x /usr/local/bin/firecracker /usr/local/bin/ignite-spawn && \ ln -s /usr/local/bin/firecracker /firecracker && \ ln -s /usr/local/bin/ignite-spawn /ignite-spawn +# Create a directory to host any volumes exposed from host +RUN mkdir /volumes + # Use a multi-stage build to allow the resulting image to only consist of one layer # This makes it more lightweight FROM scratch diff --git a/cmd/ignite/cmd/cmdutil/flags.go b/cmd/ignite/cmd/cmdutil/flags.go index 9676dfa77..a6ac6d7bb 100644 --- a/cmd/ignite/cmd/cmdutil/flags.go +++ b/cmd/ignite/cmd/cmdutil/flags.go @@ -1,13 +1,7 @@ package cmdutil import ( - "fmt" - "strings" - "github.com/spf13/pflag" - api "github.com/weaveworks/ignite/pkg/apis/ignite" - meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1" - "github.com/weaveworks/ignite/pkg/util" ) // This file contains a collection of common flag adders @@ -26,143 +20,3 @@ func AddInteractiveFlag(fs *pflag.FlagSet, interactive *bool) { func AddForceFlag(fs *pflag.FlagSet, force *bool) { fs.BoolVarP(force, "force", "f", *force, "Force this operation. Warning, use of this mode may have unintended consequences.") } - -type SizeFlag struct { - value *meta.Size -} - -func (sf *SizeFlag) Set(val string) error { - var err error - *sf.value, err = meta.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 *meta.Size, name, usage string) { - SizeVarP(fs, ptr, name, "", usage) -} - -func SizeVarP(fs *pflag.FlagSet, ptr *meta.Size, name, shorthand, usage string) { - fs.VarP(&SizeFlag{value: ptr}, name, shorthand, usage) -} - -type OCIImageRefFlag struct { - value *meta.OCIImageRef -} - -func (of *OCIImageRefFlag) Set(val string) error { - var err error - *of.value, err = meta.NewOCIImageRef(val) - return err -} - -func (of *OCIImageRefFlag) String() string { - if of.value == nil { - return "" - } - return of.value.String() -} - -func (of *OCIImageRefFlag) Type() string { - return "oci-image" -} - -var _ pflag.Value = &OCIImageRefFlag{} - -func OCIImageRefVar(fs *pflag.FlagSet, ptr *meta.OCIImageRef, name, usage string) { - OCIImageRefVarP(fs, ptr, name, "", usage) -} - -func OCIImageRefVarP(fs *pflag.FlagSet, ptr *meta.OCIImageRef, name, shorthand, usage string) { - fs.VarP(&OCIImageRefFlag{value: ptr}, name, shorthand, usage) -} - -type NetworkModeFlag struct { - value *api.NetworkMode -} - -func (nf *NetworkModeFlag) Set(val string) error { - *nf.value = api.NetworkMode(val) - return nil -} - -func (nf *NetworkModeFlag) String() string { - if nf.value == nil { - return "" - } - return nf.value.String() -} - -func (nf *NetworkModeFlag) Type() string { - return "network-mode" -} - -var _ pflag.Value = &NetworkModeFlag{} - -func NetworkModeVar(fs *pflag.FlagSet, ptr *api.NetworkMode) { - fs.Var(&NetworkModeFlag{value: ptr}, "net", fmt.Sprintf("Networking mode to use. Available options are: %v", api.GetNetworkModes())) -} - -// SSHFlag is the pflag.Value custom flag for `ignite create --ssh` -type SSHFlag struct { - value *api.SSH -} - -var _ pflag.Value = &SSHFlag{} - -func (sf *SSHFlag) Set(x string) error { - if x == "" { // only --ssh was specified, then this default "no-value" string is set - *sf.value = api.SSH{ - Generate: true, - } - } else if len(x) > 0 { // some other path was set - importKey := x - // Always digest the public key - if !strings.HasSuffix(importKey, ".pub") { - importKey = fmt.Sprintf("%s.pub", importKey) - } - // verify the file exists - if !util.FileExists(importKey) { - return fmt.Errorf("invalid SSH key: %s", importKey) - } - - // Set the SSH PublicKey field - *sf.value = api.SSH{ - PublicKey: importKey, - } - } - return nil -} - -func (sf *SSHFlag) String() string { - if sf.value == nil { - return "" - } - - return sf.value.PublicKey -} - -func (sf *SSHFlag) Type() string { - return "" -} - -func (sf *SSHFlag) IsBoolFlag() bool { - return true -} - -func SSHVar(fs *pflag.FlagSet, ptr *api.SSH) { - fs.Var(&SSHFlag{value: ptr}, "ssh", "Enable SSH for the VM. If is given, it will be imported as the public key. If just '--ssh' is specified, a new keypair will be generated.") - - sshFlag := fs.Lookup("ssh") - sshFlag.NoOptDefVal = "" - sshFlag.DefValue = "is unset, which disables SSH access to the VM" -} diff --git a/cmd/ignite/cmd/cmdutil/networkflag.go b/cmd/ignite/cmd/cmdutil/networkflag.go new file mode 100644 index 000000000..71c59393b --- /dev/null +++ b/cmd/ignite/cmd/cmdutil/networkflag.go @@ -0,0 +1,34 @@ +package cmdutil + +import ( + "fmt" + + "github.com/spf13/pflag" + api "github.com/weaveworks/ignite/pkg/apis/ignite" +) + +type NetworkModeFlag struct { + value *api.NetworkMode +} + +func (nf *NetworkModeFlag) Set(val string) error { + *nf.value = api.NetworkMode(val) + return nil +} + +func (nf *NetworkModeFlag) String() string { + if nf.value == nil { + return "" + } + return nf.value.String() +} + +func (nf *NetworkModeFlag) Type() string { + return "network-mode" +} + +var _ pflag.Value = &NetworkModeFlag{} + +func NetworkModeVar(fs *pflag.FlagSet, ptr *api.NetworkMode) { + fs.Var(&NetworkModeFlag{value: ptr}, "net", fmt.Sprintf("Networking mode to use. Available options are: %v", api.GetNetworkModes())) +} diff --git a/cmd/ignite/cmd/cmdutil/ociflag.go b/cmd/ignite/cmd/cmdutil/ociflag.go new file mode 100644 index 000000000..f12ac2d06 --- /dev/null +++ b/cmd/ignite/cmd/cmdutil/ociflag.go @@ -0,0 +1,37 @@ +package cmdutil + +import ( + "github.com/spf13/pflag" + meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1" +) + +type OCIImageRefFlag struct { + value *meta.OCIImageRef +} + +func (of *OCIImageRefFlag) Set(val string) error { + var err error + *of.value, err = meta.NewOCIImageRef(val) + return err +} + +func (of *OCIImageRefFlag) String() string { + if of.value == nil { + return "" + } + return of.value.String() +} + +func (of *OCIImageRefFlag) Type() string { + return "oci-image" +} + +var _ pflag.Value = &OCIImageRefFlag{} + +func OCIImageRefVar(fs *pflag.FlagSet, ptr *meta.OCIImageRef, name, usage string) { + OCIImageRefVarP(fs, ptr, name, "", usage) +} + +func OCIImageRefVarP(fs *pflag.FlagSet, ptr *meta.OCIImageRef, name, shorthand, usage string) { + fs.VarP(&OCIImageRefFlag{value: ptr}, name, shorthand, usage) +} diff --git a/cmd/ignite/cmd/cmdutil/sizeflag.go b/cmd/ignite/cmd/cmdutil/sizeflag.go new file mode 100644 index 000000000..39361a7f0 --- /dev/null +++ b/cmd/ignite/cmd/cmdutil/sizeflag.go @@ -0,0 +1,34 @@ +package cmdutil + +import ( + "github.com/spf13/pflag" + meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1" +) + +type SizeFlag struct { + value *meta.Size +} + +func (sf *SizeFlag) Set(val string) error { + var err error + *sf.value, err = meta.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 *meta.Size, name, usage string) { + SizeVarP(fs, ptr, name, "", usage) +} + +func SizeVarP(fs *pflag.FlagSet, ptr *meta.Size, name, shorthand, usage string) { + fs.VarP(&SizeFlag{value: ptr}, name, shorthand, usage) +} diff --git a/cmd/ignite/cmd/cmdutil/sshflag.go b/cmd/ignite/cmd/cmdutil/sshflag.go new file mode 100644 index 000000000..e55aeb579 --- /dev/null +++ b/cmd/ignite/cmd/cmdutil/sshflag.go @@ -0,0 +1,65 @@ +package cmdutil + +import ( + "fmt" + "strings" + + "github.com/spf13/pflag" + api "github.com/weaveworks/ignite/pkg/apis/ignite" + "github.com/weaveworks/ignite/pkg/util" +) + +// SSHFlag is the pflag.Value custom flag for `ignite create --ssh` +type SSHFlag struct { + value *api.SSH +} + +var _ pflag.Value = &SSHFlag{} + +func (sf *SSHFlag) Set(x string) error { + if x == "" { // only --ssh was specified, then this default "no-value" string is set + *sf.value = api.SSH{ + Generate: true, + } + } else if len(x) > 0 { // some other path was set + importKey := x + // Always digest the public key + if !strings.HasSuffix(importKey, ".pub") { + importKey = fmt.Sprintf("%s.pub", importKey) + } + // verify the file exists + if !util.FileExists(importKey) { + return fmt.Errorf("invalid SSH key: %s", importKey) + } + + // Set the SSH PublicKey field + *sf.value = api.SSH{ + PublicKey: importKey, + } + } + return nil +} + +func (sf *SSHFlag) String() string { + if sf.value == nil { + return "" + } + + return sf.value.PublicKey +} + +func (sf *SSHFlag) Type() string { + return "" +} + +func (sf *SSHFlag) IsBoolFlag() bool { + return true +} + +func SSHVar(fs *pflag.FlagSet, ptr *api.SSH) { + fs.Var(&SSHFlag{value: ptr}, "ssh", "Enable SSH for the VM. If is given, it will be imported as the public key. If just '--ssh' is specified, a new keypair will be generated.") + + sshFlag := fs.Lookup("ssh") + sshFlag.NoOptDefVal = "" + sshFlag.DefValue = "is unset, which disables SSH access to the VM" +} diff --git a/cmd/ignite/cmd/cmdutil/volumeflag.go b/cmd/ignite/cmd/cmdutil/volumeflag.go new file mode 100644 index 000000000..3973800f9 --- /dev/null +++ b/cmd/ignite/cmd/cmdutil/volumeflag.go @@ -0,0 +1,70 @@ +package cmdutil + +import ( + "fmt" + "strings" + + "github.com/spf13/pflag" + api "github.com/weaveworks/ignite/pkg/apis/ignite" +) + +var ( + formatErr = fmt.Errorf("volumes must be specified in the /host/path:/vm/path format") +) + +// VolumeFlag is the pflag.Value custom flag for `ignite create --volume` +type VolumeFlag struct { + value *api.VMStorageSpec + s string +} + +var _ pflag.Value = &VolumeFlag{} + +func (vf *VolumeFlag) Set(x string) error { + entries := strings.Split(x, ",") // We take in a comma-separated list + var storage api.VMStorageSpec + + for i, entry := range entries { + paths := strings.Split(entry, ":") + + if len(paths) != 2 { + return formatErr + } + + volumeName := fmt.Sprintf("volume%d", i) + + // Create the Volume + storage.Volumes = append(storage.Volumes, api.Volume{ + Name: volumeName, + BlockDevice: &api.BlockDeviceVolume{ + Path: paths[0], + }, + }) + + // Create the VolumeMount + storage.VolumeMounts = append(storage.VolumeMounts, api.VolumeMount{ + Name: volumeName, + MountPath: paths[1], + }) + } + + *vf.value = storage + vf.s = x // String should return the input after a successful Set + return nil +} + +func (vf *VolumeFlag) Type() string { + return "volume" +} + +func (vf *VolumeFlag) String() string { + return vf.s +} + +func VolumeVar(fs *pflag.FlagSet, ptr *api.VMStorageSpec, name, usage string) { + VolumeVarP(fs, ptr, name, "", usage) +} + +func VolumeVarP(fs *pflag.FlagSet, ptr *api.VMStorageSpec, name, shorthand, usage string) { + fs.VarP(&VolumeFlag{value: ptr}, name, shorthand, usage) +} diff --git a/cmd/ignite/cmd/vmcmd/create.go b/cmd/ignite/cmd/vmcmd/create.go index 876380eb4..659e7aced 100644 --- a/cmd/ignite/cmd/vmcmd/create.go +++ b/cmd/ignite/cmd/vmcmd/create.go @@ -77,4 +77,5 @@ func addCreateFlags(fs *pflag.FlagSet, cf *run.CreateFlags) { cmdutil.OCIImageRefVarP(fs, &cf.VM.Spec.Kernel.OCIClaim.Ref, "kernel-image", "k", "Specify an OCI image containing the kernel at /boot/vmlinux and optionally, modules") cmdutil.NetworkModeVar(fs, &cf.VM.Spec.Network.Mode) cmdutil.SSHVar(fs, &cf.SSH) + cmdutil.VolumeVarP(fs, &cf.VM.Spec.Storage, "volumes", "v", "Expose block devices from the host inside the VM") } diff --git a/cmd/ignite/run/create.go b/cmd/ignite/run/create.go index 7b49fd9f2..3a3e25a3d 100644 --- a/cmd/ignite/run/create.go +++ b/cmd/ignite/run/create.go @@ -51,8 +51,7 @@ func (cf *CreateFlags) constructVMFromCLI(args []string) error { // Parse the --copy-files flag var err error - cf.VM.Spec.CopyFiles, err = parseFileMappings(cf.CopyFiles) - if err != nil { + if cf.VM.Spec.CopyFiles, err = parseFileMappings(cf.CopyFiles); err != nil { return err } @@ -130,9 +129,9 @@ func Create(co *createOptions) error { return metadata.Success(co.VM) } -// TODO: Move this to meta, or an helper in api +// TODO: Move this to meta, or a helper in API func parseFileMappings(fileMappings []string) ([]api.FileMapping, error) { - result := []api.FileMapping{} + result := make([]api.FileMapping, 0, len(fileMappings)) for _, fileMapping := range fileMappings { files := strings.Split(fileMapping, ":") diff --git a/cmd/ignite/run/start.go b/cmd/ignite/run/start.go index 729d2609c..ef5e5d2c1 100644 --- a/cmd/ignite/run/start.go +++ b/cmd/ignite/run/start.go @@ -22,7 +22,7 @@ func (sf *StartFlags) NewStartOptions(vmMatch string) (*startOptions, error) { return nil, err } - // Disable running check as it takes a while for the in-container Ignite to update the state + // Disable running check as it takes a while for ignite-spawn to update the state ao.checkRunning = false return &startOptions{sf, ao}, nil diff --git a/docs/api/ignite_v1alpha1.md b/docs/api/ignite_v1alpha1.md index c1d386920..ff49bdcc1 100644 --- a/docs/api/ignite_v1alpha1.md +++ b/docs/api/ignite_v1alpha1.md @@ -15,6 +15,12 @@ - [Constants](#pkg-constants) - [Variables](#pkg-variables) + - [func Convert\_ignite\_VMSpec\_To\_v1alpha1\_VMSpec(in + *ignite.VMSpec, out *VMSpec, s conversion.Scope) + error](#Convert_ignite_VMSpec_To_v1alpha1_VMSpec) + - [func Convert\_v1alpha1\_VMSpec\_To\_ignite\_VMSpec(in *VMSpec, out + *ignite.VMSpec, s conversion.Scope) + error](#Convert_v1alpha1_VMSpec_To_ignite_VMSpec) - [func SetDefaults\_OCIImageClaim(obj \*OCIImageClaim)](#SetDefaults_OCIImageClaim) - [func SetDefaults\_PoolSpec(obj \*PoolSpec)](#SetDefaults_PoolSpec) @@ -56,6 +62,7 @@ #### Package files +[conversion.go](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha1/conversion.go) [defaults.go](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha1/defaults.go) [doc.go](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha1/doc.go) [json.go](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha1/json.go) @@ -102,6 +109,24 @@ var SchemeGroupVersion = schema.GroupVersion{ SchemeGroupVersion is group version used to register these objects +## func [Convert\_ignite\_VMSpec\_To\_v1alpha1\_VMSpec](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha1/conversion.go?s=239:342#L9) + +``` go +func Convert_ignite_VMSpec_To_v1alpha1_VMSpec(in *ignite.VMSpec, out *VMSpec, s conversion.Scope) error +``` + +Convert\_ignite\_VMSpec\_To\_v1alpha1\_VMSpec calls the autogenerated +conversion function along with custom conversion logic + +## func [Convert\_v1alpha1\_VMSpec\_To\_ignite\_VMSpec](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha1/conversion.go?s=637:740#L15) + +``` go +func Convert_v1alpha1_VMSpec_To_ignite_VMSpec(in *VMSpec, out *ignite.VMSpec, s conversion.Scope) error +``` + +Convert\_ignite\_VMSpec\_To\_v1alpha1\_VMSpec calls the autogenerated +conversion function along with custom conversion logic + ## func [SetDefaults\_OCIImageClaim](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha1/defaults.go?s=263:313#L13) ``` go diff --git a/docs/api/ignite_v1alpha2.md b/docs/api/ignite_v1alpha2.md index 0f6fa62d8..fb386a366 100644 --- a/docs/api/ignite_v1alpha2.md +++ b/docs/api/ignite_v1alpha2.md @@ -24,6 +24,7 @@ \*VMNetworkSpec)](#SetDefaults_VMNetworkSpec) - [func SetDefaults\_VMSpec(obj \*VMSpec)](#SetDefaults_VMSpec) - [func SetDefaults\_VMStatus(obj \*VMStatus)](#SetDefaults_VMStatus) + - [type BlockDeviceVolume](#BlockDeviceVolume) - [type FileMapping](#FileMapping) - [type Image](#Image) - [type ImageSourceType](#ImageSourceType) @@ -53,6 +54,9 @@ - [type VMSpec](#VMSpec) - [type VMState](#VMState) - [type VMStatus](#VMStatus) + - [type VMStorageSpec](#VMStorageSpec) + - [type Volume](#Volume) + - [type VolumeMount](#VolumeMount) #### Package files @@ -138,7 +142,17 @@ func SetDefaults_VMSpec(obj *VMSpec) func SetDefaults_VMStatus(obj *VMStatus) ``` -## type [FileMapping](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=7677:7772#L206) +## type [BlockDeviceVolume](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=8095:8155#L218) + +``` go +type BlockDeviceVolume struct { + Path string `json:"path"` +} +``` + +BlockDeviceVolume defines a block device on the host + +## type [FileMapping](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=8390:8485#L229) ``` go type FileMapping struct { @@ -244,7 +258,7 @@ type KernelStatus struct { KernelStatus describes the status of a kernel -## type [NetworkMode](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=8126:8149#L221) +## type [NetworkMode](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=8839:8862#L244) ``` go type NetworkMode string @@ -261,7 +275,7 @@ const ( ) ``` -### func (NetworkMode) [String](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=8189:8226#L225) +### func (NetworkMode) [String](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=8902:8939#L248) ``` go func (nm NetworkMode) String() string @@ -383,7 +397,7 @@ type PoolStatus struct { PoolStatus defines the Pool’s current status -## type [SSH](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=7992:8069#L215) +## type [SSH](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=8705:8782#L238) ``` go type SSH struct { @@ -427,7 +441,7 @@ VM represents a virtual machine run by Firecracker These files are stored in /var/lib/firecracker/vm/{vm-id}/metadata.json +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -## type [VMImageSpec](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=7298:7367#L191) +## type [VMImageSpec](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=7348:7417#L191) ``` go type VMImageSpec struct { @@ -435,7 +449,7 @@ type VMImageSpec struct { } ``` -## type [VMKernelSpec](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=7369:7490#L195) +## type [VMKernelSpec](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=7419:7540#L195) ``` go type VMKernelSpec struct { @@ -444,7 +458,7 @@ type VMKernelSpec struct { } ``` -## type [VMNetworkSpec](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=7492:7610#L200) +## type [VMNetworkSpec](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=7542:7660#L200) ``` go type VMNetworkSpec struct { @@ -453,7 +467,7 @@ type VMNetworkSpec struct { } ``` -## type [VMSpec](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=6413:7296#L171) +## type [VMSpec](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=6413:7346#L171) ``` go type VMSpec struct { @@ -463,7 +477,7 @@ type VMSpec struct { Memory meta.Size `json:"memory"` DiskSize meta.Size `json:"diskSize"` Network VMNetworkSpec `json:"network"` - + Storage VMStorageSpec `json:"storage,omitempty"` // This will be done at either "ignite start" or "ignite create" time // TODO: We might revisit this later CopyFiles []FileMapping `json:"copyFiles,omitempty"` @@ -479,7 +493,7 @@ type VMSpec struct { VMSpec describes the configuration of a VM -## type [VMState](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=8623:8642#L238) +## type [VMState](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=9336:9355#L261) ``` go type VMState string @@ -495,7 +509,7 @@ const ( ) ``` -## type [VMStatus](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=8802:9023#L247) +## type [VMStatus](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=9515:9736#L270) ``` go type VMStatus struct { @@ -508,6 +522,39 @@ type VMStatus struct { VMStatus defines the status of a VM +## type [VMStorageSpec](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=7721:7865#L206) + +``` go +type VMStorageSpec struct { + Volumes []Volume `json:"volumes,omitempty"` + VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` +} +``` + +VMStorageSpec defines the VM’s Volumes and VolumeMounts + +## type [Volume](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=7906:8037#L212) + +``` go +type Volume struct { + Name string `json:"name"` + BlockDevice *BlockDeviceVolume `json:"blockDevice,omitempty"` +} +``` + +Volume defines named storage volume + +## type [VolumeMount](https://github.com/weaveworks/ignite/tree/master/pkg/apis/ignite/v1alpha2/types.go?s=8227:8323#L223) + +``` go +type VolumeMount struct { + Name string `json:"name"` + MountPath string `json:"mountPath"` +} +``` + +VolumeMount defines the mount point for a named volume inside a VM + ----- Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) diff --git a/docs/cli/ignite/ignite_create.md b/docs/cli/ignite/ignite_create.md index 5a8b073b8..ad589a252 100644 --- a/docs/cli/ignite/ignite_create.md +++ b/docs/cli/ignite/ignite_create.md @@ -46,6 +46,7 @@ ignite create [flags] -p, --ports strings Map host ports to VM ports -s, --size size VM filesystem size, for example 5GB or 2048MB (default 4.0 GB) --ssh[=] Enable SSH for the VM. If is given, it will be imported as the public key. If just '--ssh' is specified, a new keypair will be generated. (default is unset, which disables SSH access to the VM) + -v, --volumes volume Expose block devices from the host inside the VM ``` ### Options inherited from parent commands diff --git a/docs/cli/ignite/ignite_run.md b/docs/cli/ignite/ignite_run.md index 881231c1b..f88b32f5c 100644 --- a/docs/cli/ignite/ignite_run.md +++ b/docs/cli/ignite/ignite_run.md @@ -41,6 +41,7 @@ ignite run [flags] -p, --ports strings Map host ports to VM ports -s, --size size VM filesystem size, for example 5GB or 2048MB (default 4.0 GB) --ssh[=] Enable SSH for the VM. If is given, it will be imported as the public key. If just '--ssh' is specified, a new keypair will be generated. (default is unset, which disables SSH access to the VM) + -v, --volumes volume Expose block devices from the host inside the VM ``` ### Options inherited from parent commands diff --git a/docs/cli/ignite/ignite_vm_create.md b/docs/cli/ignite/ignite_vm_create.md index df312aa1b..a3709470c 100644 --- a/docs/cli/ignite/ignite_vm_create.md +++ b/docs/cli/ignite/ignite_vm_create.md @@ -46,6 +46,7 @@ ignite vm create [flags] -p, --ports strings Map host ports to VM ports -s, --size size VM filesystem size, for example 5GB or 2048MB (default 4.0 GB) --ssh[=] Enable SSH for the VM. If is given, it will be imported as the public key. If just '--ssh' is specified, a new keypair will be generated. (default is unset, which disables SSH access to the VM) + -v, --volumes volume Expose block devices from the host inside the VM ``` ### Options inherited from parent commands diff --git a/docs/cli/ignite/ignite_vm_run.md b/docs/cli/ignite/ignite_vm_run.md index abdba0fde..c29119541 100644 --- a/docs/cli/ignite/ignite_vm_run.md +++ b/docs/cli/ignite/ignite_vm_run.md @@ -41,6 +41,7 @@ ignite vm run [flags] -p, --ports strings Map host ports to VM ports -s, --size size VM filesystem size, for example 5GB or 2048MB (default 4.0 GB) --ssh[=] Enable SSH for the VM. If is given, it will be imported as the public key. If just '--ssh' is specified, a new keypair will be generated. (default is unset, which disables SSH access to the VM) + -v, --volumes volume Expose block devices from the host inside the VM ``` ### Options inherited from parent commands diff --git a/pkg/apis/ignite/types.go b/pkg/apis/ignite/types.go index e8603bde6..05f3c1003 100644 --- a/pkg/apis/ignite/types.go +++ b/pkg/apis/ignite/types.go @@ -175,7 +175,7 @@ type VMSpec struct { Memory meta.Size `json:"memory"` DiskSize meta.Size `json:"diskSize"` Network VMNetworkSpec `json:"network"` - + Storage VMStorageSpec `json:"storage,omitempty"` // This will be done at either "ignite start" or "ignite create" time // TODO: We might revisit this later CopyFiles []FileMapping `json:"copyFiles,omitempty"` @@ -202,6 +202,29 @@ type VMNetworkSpec struct { Ports meta.PortMappings `json:"ports,omitempty"` } +// VMStorageSpec defines the VM's Volumes and VolumeMounts +type VMStorageSpec struct { + Volumes []Volume `json:"volumes,omitempty"` + VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` +} + +// Volume defines named storage volume +type Volume struct { + Name string `json:"name"` + BlockDevice *BlockDeviceVolume `json:"blockDevice,omitempty"` +} + +// BlockDeviceVolume defines a block device on the host +type BlockDeviceVolume struct { + Path string `json:"path"` +} + +// VolumeMount defines the mount point for a named volume inside a VM +type VolumeMount struct { + Name string `json:"name"` + MountPath string `json:"mountPath"` +} + // FileMapping defines mappings between files on the host and VM type FileMapping struct { HostPath string `json:"hostPath"` diff --git a/pkg/apis/ignite/v1alpha1/conversion.go b/pkg/apis/ignite/v1alpha1/conversion.go new file mode 100644 index 000000000..59f0191e5 --- /dev/null +++ b/pkg/apis/ignite/v1alpha1/conversion.go @@ -0,0 +1,18 @@ +package v1alpha1 + +import ( + "github.com/weaveworks/ignite/pkg/apis/ignite" + "k8s.io/apimachinery/pkg/conversion" +) + +// Convert_ignite_VMSpec_To_v1alpha1_VMSpec calls the autogenerated conversion function along with custom conversion logic +func Convert_ignite_VMSpec_To_v1alpha1_VMSpec(in *ignite.VMSpec, out *VMSpec, s conversion.Scope) error { + // VMSpecStorage are not supported by v1alpha1, so just ignore the warning by calling this manually + return autoConvert_ignite_VMSpec_To_v1alpha1_VMSpec(in, out, s) +} + +// Convert_ignite_VMSpec_To_v1alpha1_VMSpec calls the autogenerated conversion function along with custom conversion logic +func Convert_v1alpha1_VMSpec_To_ignite_VMSpec(in *VMSpec, out *ignite.VMSpec, s conversion.Scope) error { + // VMSpecStorage is not supported by v1alpha1, so just ignore the warning by calling this manually + return autoConvert_v1alpha1_VMSpec_To_ignite_VMSpec(in, out, s) +} diff --git a/pkg/apis/ignite/v1alpha1/zz_generated.conversion.go b/pkg/apis/ignite/v1alpha1/zz_generated.conversion.go index 90d5ba8cf..39fdb7f19 100644 --- a/pkg/apis/ignite/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/ignite/v1alpha1/zz_generated.conversion.go @@ -220,6 +220,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*ignite.VMSpec)(nil), (*VMSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_ignite_VMSpec_To_v1alpha1_VMSpec(a.(*ignite.VMSpec), b.(*VMSpec), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*VMSpec)(nil), (*ignite.VMSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_VMSpec_To_ignite_VMSpec(a.(*VMSpec), b.(*ignite.VMSpec), scope) + }); err != nil { + return err + } return nil } @@ -709,11 +719,6 @@ func autoConvert_v1alpha1_VMSpec_To_ignite_VMSpec(in *VMSpec, out *ignite.VMSpec return nil } -// Convert_v1alpha1_VMSpec_To_ignite_VMSpec is an autogenerated conversion function. -func Convert_v1alpha1_VMSpec_To_ignite_VMSpec(in *VMSpec, out *ignite.VMSpec, s conversion.Scope) error { - return autoConvert_v1alpha1_VMSpec_To_ignite_VMSpec(in, out, s) -} - func autoConvert_ignite_VMSpec_To_v1alpha1_VMSpec(in *ignite.VMSpec, out *VMSpec, s conversion.Scope) error { if err := Convert_ignite_VMImageSpec_To_v1alpha1_VMImageSpec(&in.Image, &out.Image, s); err != nil { return err @@ -727,16 +732,12 @@ func autoConvert_ignite_VMSpec_To_v1alpha1_VMSpec(in *ignite.VMSpec, out *VMSpec if err := Convert_ignite_VMNetworkSpec_To_v1alpha1_VMNetworkSpec(&in.Network, &out.Network, s); err != nil { return err } + // WARNING: in.Storage requires manual conversion: does not exist in peer-type out.CopyFiles = *(*[]FileMapping)(unsafe.Pointer(&in.CopyFiles)) out.SSH = (*SSH)(unsafe.Pointer(in.SSH)) return nil } -// Convert_ignite_VMSpec_To_v1alpha1_VMSpec is an autogenerated conversion function. -func Convert_ignite_VMSpec_To_v1alpha1_VMSpec(in *ignite.VMSpec, out *VMSpec, s conversion.Scope) error { - return autoConvert_ignite_VMSpec_To_v1alpha1_VMSpec(in, out, s) -} - func autoConvert_v1alpha1_VMStatus_To_ignite_VMStatus(in *VMStatus, out *ignite.VMStatus, s conversion.Scope) error { out.State = ignite.VMState(in.State) out.IPAddresses = *(*metav1alpha1.IPAddresses)(unsafe.Pointer(&in.IPAddresses)) diff --git a/pkg/apis/ignite/v1alpha2/types.go b/pkg/apis/ignite/v1alpha2/types.go index d20bd7e9e..73864b604 100644 --- a/pkg/apis/ignite/v1alpha2/types.go +++ b/pkg/apis/ignite/v1alpha2/types.go @@ -175,7 +175,7 @@ type VMSpec struct { Memory meta.Size `json:"memory"` DiskSize meta.Size `json:"diskSize"` Network VMNetworkSpec `json:"network"` - + Storage VMStorageSpec `json:"storage,omitempty"` // This will be done at either "ignite start" or "ignite create" time // TODO: We might revisit this later CopyFiles []FileMapping `json:"copyFiles,omitempty"` @@ -202,6 +202,29 @@ type VMNetworkSpec struct { Ports meta.PortMappings `json:"ports,omitempty"` } +// VMStorageSpec defines the VM's Volumes and VolumeMounts +type VMStorageSpec struct { + Volumes []Volume `json:"volumes,omitempty"` + VolumeMounts []VolumeMount `json:"volumeMounts,omitempty"` +} + +// Volume defines named storage volume +type Volume struct { + Name string `json:"name"` + BlockDevice *BlockDeviceVolume `json:"blockDevice,omitempty"` +} + +// BlockDeviceVolume defines a block device on the host +type BlockDeviceVolume struct { + Path string `json:"path"` +} + +// VolumeMount defines the mount point for a named volume inside a VM +type VolumeMount struct { + Name string `json:"name"` + MountPath string `json:"mountPath"` +} + // FileMapping defines mappings between files on the host and VM type FileMapping struct { HostPath string `json:"hostPath"` diff --git a/pkg/apis/ignite/v1alpha2/zz_generated.conversion.go b/pkg/apis/ignite/v1alpha2/zz_generated.conversion.go index 6f84585ec..b5284e011 100644 --- a/pkg/apis/ignite/v1alpha2/zz_generated.conversion.go +++ b/pkg/apis/ignite/v1alpha2/zz_generated.conversion.go @@ -20,6 +20,16 @@ func init() { // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*BlockDeviceVolume)(nil), (*ignite.BlockDeviceVolume)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_BlockDeviceVolume_To_ignite_BlockDeviceVolume(a.(*BlockDeviceVolume), b.(*ignite.BlockDeviceVolume), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*ignite.BlockDeviceVolume)(nil), (*BlockDeviceVolume)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_ignite_BlockDeviceVolume_To_v1alpha2_BlockDeviceVolume(a.(*ignite.BlockDeviceVolume), b.(*BlockDeviceVolume), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*FileMapping)(nil), (*ignite.FileMapping)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha2_FileMapping_To_ignite_FileMapping(a.(*FileMapping), b.(*ignite.FileMapping), scope) }); err != nil { @@ -220,9 +230,59 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*VMStorageSpec)(nil), (*ignite.VMStorageSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_VMStorageSpec_To_ignite_VMStorageSpec(a.(*VMStorageSpec), b.(*ignite.VMStorageSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*ignite.VMStorageSpec)(nil), (*VMStorageSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_ignite_VMStorageSpec_To_v1alpha2_VMStorageSpec(a.(*ignite.VMStorageSpec), b.(*VMStorageSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*Volume)(nil), (*ignite.Volume)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_Volume_To_ignite_Volume(a.(*Volume), b.(*ignite.Volume), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*ignite.Volume)(nil), (*Volume)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_ignite_Volume_To_v1alpha2_Volume(a.(*ignite.Volume), b.(*Volume), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*VolumeMount)(nil), (*ignite.VolumeMount)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_VolumeMount_To_ignite_VolumeMount(a.(*VolumeMount), b.(*ignite.VolumeMount), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*ignite.VolumeMount)(nil), (*VolumeMount)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_ignite_VolumeMount_To_v1alpha2_VolumeMount(a.(*ignite.VolumeMount), b.(*VolumeMount), scope) + }); err != nil { + return err + } return nil } +func autoConvert_v1alpha2_BlockDeviceVolume_To_ignite_BlockDeviceVolume(in *BlockDeviceVolume, out *ignite.BlockDeviceVolume, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_v1alpha2_BlockDeviceVolume_To_ignite_BlockDeviceVolume is an autogenerated conversion function. +func Convert_v1alpha2_BlockDeviceVolume_To_ignite_BlockDeviceVolume(in *BlockDeviceVolume, out *ignite.BlockDeviceVolume, s conversion.Scope) error { + return autoConvert_v1alpha2_BlockDeviceVolume_To_ignite_BlockDeviceVolume(in, out, s) +} + +func autoConvert_ignite_BlockDeviceVolume_To_v1alpha2_BlockDeviceVolume(in *ignite.BlockDeviceVolume, out *BlockDeviceVolume, s conversion.Scope) error { + out.Path = in.Path + return nil +} + +// Convert_ignite_BlockDeviceVolume_To_v1alpha2_BlockDeviceVolume is an autogenerated conversion function. +func Convert_ignite_BlockDeviceVolume_To_v1alpha2_BlockDeviceVolume(in *ignite.BlockDeviceVolume, out *BlockDeviceVolume, s conversion.Scope) error { + return autoConvert_ignite_BlockDeviceVolume_To_v1alpha2_BlockDeviceVolume(in, out, s) +} + func autoConvert_v1alpha2_FileMapping_To_ignite_FileMapping(in *FileMapping, out *ignite.FileMapping, s conversion.Scope) error { out.HostPath = in.HostPath out.VMPath = in.VMPath @@ -704,6 +764,9 @@ func autoConvert_v1alpha2_VMSpec_To_ignite_VMSpec(in *VMSpec, out *ignite.VMSpec if err := Convert_v1alpha2_VMNetworkSpec_To_ignite_VMNetworkSpec(&in.Network, &out.Network, s); err != nil { return err } + if err := Convert_v1alpha2_VMStorageSpec_To_ignite_VMStorageSpec(&in.Storage, &out.Storage, s); err != nil { + return err + } out.CopyFiles = *(*[]ignite.FileMapping)(unsafe.Pointer(&in.CopyFiles)) out.SSH = (*ignite.SSH)(unsafe.Pointer(in.SSH)) return nil @@ -727,6 +790,9 @@ func autoConvert_ignite_VMSpec_To_v1alpha2_VMSpec(in *ignite.VMSpec, out *VMSpec if err := Convert_ignite_VMNetworkSpec_To_v1alpha2_VMNetworkSpec(&in.Network, &out.Network, s); err != nil { return err } + if err := Convert_ignite_VMStorageSpec_To_v1alpha2_VMStorageSpec(&in.Storage, &out.Storage, s); err != nil { + return err + } out.CopyFiles = *(*[]FileMapping)(unsafe.Pointer(&in.CopyFiles)) out.SSH = (*SSH)(unsafe.Pointer(in.SSH)) return nil @@ -770,3 +836,69 @@ func autoConvert_ignite_VMStatus_To_v1alpha2_VMStatus(in *ignite.VMStatus, out * func Convert_ignite_VMStatus_To_v1alpha2_VMStatus(in *ignite.VMStatus, out *VMStatus, s conversion.Scope) error { return autoConvert_ignite_VMStatus_To_v1alpha2_VMStatus(in, out, s) } + +func autoConvert_v1alpha2_VMStorageSpec_To_ignite_VMStorageSpec(in *VMStorageSpec, out *ignite.VMStorageSpec, s conversion.Scope) error { + out.Volumes = *(*[]ignite.Volume)(unsafe.Pointer(&in.Volumes)) + out.VolumeMounts = *(*[]ignite.VolumeMount)(unsafe.Pointer(&in.VolumeMounts)) + return nil +} + +// Convert_v1alpha2_VMStorageSpec_To_ignite_VMStorageSpec is an autogenerated conversion function. +func Convert_v1alpha2_VMStorageSpec_To_ignite_VMStorageSpec(in *VMStorageSpec, out *ignite.VMStorageSpec, s conversion.Scope) error { + return autoConvert_v1alpha2_VMStorageSpec_To_ignite_VMStorageSpec(in, out, s) +} + +func autoConvert_ignite_VMStorageSpec_To_v1alpha2_VMStorageSpec(in *ignite.VMStorageSpec, out *VMStorageSpec, s conversion.Scope) error { + out.Volumes = *(*[]Volume)(unsafe.Pointer(&in.Volumes)) + out.VolumeMounts = *(*[]VolumeMount)(unsafe.Pointer(&in.VolumeMounts)) + return nil +} + +// Convert_ignite_VMStorageSpec_To_v1alpha2_VMStorageSpec is an autogenerated conversion function. +func Convert_ignite_VMStorageSpec_To_v1alpha2_VMStorageSpec(in *ignite.VMStorageSpec, out *VMStorageSpec, s conversion.Scope) error { + return autoConvert_ignite_VMStorageSpec_To_v1alpha2_VMStorageSpec(in, out, s) +} + +func autoConvert_v1alpha2_Volume_To_ignite_Volume(in *Volume, out *ignite.Volume, s conversion.Scope) error { + out.Name = in.Name + out.BlockDevice = (*ignite.BlockDeviceVolume)(unsafe.Pointer(in.BlockDevice)) + return nil +} + +// Convert_v1alpha2_Volume_To_ignite_Volume is an autogenerated conversion function. +func Convert_v1alpha2_Volume_To_ignite_Volume(in *Volume, out *ignite.Volume, s conversion.Scope) error { + return autoConvert_v1alpha2_Volume_To_ignite_Volume(in, out, s) +} + +func autoConvert_ignite_Volume_To_v1alpha2_Volume(in *ignite.Volume, out *Volume, s conversion.Scope) error { + out.Name = in.Name + out.BlockDevice = (*BlockDeviceVolume)(unsafe.Pointer(in.BlockDevice)) + return nil +} + +// Convert_ignite_Volume_To_v1alpha2_Volume is an autogenerated conversion function. +func Convert_ignite_Volume_To_v1alpha2_Volume(in *ignite.Volume, out *Volume, s conversion.Scope) error { + return autoConvert_ignite_Volume_To_v1alpha2_Volume(in, out, s) +} + +func autoConvert_v1alpha2_VolumeMount_To_ignite_VolumeMount(in *VolumeMount, out *ignite.VolumeMount, s conversion.Scope) error { + out.Name = in.Name + out.MountPath = in.MountPath + return nil +} + +// Convert_v1alpha2_VolumeMount_To_ignite_VolumeMount is an autogenerated conversion function. +func Convert_v1alpha2_VolumeMount_To_ignite_VolumeMount(in *VolumeMount, out *ignite.VolumeMount, s conversion.Scope) error { + return autoConvert_v1alpha2_VolumeMount_To_ignite_VolumeMount(in, out, s) +} + +func autoConvert_ignite_VolumeMount_To_v1alpha2_VolumeMount(in *ignite.VolumeMount, out *VolumeMount, s conversion.Scope) error { + out.Name = in.Name + out.MountPath = in.MountPath + return nil +} + +// Convert_ignite_VolumeMount_To_v1alpha2_VolumeMount is an autogenerated conversion function. +func Convert_ignite_VolumeMount_To_v1alpha2_VolumeMount(in *ignite.VolumeMount, out *VolumeMount, s conversion.Scope) error { + return autoConvert_ignite_VolumeMount_To_v1alpha2_VolumeMount(in, out, s) +} diff --git a/pkg/apis/ignite/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/ignite/v1alpha2/zz_generated.deepcopy.go index 3560119f1..a782e28fd 100644 --- a/pkg/apis/ignite/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/ignite/v1alpha2/zz_generated.deepcopy.go @@ -11,6 +11,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BlockDeviceVolume) DeepCopyInto(out *BlockDeviceVolume) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BlockDeviceVolume. +func (in *BlockDeviceVolume) DeepCopy() *BlockDeviceVolume { + if in == nil { + return nil + } + out := new(BlockDeviceVolume) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FileMapping) DeepCopyInto(out *FileMapping) { *out = *in @@ -387,6 +403,7 @@ func (in *VMSpec) DeepCopyInto(out *VMSpec) { out.Memory = in.Memory out.DiskSize = in.DiskSize in.Network.DeepCopyInto(&out.Network) + in.Storage.DeepCopyInto(&out.Storage) if in.CopyFiles != nil { in, out := &in.CopyFiles, &out.CopyFiles *out = make([]FileMapping, len(*in)) @@ -438,3 +455,68 @@ func (in *VMStatus) DeepCopy() *VMStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VMStorageSpec) DeepCopyInto(out *VMStorageSpec) { + *out = *in + if in.Volumes != nil { + in, out := &in.Volumes, &out.Volumes + *out = make([]Volume, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.VolumeMounts != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]VolumeMount, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VMStorageSpec. +func (in *VMStorageSpec) DeepCopy() *VMStorageSpec { + if in == nil { + return nil + } + out := new(VMStorageSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Volume) DeepCopyInto(out *Volume) { + *out = *in + if in.BlockDevice != nil { + in, out := &in.BlockDevice, &out.BlockDevice + *out = new(BlockDeviceVolume) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Volume. +func (in *Volume) DeepCopy() *Volume { + if in == nil { + return nil + } + out := new(Volume) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeMount) DeepCopyInto(out *VolumeMount) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeMount. +func (in *VolumeMount) DeepCopy() *VolumeMount { + if in == nil { + return nil + } + out := new(VolumeMount) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/ignite/validation/storage.go b/pkg/apis/ignite/validation/storage.go new file mode 100644 index 000000000..e0c311cc0 --- /dev/null +++ b/pkg/apis/ignite/validation/storage.go @@ -0,0 +1,90 @@ +package validation + +import ( + "fmt" + + api "github.com/weaveworks/ignite/pkg/apis/ignite" + "github.com/weaveworks/ignite/pkg/util" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ValidateBlockDeviceVolume validates if the BlockDeviceVolume is valid +func ValidateBlockDeviceVolume(b *api.BlockDeviceVolume, fldPath *field.Path, paths map[string]struct{}) (allErrs field.ErrorList) { + pathFldPath := fldPath.Child("path") + allErrs = append(allErrs, ValidateAbsolutePath(b.Path, pathFldPath)...) + + // Validate that the block device path points to a device file + if err := util.IsDeviceFile(b.Path); err != nil { + allErrs = append(allErrs, field.Invalid(pathFldPath, b.Path, err.Error())) + } + + // Validate path uniqueness + if _, ok := paths[b.Path]; ok { + allErrs = append(allErrs, field.Invalid(pathFldPath, b.Path, fmt.Sprintf("blockDevice path must be unique"))) + } else { + paths[b.Path] = struct{}{} + } + + return +} + +// ValidateVMStorage validates if the VMStorageSpec is valid +func ValidateVMStorage(s *api.VMStorageSpec, fldPath *field.Path) (allErrs field.ErrorList) { + // names keeps track of volume names and if they have a respective volumeMount + names := make(map[string]bool, util.MaxInt(len(s.VolumeMounts), len(s.Volumes))) + // blockDevPaths keeps track of registered block device paths + blockDevPaths := make(map[string]struct{}, len(s.Volumes)) + // mountPaths keeps track of registered volumeMount paths + mountPaths := make(map[string]struct{}, len(s.VolumeMounts)) + + // volume validation + for i, volume := range s.Volumes { + volumeFldPath := fldPath.Child(fmt.Sprintf("[%d]", i)) + allErrs = append(allErrs, ValidateNonemptyName(volume.Name, volumeFldPath.Child("name"))...) + + // For now require and validate the BlockDevice entry + blockDevFldPath := volumeFldPath.Child("blockDevice") + if volume.BlockDevice == nil { + allErrs = append(allErrs, field.Invalid(blockDevFldPath, nil, fmt.Sprint("blockDevice must be non-nil"))) + } else { + allErrs = append(allErrs, ValidateBlockDeviceVolume(volume.BlockDevice, blockDevFldPath, blockDevPaths)...) + } + + // Validate volume name uniqueness + if _, ok := names[volume.Name]; ok { + allErrs = append(allErrs, field.Invalid(volumeFldPath.Child("name"), volume.Name, "volume name must be unique")) + } else { + names[volume.Name] = false + } + } + + // volumeMount validation + for i, mount := range s.VolumeMounts { + mountFldPath := fldPath.Child(fmt.Sprintf("[%d]", i)) + mountNameFldPath := mountFldPath.Child("name") + mountPathFldPath := mountFldPath.Child("mountPath") + + allErrs = append(allErrs, ValidateNonemptyName(mount.Name, mountNameFldPath)...) + allErrs = append(allErrs, ValidateAbsolutePath(mount.MountPath, mountPathFldPath)...) + + // Validate volumeMount name uniqueness and correlation to volumes + if matched, ok := names[mount.Name]; ok { + if matched { + allErrs = append(allErrs, field.Invalid(mountNameFldPath, mount.Name, "volumeMount name must be unique")) + } else { + names[mount.Name] = true + } + } else { + allErrs = append(allErrs, field.Invalid(mountNameFldPath, mount.Name, "volumeMount name must match a volume name")) + } + + // Validate volumeMount path uniqueness + if _, ok := mountPaths[mount.MountPath]; ok { + allErrs = append(allErrs, field.Invalid(mountPathFldPath, mount.MountPath, fmt.Sprintf("volumeMount path must be unique"))) + } else { + mountPaths[mount.MountPath] = struct{}{} + } + } + + return +} diff --git a/pkg/apis/ignite/validation/validation.go b/pkg/apis/ignite/validation/validation.go index c0fa08218..619be5e97 100644 --- a/pkg/apis/ignite/validation/validation.go +++ b/pkg/apis/ignite/validation/validation.go @@ -6,6 +6,7 @@ import ( api "github.com/weaveworks/ignite/pkg/apis/ignite" meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1" + "github.com/weaveworks/ignite/pkg/util" "k8s.io/apimachinery/pkg/util/validation/field" ) @@ -15,6 +16,7 @@ func ValidateVM(obj *api.VM) (allErrs field.ErrorList) { allErrs = append(allErrs, ValidateOCIImageClaim(&obj.Spec.Image.OCIClaim, field.NewPath(".spec.image.ociClaim"))...) allErrs = append(allErrs, ValidateOCIImageClaim(&obj.Spec.Kernel.OCIClaim, field.NewPath(".spec.kernel.ociClaim"))...) allErrs = append(allErrs, ValidateFileMappings(&obj.Spec.CopyFiles, field.NewPath(".spec.copyFiles"))...) + allErrs = append(allErrs, ValidateVMStorage(&obj.Spec.Storage, field.NewPath(".spec.storage"))...) allErrs = append(allErrs, ValidateVMState(obj.Status.State, field.NewPath(".status.state"))...) // TODO: Add vCPU, memory, disk max and min sizes return @@ -46,10 +48,19 @@ func ValidateFileMappings(mappings *[]api.FileMapping, fldPath *field.Path) (all return } +// ValidateNonemptyName validated that the given name is nonempty +func ValidateNonemptyName(name string, fldPath *field.Path) (allErrs field.ErrorList) { + if util.IsEmptyString(name) { + allErrs = append(allErrs, field.Invalid(fldPath, name, "name must be non-empty")) + } + + return +} + // ValidateAbsolutePath validates if a path is absolute func ValidateAbsolutePath(pathStr string, fldPath *field.Path) (allErrs field.ErrorList) { if !path.IsAbs(pathStr) { - allErrs = append(allErrs, field.Invalid(fldPath, pathStr, fmt.Sprintf("path must be absolute %q", pathStr))) + allErrs = append(allErrs, field.Invalid(fldPath, pathStr, "path must be absolute")) } return } diff --git a/pkg/apis/ignite/zz_generated.deepcopy.go b/pkg/apis/ignite/zz_generated.deepcopy.go index a31306ce2..0398ae49a 100644 --- a/pkg/apis/ignite/zz_generated.deepcopy.go +++ b/pkg/apis/ignite/zz_generated.deepcopy.go @@ -11,6 +11,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BlockDeviceVolume) DeepCopyInto(out *BlockDeviceVolume) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BlockDeviceVolume. +func (in *BlockDeviceVolume) DeepCopy() *BlockDeviceVolume { + if in == nil { + return nil + } + out := new(BlockDeviceVolume) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FileMapping) DeepCopyInto(out *FileMapping) { *out = *in @@ -387,6 +403,7 @@ func (in *VMSpec) DeepCopyInto(out *VMSpec) { out.Memory = in.Memory out.DiskSize = in.DiskSize in.Network.DeepCopyInto(&out.Network) + in.Storage.DeepCopyInto(&out.Storage) if in.CopyFiles != nil { in, out := &in.CopyFiles, &out.CopyFiles *out = make([]FileMapping, len(*in)) @@ -438,3 +455,68 @@ func (in *VMStatus) DeepCopy() *VMStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VMStorageSpec) DeepCopyInto(out *VMStorageSpec) { + *out = *in + if in.Volumes != nil { + in, out := &in.Volumes, &out.Volumes + *out = make([]Volume, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.VolumeMounts != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]VolumeMount, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VMStorageSpec. +func (in *VMStorageSpec) DeepCopy() *VMStorageSpec { + if in == nil { + return nil + } + out := new(VMStorageSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Volume) DeepCopyInto(out *Volume) { + *out = *in + if in.BlockDevice != nil { + in, out := &in.BlockDevice, &out.BlockDevice + *out = new(BlockDeviceVolume) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Volume. +func (in *Volume) DeepCopy() *Volume { + if in == nil { + return nil + } + out := new(Volume) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VolumeMount) DeepCopyInto(out *VolumeMount) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeMount. +func (in *VolumeMount) DeepCopy() *VolumeMount { + if in == nil { + return nil + } + out := new(VolumeMount) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/constants/vm.go b/pkg/constants/vm.go index e4da8b9fd..bb01015a3 100644 --- a/pkg/constants/vm.go +++ b/pkg/constants/vm.go @@ -27,4 +27,7 @@ const ( // Where the vmlinux kernel is located inside of the container IGNITE_SPAWN_VMLINUX_FILE_PATH = "/vmlinux" + + // Subdirectory for volumes to be forwarded into the VM + IGNITE_SPAWN_VOLUME_DIR = "/volumes" ) diff --git a/pkg/container/firecracker.go b/pkg/container/firecracker.go index 5b1e5c77c..19bd3e999 100644 --- a/pkg/container/firecracker.go +++ b/pkg/container/firecracker.go @@ -6,6 +6,7 @@ import ( "os" "os/signal" "path" + "strconv" "syscall" "time" @@ -15,6 +16,7 @@ import ( api "github.com/weaveworks/ignite/pkg/apis/ignite" "github.com/weaveworks/ignite/pkg/constants" "github.com/weaveworks/ignite/pkg/logs" + "github.com/weaveworks/ignite/pkg/util" ) // ExecuteFirecracker executes the firecracker process using the Go SDK @@ -59,9 +61,9 @@ func ExecuteFirecracker(vm *api.VM, dhcpIfaces []DHCPInterface) error { KernelArgs: cmdLine, Drives: []models.Drive{{ DriveID: firecracker.String("1"), - PathOnHost: &drivePath, - IsRootDevice: firecracker.Bool(true), IsReadOnly: firecracker.Bool(false), + IsRootDevice: firecracker.Bool(true), + PathOnHost: &drivePath, }}, NetworkInterfaces: networkInterfaces, MachineCfg: models.MachineConfiguration{ @@ -83,6 +85,22 @@ func ExecuteFirecracker(vm *api.VM, dhcpIfaces []DHCPInterface) error { MetricsFifo: metricsSocketPath, } + // Add the volumes to the VM + for i, volume := range vm.Spec.Storage.Volumes { + volumePath := path.Join(constants.IGNITE_SPAWN_VOLUME_DIR, volume.Name) + if !util.FileExists(volumePath) { + log.Warnf("Skipping nonexistent volume: %q", volume.Name) + continue // Skip all nonexistent volumes + } + + cfg.Drives = append(cfg.Drives, models.Drive{ + DriveID: firecracker.String(strconv.Itoa(i + 2)), + IsReadOnly: firecracker.Bool(false), // TODO: Support read-only volumes + IsRootDevice: firecracker.Bool(false), + PathOnHost: &volumePath, + }) + } + // Remove these FIFOs for now defer os.Remove(logSocketPath) defer os.Remove(metricsSocketPath) diff --git a/pkg/dmlegacy/fstab.go b/pkg/dmlegacy/fstab.go new file mode 100644 index 000000000..4ff444fac --- /dev/null +++ b/pkg/dmlegacy/fstab.go @@ -0,0 +1,123 @@ +package dmlegacy + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "strings" + "text/tabwriter" + + api "github.com/weaveworks/ignite/pkg/apis/ignite" + "github.com/weaveworks/ignite/pkg/util" +) + +const ( + mountOptions = "rw,relatime" + uuidPath = "/dev/disk/by-uuid" +) + +type fstabEntry struct { + uuid string + mountPoint string +} + +var _ fmt.Stringer = &fstabEntry{} + +func (f *fstabEntry) isValid() bool { + // An entry is valid if both the UUID and mount point are set + return len(f.uuid) > 0 && len(f.mountPoint) > 0 +} + +func (f *fstabEntry) String() string { + return strings.Join([]string{ + fmt.Sprintf("UUID=%s", f.uuid), // Mount by UUID + f.mountPoint, // The mount point for the volume + "auto", // Discover the filesystem automatically + mountOptions, // Use the mount options defined above + "0", // Don't dump the filesystem + "2", // fsck may check this filesystem on reboot + }, "\t") +} + +func populateFstab(vm *api.VM, mountPoint string) error { + fstab, err := os.Create(path.Join(mountPoint, "/etc/fstab")) + if err != nil { + return err + } + defer fstab.Close() + + writer := new(tabwriter.Writer) + writer.Init(fstab, 0, 8, 1, '\t', 0) + entries := make(map[string]*fstabEntry, util.MaxInt(len(vm.Spec.Storage.Volumes), len(vm.Spec.Storage.VolumeMounts))) + + // Discover all volumes + for _, volume := range vm.Spec.Storage.Volumes { + if volume.BlockDevice == nil { + continue // Skip all non block device volumes for now + } + + // Retrieve the UUID for the block device + uuid, err := getUUID(volume.BlockDevice.Path) + if err != nil { + return err + } + + // Create an entry for the volume + entries[volume.Name] = &fstabEntry{uuid: uuid} + } + + // Discover all volume mounts + for _, volumeMount := range vm.Spec.Storage.VolumeMounts { + // Lookup the entry based on the volume mount's name + if entry, ok := entries[volumeMount.Name]; ok { + // Add the mount path to the entry if it exists + entry.mountPoint = volumeMount.MountPath + } + } + + for _, entry := range entries { + if entry.isValid() { + // Write the entry to /etc/fstab + if _, err := fmt.Fprint(writer, entry, "\n"); err != nil { + return err + } + } else { + // This should have been caught in validation + return fmt.Errorf("invalid fstab entry: %q -> %q", entry.uuid, entry.mountPoint) + } + } + + return writer.Flush() +} + +func getUUID(devPath string) (uuid string, err error) { + var files []os.FileInfo + if files, err = ioutil.ReadDir(uuidPath); err != nil { + return + } + + for _, fi := range files { + if fi.Mode()&os.ModeSymlink == 0 { + continue // Skip all non-symlinks + } + + // Resolve the symbolic link + var link string + if link, err = os.Readlink(path.Join(uuidPath, fi.Name())); err != nil { + return + } + + // Make the link target absolute and compare with the given path + if path.Join(uuidPath, link) == devPath { + uuid = fi.Name() // The UUID is the filename of the symbolic link + break + } + } + + if len(uuid) == 0 { + err = fmt.Errorf("no UUID found for device %q, verify it has a filesystem", devPath) + } + + return +} diff --git a/pkg/dmlegacy/vm.go b/pkg/dmlegacy/vm.go index c1150e5b1..d483723b3 100644 --- a/pkg/dmlegacy/vm.go +++ b/pkg/dmlegacy/vm.go @@ -8,7 +8,6 @@ import ( "os" "path" "path/filepath" - "time" log "github.com/sirupsen/logrus" api "github.com/weaveworks/ignite/pkg/apis/ignite" @@ -138,18 +137,18 @@ func copyToOverlay(vm *api.VM) error { ip = vm.Status.IPAddresses[0] } + // Write /etc/hosts for the VM if err := writeEtcHosts(vm, mp.Path, vm.GetUID().String(), ip); err != nil { return err } - // Set overlay root permissions - if err := os.Chmod(mp.Path, constants.DATA_DIR_PERM); err != nil { + // Populate /etc/fstab with the VM's volume mounts + if err := populateFstab(vm, mp.Path); err != nil { return err } - // TODO: This code seems to be flaky and not always copy over the files? - time.Sleep(500 * time.Millisecond) - return nil + // Set overlay root permissions + return os.Chmod(mp.Path, constants.DATA_DIR_PERM) } func copyKernelToOverlay(vm *api.VM, mountPoint string) error { diff --git a/pkg/openapi/openapi_generated.go b/pkg/openapi/openapi_generated.go index b16647ce5..ab686f44c 100644 --- a/pkg/openapi/openapi_generated.go +++ b/pkg/openapi/openapi_generated.go @@ -13,53 +13,57 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.FileMapping": schema_pkg_apis_ignite_v1alpha1_FileMapping(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.Image": schema_pkg_apis_ignite_v1alpha1_Image(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.ImageSpec": schema_pkg_apis_ignite_v1alpha1_ImageSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.ImageStatus": schema_pkg_apis_ignite_v1alpha1_ImageStatus(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.Kernel": schema_pkg_apis_ignite_v1alpha1_Kernel(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.KernelSpec": schema_pkg_apis_ignite_v1alpha1_KernelSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.KernelStatus": schema_pkg_apis_ignite_v1alpha1_KernelStatus(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.OCIImageClaim": schema_pkg_apis_ignite_v1alpha1_OCIImageClaim(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.OCIImageSource": schema_pkg_apis_ignite_v1alpha1_OCIImageSource(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.Pool": schema_pkg_apis_ignite_v1alpha1_Pool(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.PoolDevice": schema_pkg_apis_ignite_v1alpha1_PoolDevice(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.PoolSpec": schema_pkg_apis_ignite_v1alpha1_PoolSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.PoolStatus": schema_pkg_apis_ignite_v1alpha1_PoolStatus(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.SSH": schema_pkg_apis_ignite_v1alpha1_SSH(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VM": schema_pkg_apis_ignite_v1alpha1_VM(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VMImageSpec": schema_pkg_apis_ignite_v1alpha1_VMImageSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VMKernelSpec": schema_pkg_apis_ignite_v1alpha1_VMKernelSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VMNetworkSpec": schema_pkg_apis_ignite_v1alpha1_VMNetworkSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VMSpec": schema_pkg_apis_ignite_v1alpha1_VMSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VMStatus": schema_pkg_apis_ignite_v1alpha1_VMStatus(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.FileMapping": schema_pkg_apis_ignite_v1alpha2_FileMapping(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.Image": schema_pkg_apis_ignite_v1alpha2_Image(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.ImageSpec": schema_pkg_apis_ignite_v1alpha2_ImageSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.ImageStatus": schema_pkg_apis_ignite_v1alpha2_ImageStatus(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.Kernel": schema_pkg_apis_ignite_v1alpha2_Kernel(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.KernelSpec": schema_pkg_apis_ignite_v1alpha2_KernelSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.KernelStatus": schema_pkg_apis_ignite_v1alpha2_KernelStatus(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.OCIImageClaim": schema_pkg_apis_ignite_v1alpha2_OCIImageClaim(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.OCIImageSource": schema_pkg_apis_ignite_v1alpha2_OCIImageSource(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.Pool": schema_pkg_apis_ignite_v1alpha2_Pool(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.PoolDevice": schema_pkg_apis_ignite_v1alpha2_PoolDevice(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.PoolSpec": schema_pkg_apis_ignite_v1alpha2_PoolSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.PoolStatus": schema_pkg_apis_ignite_v1alpha2_PoolStatus(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.SSH": schema_pkg_apis_ignite_v1alpha2_SSH(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VM": schema_pkg_apis_ignite_v1alpha2_VM(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMImageSpec": schema_pkg_apis_ignite_v1alpha2_VMImageSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMKernelSpec": schema_pkg_apis_ignite_v1alpha2_VMKernelSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMNetworkSpec": schema_pkg_apis_ignite_v1alpha2_VMNetworkSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMSpec": schema_pkg_apis_ignite_v1alpha2_VMSpec(ref), - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMStatus": schema_pkg_apis_ignite_v1alpha2_VMStatus(ref), - "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.APIType": schema_pkg_apis_meta_v1alpha1_APIType(ref), - "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.DMID": schema_pkg_apis_meta_v1alpha1_DMID(ref), - "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.ObjectMeta": schema_pkg_apis_meta_v1alpha1_ObjectMeta(ref), - "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.PortMapping": schema_pkg_apis_meta_v1alpha1_PortMapping(ref), - "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.Size": schema_pkg_apis_meta_v1alpha1_Size(ref), - "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.Time": schema_pkg_apis_meta_v1alpha1_Time(ref), - "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.TypeMeta": schema_pkg_apis_meta_v1alpha1_TypeMeta(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.FileMapping": schema_pkg_apis_ignite_v1alpha1_FileMapping(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.Image": schema_pkg_apis_ignite_v1alpha1_Image(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.ImageSpec": schema_pkg_apis_ignite_v1alpha1_ImageSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.ImageStatus": schema_pkg_apis_ignite_v1alpha1_ImageStatus(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.Kernel": schema_pkg_apis_ignite_v1alpha1_Kernel(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.KernelSpec": schema_pkg_apis_ignite_v1alpha1_KernelSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.KernelStatus": schema_pkg_apis_ignite_v1alpha1_KernelStatus(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.OCIImageClaim": schema_pkg_apis_ignite_v1alpha1_OCIImageClaim(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.OCIImageSource": schema_pkg_apis_ignite_v1alpha1_OCIImageSource(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.Pool": schema_pkg_apis_ignite_v1alpha1_Pool(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.PoolDevice": schema_pkg_apis_ignite_v1alpha1_PoolDevice(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.PoolSpec": schema_pkg_apis_ignite_v1alpha1_PoolSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.PoolStatus": schema_pkg_apis_ignite_v1alpha1_PoolStatus(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.SSH": schema_pkg_apis_ignite_v1alpha1_SSH(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VM": schema_pkg_apis_ignite_v1alpha1_VM(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VMImageSpec": schema_pkg_apis_ignite_v1alpha1_VMImageSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VMKernelSpec": schema_pkg_apis_ignite_v1alpha1_VMKernelSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VMNetworkSpec": schema_pkg_apis_ignite_v1alpha1_VMNetworkSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VMSpec": schema_pkg_apis_ignite_v1alpha1_VMSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1.VMStatus": schema_pkg_apis_ignite_v1alpha1_VMStatus(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.BlockDeviceVolume": schema_pkg_apis_ignite_v1alpha2_BlockDeviceVolume(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.FileMapping": schema_pkg_apis_ignite_v1alpha2_FileMapping(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.Image": schema_pkg_apis_ignite_v1alpha2_Image(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.ImageSpec": schema_pkg_apis_ignite_v1alpha2_ImageSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.ImageStatus": schema_pkg_apis_ignite_v1alpha2_ImageStatus(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.Kernel": schema_pkg_apis_ignite_v1alpha2_Kernel(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.KernelSpec": schema_pkg_apis_ignite_v1alpha2_KernelSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.KernelStatus": schema_pkg_apis_ignite_v1alpha2_KernelStatus(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.OCIImageClaim": schema_pkg_apis_ignite_v1alpha2_OCIImageClaim(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.OCIImageSource": schema_pkg_apis_ignite_v1alpha2_OCIImageSource(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.Pool": schema_pkg_apis_ignite_v1alpha2_Pool(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.PoolDevice": schema_pkg_apis_ignite_v1alpha2_PoolDevice(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.PoolSpec": schema_pkg_apis_ignite_v1alpha2_PoolSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.PoolStatus": schema_pkg_apis_ignite_v1alpha2_PoolStatus(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.SSH": schema_pkg_apis_ignite_v1alpha2_SSH(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VM": schema_pkg_apis_ignite_v1alpha2_VM(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMImageSpec": schema_pkg_apis_ignite_v1alpha2_VMImageSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMKernelSpec": schema_pkg_apis_ignite_v1alpha2_VMKernelSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMNetworkSpec": schema_pkg_apis_ignite_v1alpha2_VMNetworkSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMSpec": schema_pkg_apis_ignite_v1alpha2_VMSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMStatus": schema_pkg_apis_ignite_v1alpha2_VMStatus(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMStorageSpec": schema_pkg_apis_ignite_v1alpha2_VMStorageSpec(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.Volume": schema_pkg_apis_ignite_v1alpha2_Volume(ref), + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VolumeMount": schema_pkg_apis_ignite_v1alpha2_VolumeMount(ref), + "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.APIType": schema_pkg_apis_meta_v1alpha1_APIType(ref), + "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.DMID": schema_pkg_apis_meta_v1alpha1_DMID(ref), + "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.ObjectMeta": schema_pkg_apis_meta_v1alpha1_ObjectMeta(ref), + "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.PortMapping": schema_pkg_apis_meta_v1alpha1_PortMapping(ref), + "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.Size": schema_pkg_apis_meta_v1alpha1_Size(ref), + "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.Time": schema_pkg_apis_meta_v1alpha1_Time(ref), + "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.TypeMeta": schema_pkg_apis_meta_v1alpha1_TypeMeta(ref), } } @@ -711,6 +715,26 @@ func schema_pkg_apis_ignite_v1alpha1_VMStatus(ref common.ReferenceCallback) comm } } +func schema_pkg_apis_ignite_v1alpha2_BlockDeviceVolume(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "BlockDeviceVolume defines a block device on the host", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "path": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"path"}, + }, + }, + } +} + func schema_pkg_apis_ignite_v1alpha2_FileMapping(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1286,6 +1310,11 @@ func schema_pkg_apis_ignite_v1alpha2_VMSpec(ref common.ReferenceCallback) common Ref: ref("github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMNetworkSpec"), }, }, + "storage": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMStorageSpec"), + }, + }, "copyFiles": { SchemaProps: spec.SchemaProps{ Description: "This will be done at either \"ignite start\" or \"ignite create\" time", @@ -1310,7 +1339,7 @@ func schema_pkg_apis_ignite_v1alpha2_VMSpec(ref common.ReferenceCallback) common }, }, Dependencies: []string{ - "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.FileMapping", "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.SSH", "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMImageSpec", "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMKernelSpec", "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMNetworkSpec", "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.Size"}, + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.FileMapping", "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.SSH", "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMImageSpec", "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMKernelSpec", "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMNetworkSpec", "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VMStorageSpec", "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1.Size"}, } } @@ -1359,6 +1388,98 @@ func schema_pkg_apis_ignite_v1alpha2_VMStatus(ref common.ReferenceCallback) comm } } +func schema_pkg_apis_ignite_v1alpha2_VMStorageSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VMStorageSpec defines the VM's Volumes and VolumeMounts", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumes": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.Volume"), + }, + }, + }, + }, + }, + "volumeMounts": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VolumeMount"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.Volume", "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.VolumeMount"}, + } +} + +func schema_pkg_apis_ignite_v1alpha2_Volume(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Volume defines named storage volume", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "blockDevice": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.BlockDeviceVolume"), + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{ + "github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2.BlockDeviceVolume"}, + } +} + +func schema_pkg_apis_ignite_v1alpha2_VolumeMount(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VolumeMount defines the mount point for a named volume inside a VM", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "mountPath": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "mountPath"}, + }, + }, + } +} + func schema_pkg_apis_meta_v1alpha1_APIType(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/openapi/violations.txt b/pkg/openapi/violations.txt index a4bb90467..6f9b171d4 100644 --- a/pkg/openapi/violations.txt +++ b/pkg/openapi/violations.txt @@ -4,6 +4,8 @@ API rule violation: list_type_missing,github.com/weaveworks/ignite/pkg/apis/igni API rule violation: list_type_missing,github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2,OCIImageSource,RepoDigests API rule violation: list_type_missing,github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2,PoolStatus,Devices API rule violation: list_type_missing,github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2,VMSpec,CopyFiles +API rule violation: list_type_missing,github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2,VMStorageSpec,VolumeMounts +API rule violation: list_type_missing,github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2,VMStorageSpec,Volumes API rule violation: names_match,github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha1,VMSpec,CPUs API rule violation: names_match,github.com/weaveworks/ignite/pkg/apis/ignite/v1alpha2,VMSpec,CPUs API rule violation: names_match,github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1,DMID,index diff --git a/pkg/operations/reconcile/reconcile.go b/pkg/operations/reconcile/reconcile.go index 672c6aa23..747461d6d 100644 --- a/pkg/operations/reconcile/reconcile.go +++ b/pkg/operations/reconcile/reconcile.go @@ -46,14 +46,15 @@ func ReconcileManifests(s *manifest.ManifestStorage) { // Get the real API object vm, err = c.VMs().Get(upd.APIType.GetUID()) if err != nil { - log.Errorf("Getting VM %q returned an error: %v", upd.APIType.GetUID(), err) + log.Errorf("Getting %s %q returned an error: %v", upd.APIType.GetKind(), upd.APIType.GetUID(), err) continue } // If the object was existent in the storage; validate it // Validate the VM object + // TODO: Validate name uniqueness if err := validation.ValidateVM(vm).ToAggregate(); err != nil { - log.Warn("Skipping %s of %s with UID %s, VM not valid %v.", upd.Event, upd.APIType.GetKind(), upd.APIType.GetUID(), err) + log.Warnf("Skipping %s of %s %q, not valid: %v.", upd.Event, upd.APIType.GetKind(), upd.APIType.GetUID(), err) continue } } diff --git a/pkg/operations/start.go b/pkg/operations/start.go index f6014995b..a69339bba 100644 --- a/pkg/operations/start.go +++ b/pkg/operations/start.go @@ -69,16 +69,28 @@ func StartVM(vm *api.VM, debug bool) error { "SYS_ADMIN", // Needed to run "dmsetup remove" inside the container "NET_ADMIN", // Needed for removing the IP from the container's interface }, - Devices: []string{ - "/dev/mapper/control", // This enables containerized Ignite to remove its own dm snapshot - "/dev/net/tun", // Needed for creating TAP adapters - "/dev/kvm", // Pass through virtualization support - vm.SnapshotDev(), // The block device to boot from + Devices: []*runtime.Bind{ + runtime.BindBoth("/dev/mapper/control"), // This enables containerized Ignite to remove its own dm snapshot + runtime.BindBoth("/dev/net/tun"), // Needed for creating TAP adapters + runtime.BindBoth("/dev/kvm"), // Pass through virtualization support + runtime.BindBoth(vm.SnapshotDev()), // The block device to boot from }, StopTimeout: constants.STOP_TIMEOUT + constants.IGNITE_TIMEOUT, PortBindings: vm.Spec.Network.Ports, // Add the port mappings to Docker } + // Add the volumes to the container devices + for _, volume := range vm.Spec.Storage.Volumes { + if volume.BlockDevice == nil { + continue // Skip all non block device volumes for now + } + + config.Devices = append(config.Devices, &runtime.Bind{ + HostPath: volume.BlockDevice.Path, + ContainerPath: path.Join(constants.IGNITE_SPAWN_VOLUME_DIR, volume.Name), + }) + } + // If the VM is using CNI networking, disable Docker's own implementation if vm.Spec.Network.Mode == api.NetworkModeCNI { config.NetworkMode = "none" @@ -96,8 +108,6 @@ func StartVM(vm *api.VM, debug bool) error { } if vm.Spec.Network.Mode == api.NetworkModeCNI { - // TODO: Right now IP addresses aren't reclaimed when the VM is removed. - // NetworkPlugin.RemoveContainerNetwork needs to be called when removing the VM. if err := providers.NetworkPlugin.SetupContainerNetwork(containerID); err != nil { return err } diff --git a/pkg/runtime/docker/client.go b/pkg/runtime/docker/client.go index 6bb8fc680..57aa6de5c 100644 --- a/pkg/runtime/docker/client.go +++ b/pkg/runtime/docker/client.go @@ -120,8 +120,8 @@ func (dc *dockerClient) RunContainer(image string, config *runtime.ContainerConf devices := make([]container.DeviceMapping, 0, len(config.Devices)) for _, device := range config.Devices { devices = append(devices, container.DeviceMapping{ - PathOnHost: device, - PathInContainer: device, + PathOnHost: device.HostPath, + PathInContainer: device.ContainerPath, CgroupPermissions: "rwm", }) } diff --git a/pkg/runtime/types.go b/pkg/runtime/types.go index 02c160d38..50166879f 100644 --- a/pkg/runtime/types.go +++ b/pkg/runtime/types.go @@ -24,13 +24,21 @@ type Bind struct { ContainerPath string } +// Convenience generator for Binds which have the same host and container path +func BindBoth(path string) *Bind { + return &Bind{ + HostPath: path, + ContainerPath: path, + } +} + type ContainerConfig struct { Cmd []string Hostname string Labels map[string]string Binds []*Bind CapAdds []string - Devices []string + Devices []*Bind StopTimeout uint32 AutoRemove bool NetworkMode string diff --git a/pkg/storage/watch/update/event.go b/pkg/storage/watch/update/event.go index f078eff23..57367b7d7 100644 --- a/pkg/storage/watch/update/event.go +++ b/pkg/storage/watch/update/event.go @@ -1,11 +1,31 @@ package update +import "fmt" + // ObjectEvent is an enum describing a change in an Object's state. type ObjectEvent byte +var _ fmt.Stringer = ObjectEvent(0) + const ( ObjectEventNone ObjectEvent = iota // 0 ObjectEventCreate // 1 ObjectEventModify // 2 ObjectEventDelete // 3 ) + +func (o ObjectEvent) String() string { + switch o { + case 0: + return "NONE" + case 1: + return "CREATE" + case 2: + return "MODIFY" + case 3: + return "DELETE" + } + + // Should never happen + return "UNKNOWN" +} diff --git a/pkg/util/fs.go b/pkg/util/fs.go index 5167c58aa..954c2d7e1 100644 --- a/pkg/util/fs.go +++ b/pkg/util/fs.go @@ -47,6 +47,16 @@ func DirExists(dirname string) bool { return info.IsDir() } +func IsDeviceFile(filename string) (err error) { + if exists, info := PathExists(filename); !exists { + err = fmt.Errorf("device path not found") + } else if info.Mode()&os.ModeDevice == 0 { + err = fmt.Errorf("not a device file") + } + + return +} + // CopyFile copies both files and directories func CopyFile(src string, dst string) error { return copy.Copy(src, dst) diff --git a/pkg/util/math.go b/pkg/util/math.go new file mode 100644 index 000000000..e2672c43f --- /dev/null +++ b/pkg/util/math.go @@ -0,0 +1,9 @@ +package util + +func MaxInt(a, b int) int { + if b > a { + return b + } + + return a +} diff --git a/pkg/util/util.go b/pkg/util/util.go index 42b8e42ed..a902029d8 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -16,7 +16,7 @@ import ( // GenericCheckErr is used by the commands to check if the action failed // and respond with a fatal error provided by the logger (calls os.Exit) -// Ignite has it's own, more detailed implementation of this in cmdutil +// Ignite has its own, more detailed implementation of this in cmdutil func GenericCheckErr(err error) { switch err.(type) { case nil: