resources: Officially add Validate method

This officially adds the Validate method to the resource API, and also
cleans up the ordering in existing resources.
This commit is contained in:
James Shubin
2017-01-09 05:05:16 -05:00
parent 668ec8a248
commit 56efef69ba
13 changed files with 120 additions and 115 deletions

View File

@@ -31,11 +31,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
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) * [Default - Get an empty resource with defaults](#default)
* [Validate - Validate the values of a resource struct](#validate)
* [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)
* [Validate - Validate the values of a resource struct](#validate)
* [GetUIDs - Returns the list of resource UID's](#getuids) * [GetUIDs - Returns the list of resource UID's](#getuids)
* [AutoEdges - Returns the autoedge interface matcher](#autoedges) * [AutoEdges - Returns the autoedge interface matcher](#autoedges)
* [CollectPattern - Currently a stub, API will change](#collectpattern) * [CollectPattern - Currently a stub, API will change](#collectpattern)
@@ -94,6 +94,17 @@ func (obj *FooRes) Default() Res {
} }
``` ```
###Validate
```golang
Validate() error
```
This method is used to validate if the populated resource struct is a valid
representation of the resource kind. If it does not conform to the resource
specifications, it should generate an error. If you notice that this method is
quite large, it might be an indication that you should reconsider the parameter
list and interface to this resource. This method is called _before_ `Init`.
###Init ###Init
```golang ```golang
Init() error Init() error
@@ -116,6 +127,12 @@ func (obj *FooRes) Init() error {
} }
``` ```
This method is always called after `Validate` has run successfully, with the
exception that we can't prevent a malicious or buggy `libmgmt` user to not run
this. In other words, you should expect `Validate` to have run first, but you
shouldn't allow `Init` to dangerously `rm -rf /$the_world` if your code only
checks `$the_world` in `Validate`. Remember to always program safely!
###CheckApply ###CheckApply
```golang ```golang
CheckApply(apply bool) (checkOK bool, err error) CheckApply(apply bool) (checkOK bool, err error)
@@ -367,17 +384,6 @@ func (obj *FooRes) Compare(res Res) bool {
} }
``` ```
###Validate
```golang
Validate() error
```
This method is used to validate if the populated resource struct is a valid
representation of the resource kind. If it does not conform to the resource
specifications, it should generate an error. If you notice that this method is
quite large, it might be an indication that you might want to reconsider the
parameter list and interface to this resource.
###GetUIDs ###GetUIDs
```golang ```golang
GetUIDs() []ResUID GetUIDs() []ResUID

View File

@@ -550,6 +550,9 @@ func (g *Graph) GraphSync(oldGraph *Graph) (*Graph, error) {
vertex := oldGraph.GetVertexMatch(res) vertex := oldGraph.GetVertexMatch(res)
if vertex == nil { // no match found if vertex == nil { // no match found
if err := res.Validate(); err != nil {
return nil, errwrap.Wrapf(err, "could not Validate() resource")
}
if err := res.Init(); err != nil { if err := res.Init(); err != nil {
return nil, errwrap.Wrapf(err, "could not Init() resource") return nil, errwrap.Wrapf(err, "could not Init() resource")
} }

View File

@@ -74,14 +74,7 @@ func (obj *ExecRes) Default() Res {
return &ExecRes{} return &ExecRes{}
} }
// 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
}
// Validate if the params passed in are valid data. // Validate if the params passed in are valid data.
// FIXME: where should this get called ?
func (obj *ExecRes) Validate() error { func (obj *ExecRes) Validate() error {
if obj.Cmd == "" { // this is the only thing that is really required if obj.Cmd == "" { // this is the only thing that is really required
return fmt.Errorf("Command can't be empty!") return fmt.Errorf("Command can't be empty!")
@@ -95,6 +88,12 @@ func (obj *ExecRes) Validate() error {
return nil return nil
} }
// 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
}
// BufioChanScanner wraps the scanner output in a channel. // BufioChanScanner wraps the scanner output in a channel.
func (obj *ExecRes) BufioChanScanner(scanner *bufio.Scanner) (chan string, chan error) { func (obj *ExecRes) BufioChanScanner(scanner *bufio.Scanner) (chan string, chan error) {
ch, errch := make(chan string), make(chan error) ch, errch := make(chan string), make(chan error)

View File

@@ -84,6 +84,32 @@ func (obj *FileRes) Default() Res {
} }
} }
// Validate reports any problems with the struct definition.
func (obj *FileRes) Validate() error {
if obj.Dirname != "" && !strings.HasSuffix(obj.Dirname, "/") {
return fmt.Errorf("Dirname must end with a slash.")
}
if strings.HasPrefix(obj.Basename, "/") {
return fmt.Errorf("Basename must not start with a slash.")
}
if obj.Content != nil && obj.Source != "" {
return fmt.Errorf("Can't specify both Content and Source.")
}
if obj.isDir && obj.Content != nil { // makes no sense
return fmt.Errorf("Can't specify Content when creating a Dir.")
}
// XXX: should this specify that we create an empty directory instead?
//if obj.Source == "" && obj.isDir {
// return fmt.Errorf("Can't specify an empty source when creating a Dir.")
//}
return nil
}
// 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 = ""
@@ -115,32 +141,6 @@ func (obj *FileRes) GetPath() string {
return obj.Dirname + obj.Basename return obj.Dirname + obj.Basename
} }
// Validate reports any problems with the struct definition.
func (obj *FileRes) Validate() error {
if obj.Dirname != "" && !strings.HasSuffix(obj.Dirname, "/") {
return fmt.Errorf("Dirname must end with a slash.")
}
if strings.HasPrefix(obj.Basename, "/") {
return fmt.Errorf("Basename must not start with a slash.")
}
if obj.Content != nil && obj.Source != "" {
return fmt.Errorf("Can't specify both Content and Source.")
}
if obj.isDir && obj.Content != nil { // makes no sense
return fmt.Errorf("Can't specify Content when creating a Dir.")
}
// XXX: should this specify that we create an empty directory instead?
//if obj.Source == "" && obj.isDir {
// return fmt.Errorf("Can't specify an empty source when creating a Dir.")
//}
return nil
}
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
// This one is a file watcher for files and directories. // This one is a file watcher for files and directories.
// Modify with caution, it is probably important to write some test cases first! // Modify with caution, it is probably important to write some test cases first!

View File

@@ -87,6 +87,14 @@ func (obj *HostnameRes) Default() Res {
return &HostnameRes{} return &HostnameRes{}
} }
// Validate if the params passed in are valid data.
func (obj *HostnameRes) Validate() error {
if obj.PrettyHostname == "" && obj.StaticHostname == "" && obj.TransientHostname == "" {
return ErrResourceInsufficientParameters
}
return nil
}
// 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"
@@ -102,15 +110,6 @@ func (obj *HostnameRes) Init() error {
return obj.BaseRes.Init() // call base init, b/c we're overriding return obj.BaseRes.Init() // call base init, b/c we're overriding
} }
// Validate if the params passed in are valid data.
// FIXME: where should this get called ?
func (obj *HostnameRes) Validate() error {
if obj.PrettyHostname == "" && obj.StaticHostname == "" && obj.TransientHostname == "" {
return ErrResourceInsufficientParameters
}
return nil
}
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *HostnameRes) Watch(processChan chan event.Event) error { func (obj *HostnameRes) Watch(processChan chan event.Event) error {
cuid := obj.ConvergerUID() // get the converger uid used to report status cuid := obj.ConvergerUID() // get the converger uid used to report status

View File

@@ -78,13 +78,7 @@ func (obj *MsgRes) Default() Res {
return &MsgRes{} return &MsgRes{}
} }
// Init runs some startup code for this resource. // Validate the params that are passed to MsgRes.
func (obj *MsgRes) Init() error {
obj.BaseRes.kind = "Msg"
return obj.BaseRes.Init() // call base init, b/c we're overrriding
}
// Validate the params that are passed to MsgRes
func (obj *MsgRes) Validate() error { func (obj *MsgRes) Validate() error {
invalidCharacters := regexp.MustCompile("[^a-zA-Z0-9_]") invalidCharacters := regexp.MustCompile("[^a-zA-Z0-9_]")
for field := range obj.Fields { for field := range obj.Fields {
@@ -98,6 +92,12 @@ func (obj *MsgRes) Validate() error {
return nil return nil
} }
// 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
}
// isAllStateOK derives a compound state from all internal cache flags that apply to this resource. // isAllStateOK derives a compound state from all internal cache flags that apply to this resource.
func (obj *MsgRes) isAllStateOK() bool { func (obj *MsgRes) isAllStateOK() bool {
if obj.Journal && !obj.journalStateOK { if obj.Journal && !obj.journalStateOK {

View File

@@ -51,18 +51,17 @@ func (obj *NoopRes) Default() Res {
return &NoopRes{} return &NoopRes{}
} }
// Validate if the params passed in are valid data.
func (obj *NoopRes) Validate() error {
return nil
}
// 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"
return obj.BaseRes.Init() // call base init, b/c we're overriding return obj.BaseRes.Init() // call base init, b/c we're overriding
} }
// Validate if the params passed in are valid data.
// FIXME: where should this get called ?
func (obj *NoopRes) Validate() error {
return nil
}
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *NoopRes) Watch(processChan chan event.Event) error { func (obj *NoopRes) Watch(processChan chan event.Event) error {
cuid := obj.ConvergerUID() // get the converger uid used to report status cuid := obj.ConvergerUID() // get the converger uid used to report status

View File

@@ -57,26 +57,6 @@ 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
func (obj *NspawnRes) Init() error {
var serviceName = fmt.Sprintf(nspawnServiceTmpl, obj.GetName())
obj.svc = &SvcRes{}
obj.svc.Name = serviceName
obj.svc.State = obj.State
if err := obj.svc.Init(); err != nil {
return err
}
obj.BaseRes.kind = "Nspawn"
return obj.BaseRes.Init()
}
// NewNspawnRes is the constructor for this resource // NewNspawnRes is the constructor for this resource
func NewNspawnRes(name string, state string) (*NspawnRes, error) { func NewNspawnRes(name string, state string) (*NspawnRes, error) {
obj := &NspawnRes{ obj := &NspawnRes{
@@ -88,7 +68,14 @@ func NewNspawnRes(name string, state string) (*NspawnRes, error) {
return obj, obj.Init() return obj, obj.Init()
} }
// Validate params // Default returns some sensible defaults for this resource.
func (obj *NspawnRes) Default() Res {
return &NspawnRes{
State: running,
}
}
// Validate if the params passed in are valid data.
func (obj *NspawnRes) Validate() error { func (obj *NspawnRes) Validate() error {
// TODO: validStates should be an enum! // TODO: validStates should be an enum!
validStates := map[string]struct{}{ validStates := map[string]struct{}{
@@ -101,6 +88,19 @@ func (obj *NspawnRes) Validate() error {
return obj.svc.Validate() return obj.svc.Validate()
} }
// Init runs some startup code for this resource.
func (obj *NspawnRes) Init() error {
var serviceName = fmt.Sprintf(nspawnServiceTmpl, obj.GetName())
obj.svc = &SvcRes{}
obj.svc.Name = serviceName
obj.svc.State = obj.State
if err := obj.svc.Init(); err != nil {
return err
}
obj.BaseRes.kind = "Nspawn"
return obj.BaseRes.Init()
}
// Watch for state changes and sends a message to the bus if there is a change // Watch for state changes and sends a message to the bus if there is a change
func (obj *NspawnRes) Watch(processChan chan event.Event) error { func (obj *NspawnRes) Watch(processChan chan event.Event) error {
cuid := obj.ConvergerUID() // get the converger uid used to report status cuid := obj.ConvergerUID() // get the converger uid used to report status

View File

@@ -74,6 +74,11 @@ func (obj *PasswordRes) Default() Res {
} }
} }
// Validate if the params passed in are valid data.
func (obj *PasswordRes) Validate() error {
return nil
}
// 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 {
@@ -88,12 +93,6 @@ func (obj *PasswordRes) Init() error {
return obj.BaseRes.Init() // call base init, b/c we're overriding return obj.BaseRes.Init() // call base init, b/c we're overriding
} }
// Validate if the params passed in are valid data.
// FIXME: where should this get called ?
func (obj *PasswordRes) Validate() error {
return nil
}
func (obj *PasswordRes) read() (string, error) { func (obj *PasswordRes) read() (string, error) {
file, err := os.Open(obj.path) // open a handle to read the file file, err := os.Open(obj.path) // open a handle to read the file
if err != nil { if err != nil {

View File

@@ -67,6 +67,15 @@ func (obj *PkgRes) Default() Res {
} }
} }
// Validate checks if the resource data structure was populated correctly.
func (obj *PkgRes) Validate() error {
if obj.State == "" {
return fmt.Errorf("State cannot be empty!")
}
return nil
}
// 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"
@@ -102,15 +111,6 @@ func (obj *PkgRes) Init() error {
return nil return nil
} }
// Validate checks if the resource data structure was populated correctly.
func (obj *PkgRes) Validate() error {
if obj.State == "" {
return fmt.Errorf("State cannot be empty!")
}
return nil
}
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
// It uses the PackageKit UpdatesChanged signal to watch for changes. // It uses the PackageKit UpdatesChanged signal to watch for changes.
// TODO: https://github.com/hughsie/PackageKit/issues/109 // TODO: https://github.com/hughsie/PackageKit/issues/109

View File

@@ -164,8 +164,8 @@ type Base interface {
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 Default() Res // return a struct with sane defaults as a Res
Validate() error
Init() error Init() error
//Validate() error // TODO: this might one day be added
GetUIDs() []ResUID // most resources only return one GetUIDs() []ResUID // most resources only return one
Watch(chan event.Event) error // send on channel to signal process() events Watch(chan event.Event) error // send on channel to signal process() events
CheckApply(apply bool) (checkOK bool, err error) CheckApply(apply bool) (checkOK bool, err error)

View File

@@ -61,12 +61,6 @@ func (obj *SvcRes) Default() Res {
return &SvcRes{} return &SvcRes{}
} }
// 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
}
// Validate checks if the resource data structure was populated correctly. // Validate checks if the resource data structure was populated correctly.
func (obj *SvcRes) Validate() error { func (obj *SvcRes) Validate() error {
if obj.State != "running" && obj.State != "stopped" && obj.State != "" { if obj.State != "running" && obj.State != "stopped" && obj.State != "" {
@@ -78,6 +72,12 @@ func (obj *SvcRes) Validate() error {
return nil return nil
} }
// 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
}
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *SvcRes) Watch(processChan chan event.Event) error { func (obj *SvcRes) Watch(processChan chan event.Event) error {
cuid := obj.ConvergerUID() // get the converger uid used to report status cuid := obj.ConvergerUID() // get the converger uid used to report status

View File

@@ -60,17 +60,17 @@ func (obj *TimerRes) Default() Res {
return &TimerRes{} return &TimerRes{}
} }
// Validate the params that are passed to TimerRes.
func (obj *TimerRes) Validate() error {
return nil
}
// 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"
return obj.BaseRes.Init() // call base init, b/c we're overrriding return obj.BaseRes.Init() // call base init, b/c we're overrriding
} }
// Validate the params that are passed to TimerRes.
func (obj *TimerRes) Validate() error {
return nil
}
// newTicker creates a new ticker // newTicker creates a new ticker
func (obj *TimerRes) newTicker() *time.Ticker { func (obj *TimerRes) newTicker() *time.Ticker {
return time.NewTicker(time.Duration(obj.Interval) * time.Second) return time.NewTicker(time.Duration(obj.Interval) * time.Second)