Skip to content

Commit

Permalink
Adds Matching resource (#226)
Browse files Browse the repository at this point in the history
* Adds Matching resource

* Fix merge conflicts for matching resource

* Add example for matching resources that utilizes Templates
  • Loading branch information
OmarDarwish authored and aelsabbahy committed Jul 6, 2017
1 parent 291b441 commit df62ee0
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 1 deletion.
58 changes: 58 additions & 0 deletions docs/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* [interface](#interface)
* [kernel-param](#kernel-param)
* [mount](#mount)
* [matching](#matching)
* [package](#package)
* [port](#port)
* [process](#process)
Expand Down Expand Up @@ -614,6 +615,63 @@ mount:
filesystem: xfs
```

### matching
Validates specified content against a matcher. Best used with [Templates](#templates).

#### With [Templates](#templates):
Let's say we have a `data.json` file that gets generated as part of some testing pipeline:

```json
{
"instance_count": 14,
"failures": 3,
"status": "FAIL"
}
```

This could then be passed into goss: `goss --vars data.json validate`

And then validated against:

```yaml
matching:
check_instance_count: # Make sure there is at least one instance
content: {{ .Vars.instance_count }}
matches:
gt: 0
check_failure_count_from_all_instance: # expect no failures
content: {{ .Vars.failures }}
matches: 0
check_status:
content: {{ .Vars.status }}
matches:
- not: FAIL
```

#### Without [Templates](#templates):
```yaml
matching:
has_substr: # friendly test name
content: some string
matches:
match-regexp: some str
has_2:
content:
- 2
matches:
contain-element: 2
has_foo_bar_and_baz:
content:
foo: bar
baz: bing
matches:
and:
- have-key-with-value:
foo: bar
- have-key: baz
```

### package
Validates the state of a package
Expand Down
19 changes: 18 additions & 1 deletion goss_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type GossConfig struct {
Mounts resource.MountMap `json:"mount,omitempty" yaml:"mount,omitempty"`
Interfaces resource.InterfaceMap `json:"interface,omitempty" yaml:"interface,omitempty"`
HTTPs resource.HTTPMap `json:"http,omitempty" yaml:"http,omitempty"`
Matchings resource.MatchingMap `json:"matching,omitempty" yaml:"matching,omitempty"`
}

func NewGossConfig() *GossConfig {
Expand All @@ -47,7 +48,23 @@ func NewGossConfig() *GossConfig {
func (c *GossConfig) Resources() []resource.Resource {
var tests []resource.Resource

gm := genericConcatMaps(c.Commands, c.HTTPs, c.Addrs, c.DNS, c.Packages, c.Services, c.Files, c.Processes, c.Users, c.Groups, c.Ports, c.KernelParams, c.Mounts, c.Interfaces)
gm := genericConcatMaps(c.Commands,
c.HTTPs,
c.Addrs,
c.DNS,
c.Packages,
c.Services,
c.Files,
c.Processes,
c.Users,
c.Groups,
c.Ports,
c.KernelParams,
c.Mounts,
c.Interfaces,
c.Matchings,
)

for _, m := range gm {
for _, t := range m {
// FIXME: Can this be moved to a safer compile-time check?
Expand Down
107 changes: 107 additions & 0 deletions resource/matching.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package resource

import (
"encoding/json"
"fmt"
"reflect"
"strings"

"github.com/aelsabbahy/goss/system"
"github.com/aelsabbahy/goss/util"
)

type Matching struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
Content interface{} `json:"content,omitempty" yaml:"content,omitempty"`
Id string `json:"-" yaml:"-"`
Matches matcher `json:"matches" yaml:"matches"`
}

type MatchingMap map[string]*Matching

func (a *Matching) ID() string { return a.Id }
func (a *Matching) SetID(id string) { a.Id = id }

// FIXME: Can this be refactored?
func (r *Matching) GetTitle() string { return r.Title }
func (r *Matching) GetMeta() meta { return r.Meta }

func (a *Matching) Validate(sys system.System) []TestResult {
skip := false

// ValidateValue expects a function
stub := func() (interface{}, error) {
return a.Content, nil
}

var results []TestResult
results = append(results, ValidateValue(a, "matches", a.Matches, stub, skip))
return results
}

func (ret *MatchingMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}

// Validate configuration
zero := Matching{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}

var tmp map[string]*Matching
if err := unmarshal(&tmp); err != nil {
return err
}

typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}

*ret = tmp
return nil
}

func (ret *MatchingMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := Matching{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}

var tmp map[string]*Matching
if err := unmarshal(&tmp); err != nil {
return err
}

typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}

*ret = tmp
return nil
}

0 comments on commit df62ee0

Please sign in to comment.