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)
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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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())