From 60912bd01ce7717ce7d5cbe30e14acebe1c30cb2 Mon Sep 17 00:00:00 2001 From: James Shubin Date: Mon, 9 Jan 2017 04:06:20 -0500 Subject: [PATCH] resources: Add a `Default` method to the resource API This provides sensible defaults for when they're not the zero value. --- docs/resource-guide.md | 60 ++++++++++++++++++++++++++++++++++++++++++ resources/exec.go | 25 ++++++++++++++++++ resources/file.go | 27 +++++++++++++++++++ resources/hostname.go | 25 ++++++++++++++++++ resources/msg.go | 25 ++++++++++++++++++ resources/noop.go | 26 ++++++++++++++++++ resources/nspawn.go | 28 ++++++++++++++++++++ resources/password.go | 27 +++++++++++++++++++ resources/pkg.go | 27 +++++++++++++++++++ resources/resources.go | 4 ++- resources/svc.go | 25 ++++++++++++++++++ resources/timer.go | 26 ++++++++++++++++++ resources/virt.go | 25 ++++++++++++++++++ 13 files changed, 349 insertions(+), 1 deletion(-) diff --git a/docs/resource-guide.md b/docs/resource-guide.md index 0f1fe720..de112c32 100644 --- a/docs/resource-guide.md +++ b/docs/resource-guide.md @@ -30,10 +30,12 @@ along with this program. If not, see . 1. [Overview](#overview) 2. [Theory - Resource theory in mgmt](#theory) 3. [Resource API - Getting started with mgmt](#resource-api) + * [Default - Get an empty resource with defaults](#default) * [Init - Initialize the resource](#init) * [CheckApply - Check and apply resource state](#checkapply) * [Watch - Detect resource changes](#watch) * [Compare - Compare resource with another](#compare) + * [(UnmarshalYAML) - Optional, sets the YAML defaults](#unmarshalyaml) 4. [Further considerations - More information about resource writing](#further-considerations) 5. [Automatic edges - Adding automatic resources dependencies](#automatic-edges) 6. [Automatic grouping - Grouping multiple resources into one](#automatic-grouping) @@ -69,6 +71,25 @@ To implement a resource in `mgmt` it must satisfy the interface. What follows are each of the method signatures and a description of each. +###Default +```golang +Default() Res +``` + +This returns a populated resource struct as a `Res`. It shouldn't populate any +values which already have the correct default as the golang zero value. In +general it is preferable if the zero values make for the correct defaults. + +####Example +```golang +// Default returns some sensible defaults for this resource. +func (obj *FooRes) Default() Res { + return &FooRes{ + Answer: 42, // sometimes, defaults shouldn't be the zero value + } +} +``` + ###Init ```golang Init() error @@ -378,6 +399,42 @@ CollectPattern() string This is currently a stub and will be updated once the DSL is further along. +###UnmarshalYAML +```golang +UnmarshalYAML(unmarshal func(interface{}) error) error // optional +``` + +This is optional, but recommended for any resource that will have a YAML +accessible struct, and an entry in the `GraphConfig` struct. It is not required +because to do so would mean that third-party or custom resources (such as those +someone writes to use with `libmgmt`) would have to implement this needlessly. + +The signature intentionally matches what is required to satisfy the `go-yaml` +[Unmarshaler](https://godoc.org/gopkg.in/yaml.v2#Unmarshaler) interface. + +####Example +```golang +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *FooRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes FooRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*FooRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to FooRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = FooRes(raw) // restore from indirection with type conversion! + return nil +} +``` + ##Further considerations There is some additional information that any resource writer will need to know. Each issue is listed separately below! @@ -419,6 +476,9 @@ type GraphConfig struct { } ``` +It's also recommended that you add the [UnmarshalYAML](#unmarshalyaml) method to +your resources so that unspecified values are given sane defaults. + ###Gob registration All resources must be registered with the `golang` _gob_ module so that they can be encoded and decoded. Make sure to include the following code snippet for this diff --git a/resources/exec.go b/resources/exec.go index 80dc6972..445687f5 100644 --- a/resources/exec.go +++ b/resources/exec.go @@ -69,6 +69,11 @@ func NewExecRes(name, cmd, shell string, timeout int, watchcmd, watchshell, ifcm return obj, obj.Init() } +// Default returns some sensible defaults for this resource. +func (obj *ExecRes) Default() Res { + return &ExecRes{} +} + // Init runs some startup code for this resource. func (obj *ExecRes) Init() error { obj.BaseRes.kind = "Exec" @@ -433,3 +438,23 @@ func (obj *ExecRes) Compare(res Res) bool { } return true } + +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *ExecRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes ExecRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*ExecRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to ExecRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = ExecRes(raw) // restore from indirection with type conversion! + return nil +} diff --git a/resources/file.go b/resources/file.go index 10e84004..09376942 100644 --- a/resources/file.go +++ b/resources/file.go @@ -77,6 +77,13 @@ func NewFileRes(name, path, dirname, basename string, content *string, source, s return obj, obj.Init() } +// Default returns some sensible defaults for this resource. +func (obj *FileRes) Default() Res { + return &FileRes{ + State: "exists", + } +} + // Init runs some startup code for this resource. func (obj *FileRes) Init() error { obj.sha256sum = "" @@ -804,3 +811,23 @@ func (obj *FileRes) CollectPattern(pattern string) { // XXX: currently the pattern for files can only override the Dirname variable :P obj.Dirname = pattern // XXX: simplistic for now } + +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *FileRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes FileRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*FileRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to FileRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = FileRes(raw) // restore from indirection with type conversion! + return nil +} diff --git a/resources/hostname.go b/resources/hostname.go index 48dc9ab1..d0a5d6b7 100644 --- a/resources/hostname.go +++ b/resources/hostname.go @@ -82,6 +82,11 @@ func NewHostnameRes(name, staticHostname, transientHostname, prettyHostname stri return obj, obj.Init() } +// Default returns some sensible defaults for this resource. +func (obj *HostnameRes) Default() Res { + return &HostnameRes{} +} + // Init runs some startup code for this resource. func (obj *HostnameRes) Init() error { obj.BaseRes.kind = "Hostname" @@ -293,3 +298,23 @@ func (obj *HostnameRes) Compare(res Res) bool { } return true } + +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *HostnameRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes HostnameRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*HostnameRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to HostnameRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = HostnameRes(raw) // restore from indirection with type conversion! + return nil +} diff --git a/resources/msg.go b/resources/msg.go index 94bd51d4..72d1ac37 100644 --- a/resources/msg.go +++ b/resources/msg.go @@ -73,6 +73,11 @@ func NewMsgRes(name, body, priority string, journal, syslog bool, fields map[str return obj, obj.Init() } +// Default returns some sensible defaults for this resource. +func (obj *MsgRes) Default() Res { + return &MsgRes{} +} + // Init runs some startup code for this resource. func (obj *MsgRes) Init() error { obj.BaseRes.kind = "Msg" @@ -259,3 +264,23 @@ func (obj *MsgRes) Compare(res Res) bool { } return true } + +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *MsgRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes MsgRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*MsgRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to MsgRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = MsgRes(raw) // restore from indirection with type conversion! + return nil +} diff --git a/resources/noop.go b/resources/noop.go index efc22c8b..a989665d 100644 --- a/resources/noop.go +++ b/resources/noop.go @@ -19,6 +19,7 @@ package resources import ( "encoding/gob" + "fmt" "log" "github.com/purpleidea/mgmt/event" @@ -45,6 +46,11 @@ func NewNoopRes(name string) (*NoopRes, error) { return obj, obj.Init() } +// Default returns some sensible defaults for this resource. +func (obj *NoopRes) Default() Res { + return &NoopRes{} +} + // Init runs some startup code for this resource. func (obj *NoopRes) Init() error { obj.BaseRes.kind = "Noop" @@ -153,3 +159,23 @@ func (obj *NoopRes) Compare(res Res) bool { } return true } + +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *NoopRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes NoopRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*NoopRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to NoopRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = NoopRes(raw) // restore from indirection with type conversion! + return nil +} diff --git a/resources/nspawn.go b/resources/nspawn.go index f66df22e..7fb02dfc 100644 --- a/resources/nspawn.go +++ b/resources/nspawn.go @@ -57,6 +57,13 @@ type NspawnRes struct { svc *SvcRes } +// Default returns some sensible defaults for this resource. +func (obj *NspawnRes) Default() Res { + return &NspawnRes{ + State: running, + } +} + // Init runs some startup code for this resource func (obj *NspawnRes) Init() error { var serviceName = fmt.Sprintf(nspawnServiceTmpl, obj.GetName()) @@ -83,6 +90,7 @@ func NewNspawnRes(name string, state string) (*NspawnRes, error) { // Validate params func (obj *NspawnRes) Validate() error { + // TODO: validStates should be an enum! validStates := map[string]struct{}{ stopped: {}, running: {}, @@ -304,3 +312,23 @@ func (obj *NspawnRes) Compare(res Res) bool { func (obj *NspawnRes) AutoEdges() AutoEdge { return nil } + +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *NspawnRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes NspawnRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*NspawnRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to NspawnRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = NspawnRes(raw) // restore from indirection with type conversion! + return nil +} diff --git a/resources/password.go b/resources/password.go index efdc16bf..415889d3 100644 --- a/resources/password.go +++ b/resources/password.go @@ -67,6 +67,13 @@ func NewPasswordRes(name string, length uint16) (*PasswordRes, error) { return obj, obj.Init() } +// Default returns some sensible defaults for this resource. +func (obj *PasswordRes) Default() Res { + return &PasswordRes{ + Length: 64, // safe default + } +} + // Init generates a new password for this resource if one was not provided. It // will save this into a local file. It will load it back in from previous runs. func (obj *PasswordRes) Init() error { @@ -363,3 +370,23 @@ func (obj *PasswordRes) Compare(res Res) bool { } return true } + +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *PasswordRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes PasswordRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*PasswordRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to PasswordRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = PasswordRes(raw) // restore from indirection with type conversion! + return nil +} diff --git a/resources/pkg.go b/resources/pkg.go index b2d4e415..0f63a1dc 100644 --- a/resources/pkg.go +++ b/resources/pkg.go @@ -60,6 +60,13 @@ func NewPkgRes(name, state string, allowuntrusted, allownonfree, allowunsupporte return obj, obj.Init() } +// Default returns some sensible defaults for this resource. +func (obj *PkgRes) Default() Res { + return &PkgRes{ + State: "installed", // i think this is preferable to "latest" + } +} + // Init runs some startup code for this resource. func (obj *PkgRes) Init() error { obj.BaseRes.kind = "Pkg" @@ -544,3 +551,23 @@ func ReturnSvcInFileList(fileList []string) []string { } return result } + +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *PkgRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes PkgRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*PkgRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to PkgRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = PkgRes(raw) // restore from indirection with type conversion! + return nil +} diff --git a/resources/resources.go b/resources/resources.go index 99efaedf..573b339e 100644 --- a/resources/resources.go +++ b/resources/resources.go @@ -162,7 +162,8 @@ type Base interface { // Res is the minimum interface you need to implement to define a new resource. type Res interface { - Base // include everything from the Base interface + Base // include everything from the Base interface + Default() Res // return a struct with sane defaults as a Res Init() error //Validate() error // TODO: this might one day be added GetUIDs() []ResUID // most resources only return one @@ -171,6 +172,7 @@ type Res interface { AutoEdges() AutoEdge Compare(Res) bool CollectPattern(string) // XXX: temporary until Res collection is more advanced + //UnmarshalYAML(unmarshal func(interface{}) error) error // optional } // BaseRes is the base struct that gets used in every resource. diff --git a/resources/svc.go b/resources/svc.go index 02325e4c..abda0347 100644 --- a/resources/svc.go +++ b/resources/svc.go @@ -56,6 +56,11 @@ func NewSvcRes(name, state, startup string) (*SvcRes, error) { return obj, obj.Init() } +// Default returns some sensible defaults for this resource. +func (obj *SvcRes) Default() Res { + return &SvcRes{} +} + // Init runs some startup code for this resource. func (obj *SvcRes) Init() error { obj.BaseRes.kind = "Svc" @@ -454,3 +459,23 @@ func (obj *SvcRes) Compare(res Res) bool { } return true } + +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *SvcRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes SvcRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*SvcRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to SvcRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = SvcRes(raw) // restore from indirection with type conversion! + return nil +} diff --git a/resources/timer.go b/resources/timer.go index 2bd713ac..2f29ca05 100644 --- a/resources/timer.go +++ b/resources/timer.go @@ -19,6 +19,7 @@ package resources import ( "encoding/gob" + "fmt" "log" "time" @@ -54,6 +55,11 @@ func NewTimerRes(name string, interval uint32) (*TimerRes, error) { return obj, obj.Init() } +// Default returns some sensible defaults for this resource. +func (obj *TimerRes) Default() Res { + return &TimerRes{} +} + // Init runs some startup code for this resource. func (obj *TimerRes) Init() error { obj.BaseRes.kind = "Timer" @@ -165,3 +171,23 @@ func (obj *TimerRes) Compare(res Res) bool { } return true } + +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *TimerRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes TimerRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*TimerRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to TimerRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = TimerRes(raw) // restore from indirection with type conversion! + return nil +} diff --git a/resources/virt.go b/resources/virt.go index dbc2fec3..10eaef28 100644 --- a/resources/virt.go +++ b/resources/virt.go @@ -91,6 +91,11 @@ func NewVirtRes(name string, uri, state string, transient bool, cpus uint, memor return obj, obj.Init() } +// Default returns some sensible defaults for this resource. +func (obj *VirtRes) Default() Res { + return &VirtRes{} +} + // Init runs some startup code for this resource. func (obj *VirtRes) Init() error { if !libvirtInitialized { @@ -765,6 +770,26 @@ func (obj *VirtRes) Compare(res Res) bool { func (obj *VirtRes) CollectPattern(string) { } +// UnmarshalYAML is the custom unmarshal handler for this struct. +// It is primarily useful for setting the defaults. +func (obj *VirtRes) UnmarshalYAML(unmarshal func(interface{}) error) error { + type rawRes VirtRes // indirection to avoid infinite recursion + + def := obj.Default() // get the default + res, ok := def.(*VirtRes) // put in the right format + if !ok { + return fmt.Errorf("could not convert to VirtRes") + } + raw := rawRes(*res) // convert; the defaults go here + + if err := unmarshal(&raw); err != nil { + return err + } + + *obj = VirtRes(raw) // restore from indirection with type conversion! + return nil +} + // randMAC returns a random mac address in the libvirt range. func randMAC() string { rand.Seed(time.Now().UnixNano())