resources: Add a Default method to the resource API

This provides sensible defaults for when they're not the zero value.
This commit is contained in:
James Shubin
2017-01-09 04:06:20 -05:00
parent 0b416e44f8
commit 60912bd01c
13 changed files with 349 additions and 1 deletions

View File

@@ -30,10 +30,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
1. [Overview](#overview) 1. [Overview](#overview)
2. [Theory - Resource theory in mgmt](#theory) 2. [Theory - Resource theory in mgmt](#theory)
3. [Resource API - Getting started with mgmt](#resource-api) 3. [Resource API - Getting started with mgmt](#resource-api)
* [Default - Get an empty resource with defaults](#default)
* [Init - Initialize the resource](#init) * [Init - Initialize the resource](#init)
* [CheckApply - Check and apply resource state](#checkapply) * [CheckApply - Check and apply resource state](#checkapply)
* [Watch - Detect resource changes](#watch) * [Watch - Detect resource changes](#watch)
* [Compare - Compare resource with another](#compare) * [Compare - Compare resource with another](#compare)
* [(UnmarshalYAML) - Optional, sets the YAML defaults](#unmarshalyaml)
4. [Further considerations - More information about resource writing](#further-considerations) 4. [Further considerations - More information about resource writing](#further-considerations)
5. [Automatic edges - Adding automatic resources dependencies](#automatic-edges) 5. [Automatic edges - Adding automatic resources dependencies](#automatic-edges)
6. [Automatic grouping - Grouping multiple resources into one](#automatic-grouping) 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 interface. What follows are each of the method signatures and a description of
each. 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 ###Init
```golang ```golang
Init() error Init() error
@@ -378,6 +399,42 @@ CollectPattern() string
This is currently a stub and will be updated once the DSL is further along. 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 ##Further considerations
There is some additional information that any resource writer will need to know. There is some additional information that any resource writer will need to know.
Each issue is listed separately below! 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 ###Gob registration
All resources must be registered with the `golang` _gob_ module so that they can 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 be encoded and decoded. Make sure to include the following code snippet for this

View File

@@ -69,6 +69,11 @@ func NewExecRes(name, cmd, shell string, timeout int, watchcmd, watchshell, ifcm
return obj, obj.Init() 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. // Init runs some startup code for this resource.
func (obj *ExecRes) Init() error { func (obj *ExecRes) Init() error {
obj.BaseRes.kind = "Exec" obj.BaseRes.kind = "Exec"
@@ -433,3 +438,23 @@ func (obj *ExecRes) Compare(res Res) bool {
} }
return true 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
}

View File

@@ -77,6 +77,13 @@ func NewFileRes(name, path, dirname, basename string, content *string, source, s
return obj, obj.Init() 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. // Init runs some startup code for this resource.
func (obj *FileRes) Init() error { func (obj *FileRes) Init() error {
obj.sha256sum = "" 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 // XXX: currently the pattern for files can only override the Dirname variable :P
obj.Dirname = pattern // XXX: simplistic for now 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
}

View File

@@ -82,6 +82,11 @@ func NewHostnameRes(name, staticHostname, transientHostname, prettyHostname stri
return obj, obj.Init() 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. // Init runs some startup code for this resource.
func (obj *HostnameRes) Init() error { func (obj *HostnameRes) Init() error {
obj.BaseRes.kind = "Hostname" obj.BaseRes.kind = "Hostname"
@@ -293,3 +298,23 @@ func (obj *HostnameRes) Compare(res Res) bool {
} }
return true 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
}

View File

@@ -73,6 +73,11 @@ func NewMsgRes(name, body, priority string, journal, syslog bool, fields map[str
return obj, obj.Init() 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. // Init runs some startup code for this resource.
func (obj *MsgRes) Init() error { func (obj *MsgRes) Init() error {
obj.BaseRes.kind = "Msg" obj.BaseRes.kind = "Msg"
@@ -259,3 +264,23 @@ func (obj *MsgRes) Compare(res Res) bool {
} }
return true 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
}

View File

@@ -19,6 +19,7 @@ package resources
import ( import (
"encoding/gob" "encoding/gob"
"fmt"
"log" "log"
"github.com/purpleidea/mgmt/event" "github.com/purpleidea/mgmt/event"
@@ -45,6 +46,11 @@ func NewNoopRes(name string) (*NoopRes, error) {
return obj, obj.Init() 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. // Init runs some startup code for this resource.
func (obj *NoopRes) Init() error { func (obj *NoopRes) Init() error {
obj.BaseRes.kind = "Noop" obj.BaseRes.kind = "Noop"
@@ -153,3 +159,23 @@ func (obj *NoopRes) Compare(res Res) bool {
} }
return true 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
}

View File

@@ -57,6 +57,13 @@ type NspawnRes struct {
svc *SvcRes 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 // Init runs some startup code for this resource
func (obj *NspawnRes) Init() error { func (obj *NspawnRes) Init() error {
var serviceName = fmt.Sprintf(nspawnServiceTmpl, obj.GetName()) var serviceName = fmt.Sprintf(nspawnServiceTmpl, obj.GetName())
@@ -83,6 +90,7 @@ func NewNspawnRes(name string, state string) (*NspawnRes, error) {
// Validate params // Validate params
func (obj *NspawnRes) Validate() error { func (obj *NspawnRes) Validate() error {
// TODO: validStates should be an enum!
validStates := map[string]struct{}{ validStates := map[string]struct{}{
stopped: {}, stopped: {},
running: {}, running: {},
@@ -304,3 +312,23 @@ func (obj *NspawnRes) Compare(res Res) bool {
func (obj *NspawnRes) AutoEdges() AutoEdge { func (obj *NspawnRes) AutoEdges() AutoEdge {
return nil 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
}

View File

@@ -67,6 +67,13 @@ func NewPasswordRes(name string, length uint16) (*PasswordRes, error) {
return obj, obj.Init() 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 // 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. // will save this into a local file. It will load it back in from previous runs.
func (obj *PasswordRes) Init() error { func (obj *PasswordRes) Init() error {
@@ -363,3 +370,23 @@ func (obj *PasswordRes) Compare(res Res) bool {
} }
return true 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
}

View File

@@ -60,6 +60,13 @@ func NewPkgRes(name, state string, allowuntrusted, allownonfree, allowunsupporte
return obj, obj.Init() 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. // Init runs some startup code for this resource.
func (obj *PkgRes) Init() error { func (obj *PkgRes) Init() error {
obj.BaseRes.kind = "Pkg" obj.BaseRes.kind = "Pkg"
@@ -544,3 +551,23 @@ func ReturnSvcInFileList(fileList []string) []string {
} }
return result 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
}

View File

@@ -163,6 +163,7 @@ type Base interface {
// Res is the minimum interface you need to implement to define a new resource. // Res is the minimum interface you need to implement to define a new resource.
type Res interface { 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 Init() error
//Validate() error // TODO: this might one day be added //Validate() error // TODO: this might one day be added
GetUIDs() []ResUID // most resources only return one GetUIDs() []ResUID // most resources only return one
@@ -171,6 +172,7 @@ type Res interface {
AutoEdges() AutoEdge AutoEdges() AutoEdge
Compare(Res) bool Compare(Res) bool
CollectPattern(string) // XXX: temporary until Res collection is more advanced 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. // BaseRes is the base struct that gets used in every resource.

View File

@@ -56,6 +56,11 @@ func NewSvcRes(name, state, startup string) (*SvcRes, error) {
return obj, obj.Init() 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. // Init runs some startup code for this resource.
func (obj *SvcRes) Init() error { func (obj *SvcRes) Init() error {
obj.BaseRes.kind = "Svc" obj.BaseRes.kind = "Svc"
@@ -454,3 +459,23 @@ func (obj *SvcRes) Compare(res Res) bool {
} }
return true 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
}

View File

@@ -19,6 +19,7 @@ package resources
import ( import (
"encoding/gob" "encoding/gob"
"fmt"
"log" "log"
"time" "time"
@@ -54,6 +55,11 @@ func NewTimerRes(name string, interval uint32) (*TimerRes, error) {
return obj, obj.Init() 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. // Init runs some startup code for this resource.
func (obj *TimerRes) Init() error { func (obj *TimerRes) Init() error {
obj.BaseRes.kind = "Timer" obj.BaseRes.kind = "Timer"
@@ -165,3 +171,23 @@ func (obj *TimerRes) Compare(res Res) bool {
} }
return true 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
}

View File

@@ -91,6 +91,11 @@ func NewVirtRes(name string, uri, state string, transient bool, cpus uint, memor
return obj, obj.Init() 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. // Init runs some startup code for this resource.
func (obj *VirtRes) Init() error { func (obj *VirtRes) Init() error {
if !libvirtInitialized { if !libvirtInitialized {
@@ -765,6 +770,26 @@ func (obj *VirtRes) Compare(res Res) bool {
func (obj *VirtRes) CollectPattern(string) { 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. // randMAC returns a random mac address in the libvirt range.
func randMAC() string { func randMAC() string {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())