resources: Unify resource creation and kind setting

This removes the duplication of the kind string and cleans up things for
resource creation.
This commit is contained in:
James Shubin
2017-06-07 02:26:14 -04:00
parent 2f6c77fba2
commit b8ff6938df
30 changed files with 60 additions and 108 deletions

View File

@@ -73,14 +73,13 @@ Init() error
```
This is called to initialize the resource. If something goes wrong, it should
return an error. It should set the resource `kind`, do any resource specific
work, and finish by calling the `Init` method of the base resource.
return an error. It should do any resource specific work, and finish by calling
the `Init` method of the base resource.
#### Example
```golang
// Init initializes the Foo resource.
func (obj *FooRes) Init() error {
obj.BaseRes.Kind = "foo" // must lower case resource kind
// run the resource specific initialization, and error if anything fails
if some_error {
return err // something went wrong!
@@ -399,9 +398,9 @@ 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.
accessible 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.
@@ -453,35 +452,15 @@ type FooRes struct {
}
```
### YAML
In addition to labelling your resource struct with YAML fields, you must also
add an entry to the internal `GraphConfig` struct. It is a fairly straight
forward one line patch.
### Resource registration
All resources must be registered with the engine so that they can be found. This
also ensures they can be encoded and decoded. Make sure to include the following
code snippet for this to work.
```golang
type GraphConfig struct {
// [snip...]
Resources struct {
Noop []*resources.NoopRes `yaml:"noop"`
File []*resources.FileRes `yaml:"file"`
// [snip...]
Foo []*resources.FooRes `yaml:"foo"` // tada :)
}
}
```
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
to work.
```golang
import "encoding/gob"
func init() { // special golang method that runs once
gob.Register(&FooRes{}) // substitude your resource here
// set your resource kind and struct here (the kind must be lower case)
RegisterResource("foo", func() Res { return &FooRes{} })
}
```

View File

@@ -171,7 +171,6 @@ func GetResources(obj *EmbdEtcd, hostnameFilter, kindFilter []string) ([]resourc
}
if obj, err := resources.B64ToRes(val); err == nil {
obj.SetKind(kind) // cheap init
log.Printf("Etcd: Get: (Hostname, Kind, Name): (%s, %s, %s)", hostname, kind, name)
resourceList = append(resourceList, obj)
} else {

View File

@@ -61,12 +61,12 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
return nil, err
}
// FIXME: these are being specified temporarily until it's the default!
metaparams := resources.DefaultMetaParams
exec1 := &resources.ExecRes{
BaseRes: resources.BaseRes{
Name: "exec1",
Kind: "exec",
MetaParams: metaparams,
},
Cmd: "echo hello world && echo goodbye world 1>&2", // to stdout && stderr
@@ -77,6 +77,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
output := &resources.FileRes{
BaseRes: resources.BaseRes{
Name: "output",
Kind: "file",
MetaParams: metaparams,
// send->recv!
Recv: map[string]*resources.Send{
@@ -92,6 +93,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
stdout := &resources.FileRes{
BaseRes: resources.BaseRes{
Name: "stdout",
Kind: "file",
MetaParams: metaparams,
// send->recv!
Recv: map[string]*resources.Send{
@@ -107,6 +109,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
stderr := &resources.FileRes{
BaseRes: resources.BaseRes{
Name: "stderr",
Kind: "file",
MetaParams: metaparams,
// send->recv!
Recv: map[string]*resources.Send{

View File

@@ -63,6 +63,7 @@ func (obj *MyGAPI) subGraph() (*pgraph.Graph, error) {
f1 := &resources.FileRes{
BaseRes: resources.BaseRes{
Name: "file1",
Kind: "file",
MetaParams: metaparams,
},
Path: "/tmp/mgmt/sub1",
@@ -74,6 +75,7 @@ func (obj *MyGAPI) subGraph() (*pgraph.Graph, error) {
n1 := &resources.NoopRes{
BaseRes: resources.BaseRes{
Name: "noop1",
Kind: "noop",
MetaParams: metaparams,
},
}
@@ -93,7 +95,6 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
return nil, err
}
// FIXME: these are being specified temporarily until it's the default!
metaparams := resources.DefaultMetaParams
content := "I created a subgraph!\n"

View File

@@ -61,13 +61,13 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
return nil, err
}
// FIXME: these are being specified temporarily until it's the default!
metaparams := resources.DefaultMetaParams
content := "I created a subgraph!\n"
f0 := &resources.FileRes{
BaseRes: resources.BaseRes{
Name: "README",
Kind: "file",
MetaParams: metaparams,
},
Path: "/tmp/mgmt/README",
@@ -86,6 +86,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
f1 := &resources.FileRes{
BaseRes: resources.BaseRes{
Name: "file1",
Kind: "file",
MetaParams: metaparams,
},
Path: "/tmp/mgmt/sub1",
@@ -97,6 +98,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
n1 := &resources.NoopRes{
BaseRes: resources.BaseRes{
Name: "noop1",
Kind: "noop",
MetaParams: metaparams,
},
}
@@ -110,6 +112,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
subGraphRes0 := &resources.GraphRes{ // TODO: should we name this SubGraphRes ?
BaseRes: resources.BaseRes{
Name: "subgraph1",
Kind: "graph",
MetaParams: metaparams,
},
Graph: subGraph,

View File

@@ -57,9 +57,11 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
return nil, fmt.Errorf("libmgmt: MyGAPI is not initialized")
}
// TODO: this method of instantiation is deprecated, use: NewResource
n1 := &resources.NoopRes{
BaseRes: resources.BaseRes{
Name: "noop1",
Kind: "noop",
MetaParams: resources.DefaultMetaParams,
},
}

View File

@@ -65,12 +65,11 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
}
var vertex pgraph.Vertex
for i := uint(0); i < obj.Count; i++ {
n := &resources.NoopRes{
BaseRes: resources.BaseRes{
Name: fmt.Sprintf("noop%d", i),
MetaParams: resources.DefaultMetaParams,
},
n, err := resources.NewResource("noop")
if err != nil {
return nil, err
}
n.SetName(fmt.Sprintf("noop%d", i))
g.AddVertex(n)
if i > 0 {
g.AddEdge(vertex, n, &resources.Edge{Name: fmt.Sprintf("e%d", i)})

View File

@@ -61,13 +61,13 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
return nil, err
}
// FIXME: these are being specified temporarily until it's the default!
metaparams := resources.DefaultMetaParams
content := "Delete me to trigger a notification!\n"
f0 := &resources.FileRes{
BaseRes: resources.BaseRes{
Name: "README",
Kind: "file",
MetaParams: metaparams,
},
Path: "/tmp/mgmt/README",
@@ -80,6 +80,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
p1 := &resources.PasswordRes{
BaseRes: resources.BaseRes{
Name: "password1",
Kind: "password",
MetaParams: metaparams,
},
Length: 8, // generated string will have this many characters
@@ -90,6 +91,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
f1 := &resources.FileRes{
BaseRes: resources.BaseRes{
Name: "file1",
Kind: "file",
MetaParams: metaparams,
// send->recv!
Recv: map[string]*resources.Send{
@@ -106,6 +108,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
n1 := &resources.NoopRes{
BaseRes: resources.BaseRes{
Name: "noop1",
Kind: "noop",
MetaParams: metaparams,
},
}

View File

@@ -19,7 +19,6 @@
package resources
import (
"encoding/gob"
"fmt"
"log"
"os"
@@ -39,7 +38,6 @@ const (
)
func init() {
gob.Register(&AugeasRes{})
RegisterResource("augeas", func() Res { return &AugeasRes{} })
}
@@ -94,7 +92,6 @@ func (obj *AugeasRes) Validate() error {
// Init initiates the resource.
func (obj *AugeasRes) Init() error {
obj.BaseRes.Kind = "augeas"
return obj.BaseRes.Init() // call base init, b/c we're overriding
}

View File

@@ -108,6 +108,7 @@ func NewNoopResTest(name string) *NoopResTest {
NoopRes: NoopRes{
BaseRes: BaseRes{
Name: name,
Kind: "noop",
MetaParams: MetaParams{
AutoGroup: true, // always autogroup
},

View File

@@ -20,7 +20,6 @@ package resources
import (
"bufio"
"bytes"
"encoding/gob"
"fmt"
"log"
"os/exec"
@@ -34,7 +33,6 @@ import (
)
func init() {
gob.Register(&ExecRes{})
RegisterResource("exec", func() Res { return &ExecRes{} })
}
@@ -73,7 +71,6 @@ func (obj *ExecRes) Validate() error {
// Init runs some startup code for this resource.
func (obj *ExecRes) Init() error {
obj.BaseRes.Kind = "exec"
return obj.BaseRes.Init() // call base init, b/c we're overriding
}

View File

@@ -25,6 +25,7 @@ func TestExecSendRecv1(t *testing.T) {
r1 := &ExecRes{
BaseRes: BaseRes{
Name: "exec1",
Kind: "exec",
//MetaParams: MetaParams,
},
Cmd: "echo hello world",
@@ -71,6 +72,7 @@ func TestExecSendRecv2(t *testing.T) {
r1 := &ExecRes{
BaseRes: BaseRes{
Name: "exec1",
Kind: "exec",
//MetaParams: MetaParams,
},
Cmd: "echo hello world 1>&2", // to stderr
@@ -117,6 +119,7 @@ func TestExecSendRecv3(t *testing.T) {
r1 := &ExecRes{
BaseRes: BaseRes{
Name: "exec1",
Kind: "exec",
//MetaParams: MetaParams,
},
Cmd: "echo hello world && echo goodbye world 1>&2", // to stdout && stderr

View File

@@ -20,7 +20,6 @@ package resources
import (
"bytes"
"crypto/sha256"
"encoding/gob"
"encoding/hex"
"fmt"
"io"
@@ -41,7 +40,6 @@ import (
)
func init() {
gob.Register(&FileRes{})
RegisterResource("file", func() Res { return &FileRes{} })
}
@@ -148,7 +146,6 @@ func (obj *FileRes) Init() error {
obj.path = obj.GetPath() // compute once
obj.isDir = strings.HasSuffix(obj.path, "/") // dirs have trailing slashes
obj.BaseRes.Kind = "file"
return obj.BaseRes.Init() // call base init, b/c we're overriding
}

View File

@@ -18,7 +18,6 @@
package resources
import (
"encoding/gob"
"fmt"
"github.com/purpleidea/mgmt/pgraph"
@@ -29,7 +28,6 @@ import (
func init() {
RegisterResource("graph", func() Res { return &GraphRes{} })
gob.Register(&GraphRes{})
}
// GraphRes is a resource that recursively runs a sub graph of resources.
@@ -89,7 +87,6 @@ func (obj *GraphRes) Init() error {
}
}
obj.BaseRes.Kind = "graph"
return obj.BaseRes.Init() // call base init, b/c we're overrriding
}

View File

@@ -18,7 +18,6 @@
package resources
import (
"encoding/gob"
"errors"
"fmt"
"log"
@@ -36,7 +35,6 @@ var ErrResourceInsufficientParameters = errors.New(
func init() {
RegisterResource("hostname", func() Res { return &HostnameRes{} })
gob.Register(&HostnameRes{})
}
const (
@@ -88,7 +86,6 @@ func (obj *HostnameRes) Validate() error {
// Init runs some startup code for this resource.
func (obj *HostnameRes) Init() error {
obj.BaseRes.Kind = "hostname"
if obj.PrettyHostname == "" {
obj.PrettyHostname = obj.Hostname
}

View File

@@ -18,7 +18,6 @@
package resources
import (
"encoding/gob"
"fmt"
"log"
"strconv"
@@ -28,7 +27,6 @@ import (
func init() {
RegisterResource("kv", func() Res { return &KVRes{} })
gob.Register(&KVRes{})
}
// KVResSkipCmpStyle represents the different styles of comparison when using SkipLessThan.
@@ -89,7 +87,6 @@ func (obj *KVRes) Validate() error {
// Init initializes the resource.
func (obj *KVRes) Init() error {
obj.BaseRes.Kind = "kv"
return obj.BaseRes.Init() // call base init, b/c we're overriding
}

View File

@@ -18,7 +18,6 @@
package resources
import (
"encoding/gob"
"fmt"
"log"
"regexp"
@@ -29,7 +28,6 @@ import (
func init() {
RegisterResource("msg", func() Res { return &MsgRes{} })
gob.Register(&MsgRes{})
}
// MsgRes is a resource that writes messages to logs.
@@ -76,7 +74,6 @@ func (obj *MsgRes) Validate() error {
// Init runs some startup code for this resource.
func (obj *MsgRes) Init() error {
obj.BaseRes.Kind = "msg"
return obj.BaseRes.Init() // call base init, b/c we're overrriding
}

View File

@@ -18,14 +18,12 @@
package resources
import (
"encoding/gob"
"fmt"
"log"
)
func init() {
RegisterResource("noop", func() Res { return &NoopRes{} })
gob.Register(&NoopRes{})
}
// NoopRes is a no-op resource that does nothing.
@@ -50,7 +48,6 @@ func (obj *NoopRes) Validate() error {
// Init runs some startup code for this resource.
func (obj *NoopRes) Init() error {
obj.BaseRes.Kind = "noop"
return obj.BaseRes.Init() // call base init, b/c we're overriding
}

View File

@@ -18,7 +18,6 @@
package resources
import (
"encoding/gob"
"errors"
"fmt"
"log"
@@ -42,7 +41,6 @@ const (
func init() {
RegisterResource("nspawn", func() Res { return &NspawnRes{} })
gob.Register(&NspawnRes{})
}
// NspawnRes is an nspawn container resource.
@@ -93,7 +91,6 @@ func (obj *NspawnRes) Init() error {
if err := obj.svc.Init(); err != nil {
return err
}
obj.BaseRes.Kind = "nspawn"
return obj.BaseRes.Init()
}

View File

@@ -19,7 +19,6 @@ package resources
import (
"crypto/rand"
"encoding/gob"
"fmt"
"io/ioutil"
"log"
@@ -35,7 +34,6 @@ import (
func init() {
RegisterResource("password", func() Res { return &PasswordRes{} })
gob.Register(&PasswordRes{})
}
const (
@@ -74,7 +72,6 @@ func (obj *PasswordRes) Validate() error {
// 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 {
obj.BaseRes.Kind = "password" // must be set before using VarDir
dir, err := obj.VarDir("")
if err != nil {

View File

@@ -18,7 +18,6 @@
package resources
import (
"encoding/gob"
"fmt"
"log"
"path"
@@ -32,7 +31,6 @@ import (
func init() {
RegisterResource("pkg", func() Res { return &PkgRes{} })
gob.Register(&PkgRes{})
}
// PkgRes is a package resource for packagekit.
@@ -67,7 +65,6 @@ func (obj *PkgRes) Validate() error {
// Init runs some startup code for this resource.
func (obj *PkgRes) Init() error {
obj.BaseRes.Kind = "pkg"
if err := obj.BaseRes.Init(); err != nil { // call base init, b/c we're overriding
return err
}

View File

@@ -19,6 +19,7 @@
package resources
import (
"encoding/gob"
"fmt"
"log"
"math"
@@ -43,18 +44,21 @@ var registeredResources = map[string]func() Res{}
// RegisterResource registers a new resource by providing a constructor
// function that returns a resource object ready to be unmarshalled from YAML.
func RegisterResource(name string, creator func() Res) {
registeredResources[name] = creator
func RegisterResource(kind string, fn func() Res) {
gob.Register(fn())
registeredResources[kind] = fn
}
// NewEmptyNamedResource returns an empty resource object from a registered
// type, ready to be unmarshalled.
func NewEmptyNamedResource(name string) (Res, error) {
fn, ok := registeredResources[name]
// NewResource returns an empty resource object from a registered kind.
func NewResource(kind string) (Res, error) {
fn, ok := registeredResources[kind]
if !ok {
return nil, fmt.Errorf("no resource named %s available", name)
return nil, fmt.Errorf("no resource kind `%s` available", kind)
}
return fn(), nil
res := fn().Default()
res.SetKind(kind)
//*res.Meta() = DefaultMetaParams // TODO: centralize this here?
return res, nil
}
//go:generate stringer -type=ResState -output=resstate_stringer.go

View File

@@ -42,6 +42,7 @@ func TestCompare2(t *testing.T) {
r1 := &NoopRes{
BaseRes: BaseRes{
Name: "noop1",
Kind: "noop",
MetaParams: MetaParams{
Noop: true,
},
@@ -49,7 +50,8 @@ func TestCompare2(t *testing.T) {
}
r2 := &NoopRes{
BaseRes: BaseRes{
Name: "noop1", // same nampe
Name: "noop1", // same name
Kind: "noop",
MetaParams: MetaParams{
Noop: false, // different noop
},
@@ -111,12 +113,9 @@ func TestMiscEncodeDecode1(t *testing.T) {
func TestMiscEncodeDecode2(t *testing.T) {
var err error
//gob.Register( &NoopRes{} ) // happens in noop.go : init()
//gob.Register( &FileRes{} ) // happens in file.go : init()
// ...
// encode
var input Res = &FileRes{}
input, _ := NewResource("file")
b64, err := ResToB64(input)
if err != nil {

View File

@@ -41,6 +41,7 @@ func NewNoopResTestSema(name string, semas []string) *NoopResTest {
NoopRes: NoopRes{
BaseRes: BaseRes{
Name: name,
Kind: "noop",
MetaParams: MetaParams{
AutoGroup: true, // always autogroup
Sema: semas,

View File

@@ -20,7 +20,6 @@
package resources
import (
"encoding/gob"
"fmt"
"log"
@@ -34,7 +33,6 @@ import (
func init() {
RegisterResource("svc", func() Res { return &SvcRes{} })
gob.Register(&SvcRes{})
}
// SvcRes is a service resource for systemd units.
@@ -67,7 +65,6 @@ func (obj *SvcRes) Validate() error {
// Init runs some startup code for this resource.
func (obj *SvcRes) Init() error {
obj.BaseRes.Kind = "svc"
return obj.BaseRes.Init() // call base init, b/c we're overriding
}

View File

@@ -18,7 +18,6 @@
package resources
import (
"encoding/gob"
"fmt"
"log"
"time"
@@ -26,7 +25,6 @@ import (
func init() {
RegisterResource("timer", func() Res { return &TimerRes{} })
gob.Register(&TimerRes{})
}
// TimerRes is a timer resource for time based events.
@@ -59,7 +57,6 @@ func (obj *TimerRes) Validate() error {
// Init runs some startup code for this resource.
func (obj *TimerRes) Init() error {
obj.BaseRes.Kind = "timer"
return obj.BaseRes.Init() // call base init, b/c we're overrriding
}

View File

@@ -52,7 +52,7 @@ func B64ToRes(str string) (Res, error) {
}
res, ok := output.(Res)
if !ok {
return nil, fmt.Errorf("Output %v is not a Res", res)
return nil, fmt.Errorf("output `%v` is not a Res", output)
}
return res, nil

View File

@@ -19,7 +19,6 @@
package resources
import (
"encoding/gob"
"fmt"
"log"
"math/rand"
@@ -38,7 +37,6 @@ import (
func init() {
RegisterResource("virt", func() Res { return &VirtRes{} })
gob.Register(&VirtRes{})
}
const (
@@ -192,7 +190,6 @@ func (obj *VirtRes) Init() error {
}
}
obj.wg = &sync.WaitGroup{}
obj.BaseRes.Kind = "virt"
return obj.BaseRes.Init() // call base init, b/c we're overriding
}

View File

@@ -55,19 +55,18 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
if !obj.initialized {
return nil, fmt.Errorf("%s: MyGAPI is not initialized", obj.Name)
}
// FIXME: these are being specified temporarily until it's the default!
metaparams := resources.DefaultMetaParams
var err error
g, err := pgraph.NewGraph(obj.Name)
if err != nil {
return nil, err
}
n0 := &resources.NoopRes{
BaseRes: resources.BaseRes{
Name: "noop1",
MetaParams: metaparams,
},
n0, err := resources.NewResource("noop")
if err != nil {
return nil, err
}
n0.SetName("noop1")
g.AddVertex(n0)
//g, err := config.NewGraphFromConfig(obj.data.Hostname, obj.data.World, obj.data.Noop)

View File

@@ -122,7 +122,7 @@ func (r *Resource) UnmarshalYAML(unmarshal func(interface{}) error) error {
// Decode is the second stage for unmarshaling of resources (knowing their
// kind).
func (r *Resource) Decode(kind string) (err error) {
r.resource, err = resources.NewEmptyNamedResource(kind)
r.resource, err = resources.NewResource(kind)
if err != nil {
return err
}
@@ -134,7 +134,6 @@ func (r *Resource) Decode(kind string) (err error) {
// Set resource name, meta and kind
r.resource.SetName(r.Name)
r.resource.SetKind(strings.ToLower(kind))
meta := r.resource.Meta()
*meta = r.Meta
return
@@ -199,7 +198,6 @@ func (c *GraphConfig) NewGraphFromConfig(hostname string, world resources.World,
} else if !noop { // do not export any resources if noop
// store for addition to backend storage...
res.SetName(res.GetName()[2:]) // slice off @@
res.SetKind(kind) // cheap init
resourceList = append(resourceList, res)
}
}