Skip to content
This repository has been archived by the owner on Jan 12, 2021. It is now read-only.

Commit

Permalink
Add support for disk_usage to goss (#8)
Browse files Browse the repository at this point in the history
MVP PR that adds support for disk_usage (total bytes, free bytes and percent available).

Testing strategy:
- ci on linux and windows passes
- goss add disk-usage "invalid dir", goss add disk-usage / and goss validate work as expected
  • Loading branch information
stefan-improbable committed Jun 19, 2019
1 parent 7ed2517 commit 6e9588d
Show file tree
Hide file tree
Showing 15 changed files with 431 additions and 15 deletions.
7 changes: 7 additions & 0 deletions add.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ func AddResource(fileName string, gossConfig GossConfig, resourceName, key strin
os.Exit(1)
}
resourcePrint(fileName, res)
case "DiskUsage":
res, err := gossConfig.DiskUsages.AppendSysResource(key, sys, config)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
resourcePrint(fileName, res)
default:
panic("Undefined resource name: " + resourceName)
}
Expand Down
8 changes: 8 additions & 0 deletions cmd/goss/goss.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,14 @@ func main() {
return nil
},
},
{
Name: "disk-usage",
Usage: "add new disk usage",
Action: func(c *cli.Context) error {
goss.AddResources(c.GlobalString("gossfile"), "DiskUsage", c.Args(), c)
return nil
},
},
},
},
}
Expand Down
24 changes: 24 additions & 0 deletions docs/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* [Available tests](#available-tests)
* [addr](#addr)
* [command](#command)
* [disk-usage](#disk-usage)
* [dns](#dns)
* [file](#file)
* [gossfile](#gossfile)
Expand Down Expand Up @@ -108,6 +109,7 @@ This will add a test for a resource. Non existent resources will add a test to e
#### Resource types
* `addr` - can verify if a remote `address:port` is reachable, see [addr](#addr)
* `command` - can run a [command](#command) and validate the exit status and/or output
* `disk-usage` - calculates [disk-usage](#disk-usage) and validates whether there is enough space
* `dns` - resolves a [dns](#dns) name and validates the addresses
* `file` - can validate a [file](#file) existence, permissions, stats (size, etc) and contents
* `goss` - allows you to include the contents of another [gossfile](#gossfile)
Expand Down Expand Up @@ -401,6 +403,7 @@ If you want to keep your tests in separate files, the best way to obtain a singl
* [addr](#addr)
* [command](#command)
* [disk-usage](#disk-usage)
* [dns](#dns)
* [file](#file)
* [gossfile](#gossfile)
Expand Down Expand Up @@ -451,6 +454,27 @@ command:
The `exec` attribute is the command to run; this defaults to the name of
the hash for backwards compatibility
### disk-usage
Validates whether there is enough space remaining on disk.
```yaml
disk_usage:
/:
exists: true
total_bytes:
gt: 1e9
free_bytes:
gt: 500e6
utilization_percent:
lt: 80
```
This test validates that:
- the mount point `/` exists
- the size of `/` mount point is greater than 1GB
- the amout of free space on `/` is more than 500MB
- utilization, calculated as `free_bytes/total_bytes` is at most 80%
### dns
Validates that the provided address is resolvable and the addrs it resolves to.
Expand Down
7 changes: 7 additions & 0 deletions goss_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type GossConfig struct {
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"`
DiskUsages resource.DiskUsageMap `json:"disk_usage,omitempty" yaml:"disk_usage,omitempty"`
}

func NewGossConfig() *GossConfig {
Expand All @@ -43,6 +44,7 @@ func NewGossConfig() *GossConfig {
Interfaces: make(resource.InterfaceMap),
HTTPs: make(resource.HTTPMap),
Matchings: make(resource.MatchingMap),
DiskUsages: make(resource.DiskUsageMap),
}
}

Expand All @@ -64,6 +66,7 @@ func (c *GossConfig) Resources() []resource.Resource {
c.Mounts,
c.Interfaces,
c.Matchings,
c.DiskUsages,
)

for _, m := range gm {
Expand Down Expand Up @@ -162,5 +165,9 @@ func mergeGoss(g1, g2 GossConfig) GossConfig {
g1.Matchings[k] = v
}

for k, v := range g2.DiskUsages {
g1.DiskUsages[k] = v
}

return g1
}
70 changes: 70 additions & 0 deletions resource/disk_usage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package resource

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

type DiskUsage struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
Path string `json:"-" yaml:"-"`
Exists matcher `json:"exists" yaml:"exists"`
TotalBytes matcher `json:"total_bytes" yaml:"total_bytes"`
FreeBytes matcher `json:"free_bytes" yaml:"free_bytes"`
UtilizationPercent matcher `json:"utilization_percent" yaml:"utilization_percent"`
}

func (u *DiskUsage) ID() string { return u.Path }
func (u *DiskUsage) SetID(id string) { u.Path = id }

func (u *DiskUsage) GetTitle() string { return u.Title }
func (u *DiskUsage) GetMeta() meta { return u.Meta }

func (u *DiskUsage) Validate(sys *system.System) []TestResult {
skip := false
sysDU := sys.NewDiskUsage(u.Path, sys, util.Config{})
sysDU.Calculate()

var results []TestResult
results = append(results, ValidateValue(u, "exists", u.Exists, sysDU.Exists, skip))
if shouldSkip(results) {
skip = true
}
if u.TotalBytes != nil {
results = append(results, ValidateValue(u, "total_bytes", u.TotalBytes, sysDU.TotalBytes, skip))
}
if u.FreeBytes != nil {
results = append(results, ValidateValue(u, "free_bytes", u.FreeBytes, sysDU.FreeBytes, skip))
}
if u.UtilizationPercent != nil {
results = append(results, ValidateValue(u, "utilization_percent", u.UtilizationPercent, sysDU.UtilizationPercent, skip))
}
return results
}

func NewDiskUsage(sysDiskUsage system.DiskUsage, config util.Config) (*DiskUsage, error) {
sysDiskUsage.Calculate()
exists, _ := sysDiskUsage.Exists()
u := &DiskUsage{
Path: sysDiskUsage.Path(),
Exists: exists,
}

if !contains(config.IgnoreList, "total_bytes") {
if totalBytes, err := sysDiskUsage.TotalBytes(); err != nil {
u.TotalBytes = totalBytes
}
}
if !contains(config.IgnoreList, "free_bytes") {
if freeBytes, err := sysDiskUsage.FreeBytes(); err != nil {
u.FreeBytes = freeBytes
}
}
if !contains(config.IgnoreList, "utilization_percent") {
if utilizationPercent, err := sysDiskUsage.UtilizationPercent(); err != nil {
u.UtilizationPercent = utilizationPercent
}
}
return u, nil
}
4 changes: 2 additions & 2 deletions resource/gomega.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (

func matcherToGomegaMatcher(matcher interface{}) (types.GomegaMatcher, error) {
switch x := matcher.(type) {
case string, int, bool, float64:
return gomega.Equal(x), nil
case string, int, uint, int64, uint64, bool, float64:
return gomega.BeEquivalentTo(x), nil
case []interface{}:
var matchers []types.GomegaMatcher
for _, valueI := range x {
Expand Down
24 changes: 12 additions & 12 deletions resource/gomega_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ var gomegaTests = []struct {
// Default for simple types
{
in: `"foo"`,
want: gomega.Equal("foo"),
want: gomega.BeEquivalentTo("foo"),
},
{
in: `1`,
want: gomega.Equal(float64(1)),
want: gomega.BeEquivalentTo(float64(1)),
},
{
in: `true`,
want: gomega.Equal(true),
want: gomega.BeEquivalentTo(true),
},
// Default for Array
{
Expand Down Expand Up @@ -73,11 +73,11 @@ var gomegaTests = []struct {
// Collection
{
in: `{"consist-of": ["foo"]}`,
want: gomega.ConsistOf(gomega.Equal("foo")),
want: gomega.ConsistOf(gomega.BeEquivalentTo("foo")),
},
{
in: `{"contain-element": "foo"}`,
want: gomega.ContainElement(gomega.Equal("foo")),
want: gomega.ContainElement(gomega.BeEquivalentTo("foo")),
},
{
in: `{"have-len": 3}`,
Expand All @@ -88,30 +88,30 @@ var gomegaTests = []struct {
// Keys are sorted and then passed to gomega.And so the order
// of the conditions in this `want` is important
want: gomega.And(
gomega.HaveKeyWithValue("bar", gomega.Equal("baz")),
gomega.HaveKeyWithValue("foo", gomega.Equal(1)),
gomega.HaveKeyWithValue("bar", gomega.BeEquivalentTo("baz")),
gomega.HaveKeyWithValue("foo", gomega.BeEquivalentTo(1)),
),
useNegateTester: true,
},
{
in: `{"have-key": "foo"}`,
want: gomega.HaveKey(gomega.Equal("foo")),
want: gomega.HaveKey(gomega.BeEquivalentTo("foo")),
},

// Negation
{
in: `{"not": "foo"}`,
want: gomega.Not(gomega.Equal("foo")),
want: gomega.Not(gomega.BeEquivalentTo("foo")),
},
// Complex logic
{
in: `{"and": ["foo", "foo"]}`,
want: gomega.And(gomega.Equal("foo"), gomega.Equal("foo")),
want: gomega.And(gomega.BeEquivalentTo("foo"), gomega.BeEquivalentTo("foo")),
useNegateTester: true,
},
{
in: `{"and": [{"have-prefix": "foo"}, "foo"]}`,
want: gomega.And(gomega.HavePrefix("foo"), gomega.Equal("foo")),
want: gomega.And(gomega.HavePrefix("foo"), gomega.BeEquivalentTo("foo")),
useNegateTester: true,
},
{
Expand All @@ -120,7 +120,7 @@ var gomegaTests = []struct {
},
{
in: `{"or": ["foo", "foo"]}`,
want: gomega.Or(gomega.Equal("foo"), gomega.Equal("foo")),
want: gomega.Or(gomega.BeEquivalentTo("foo"), gomega.BeEquivalentTo("foo")),
},
{
in: `{"not": {"and": [{"have-prefix": "foo"}]}}`,
Expand Down
100 changes: 100 additions & 0 deletions resource/resource_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -1513,3 +1513,103 @@ func (ret *HTTPMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
*ret = tmp
return nil
}

//go:generate sed -i -e "/^\\/\\/ +build genny/d" resource_list.go
//go:generate goimports -w resource_list.go resource_list.go

type DiskUsageMap map[string]*DiskUsage

func (r DiskUsageMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*DiskUsage, error) {
sysres := sys.NewDiskUsage(sr, sys, config)
res, err := NewDiskUsage(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}

func (r DiskUsageMap) AppendSysResourceIfExists(sr string, sys *system.System) (*DiskUsage, system.DiskUsage, bool) {
sysres := sys.NewDiskUsage(sr, sys, util.Config{})
// FIXME: Do we want to be silent about errors?
res, _ := NewDiskUsage(sysres, util.Config{})
if e, _ := sysres.Exists(); e != true {
return res, sysres, false
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true
}

func (ret *DiskUsageMap) 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 := DiskUsage{}
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]*DiskUsage
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 *DiskUsageMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := DiskUsage{}
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]*DiskUsage
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
}
2 changes: 1 addition & 1 deletion resource/resource_list_genny.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/cheekybits/genny/generic"
)

//go:generate genny -in=$GOFILE -out=resource_list.go gen "ResourceType=Addr,Command,DNS,File,Gossfile,Group,Package,Port,Process,Service,User,KernelParam,Mount,Interface,HTTP"
//go:generate genny -in=$GOFILE -out=resource_list.go gen "ResourceType=Addr,Command,DNS,File,Gossfile,Group,Package,Port,Process,Service,User,KernelParam,Mount,Interface,HTTP,DiskUsage"
//go:generate sed -i -e "/^\\/\\/ +build genny/d" resource_list.go
//go:generate goimports -w resource_list.go resource_list.go

Expand Down
2 changes: 2 additions & 0 deletions resource/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ func ValidateValue(res ResourceRead, property string, expectedValue interface{},
foundValue, err = f()
case func() (int, error):
foundValue, err = f()
case func() (uint64, error):
foundValue, err = f()
case func() ([]string, error):
foundValue, err = f()
case func() (interface{}, error):
Expand Down
Loading

0 comments on commit 6e9588d

Please sign in to comment.