Skip to content

Commit

Permalink
Add policylib Go package
Browse files Browse the repository at this point in the history
  • Loading branch information
frncmx committed Dec 3, 2024
1 parent 3d72185 commit d8beba6
Show file tree
Hide file tree
Showing 14 changed files with 289 additions and 0 deletions.
3 changes: 3 additions & 0 deletions export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package policylib

var EParse = parse
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/spacelift-io/spacelift-policies-example-library

go 1.23.3

require (
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
120 changes: 120 additions & 0 deletions templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package policylib

import (
"embed"
"io"
"io/fs"
"strings"

"github.com/pkg/errors"
"gopkg.in/yaml.v3"
)

//go:embed examples
var embeddedTemplates embed.FS

func Templates() []Template {
results, err := parse(embeddedTemplates)
if err != nil {
panic("policylib: " + err.Error())
}
return results
}

type Template struct {
Name string
Type string
Description string
Labels []string
Body string
}

func parse(f fs.FS) ([]Template, error) {
var result []Template
err := fs.WalkDir(f, ".", func(path string, _ fs.DirEntry, err error) error {
if err != nil {
return err
}

const yml = ".yml"
if !strings.HasSuffix(path, yml) {
return nil
}

meta, err := f.Open(path)
if err != nil {
return err
}
defer func() { _ = meta.Close() }()

body, err := f.Open(strings.TrimSuffix(path, yml) + ".rego")
if err != nil {
return err
}
defer func() { _ = body.Close() }()

template, err := newTemplateFrom(meta, body)
result = append(result, template)
return errors.Wrap(err, "parse "+path)
})
return result, errors.Wrap(err, "parse templates")
}

func newTemplateFrom(metaData, policy io.Reader) (Template, error) {
var meta struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Type string `yaml:"type"`
Labels []string `yaml:"labels"`
}

err := yaml.NewDecoder(metaData).Decode(&meta)
if err != nil {
return Template{}, errors.Wrap(err, "decode meta data")
}

body, err := io.ReadAll(policy)
if err != nil {
return Template{}, errors.Wrap(err, "read template body")
}

result := Template{
Name: meta.Name,
Type: meta.Type,
Description: meta.Description,
Labels: meta.Labels,
Body: string(body),
}

return result, result.Validate()
}

func (t Template) Validate() error {
if t.Name == "" {
return errors.New("validate: name cannot be empty")
}
if _, ok := acceptedPolicyTypes[t.Type]; !ok {
return errors.Errorf("validate: unknown policy type %q", t.Type)
}
if t.Body == "" {
return errors.New("validate: body cannot be empty")
}
return nil
}

var acceptedPolicyTypes = map[string]struct{}{
"unknown": {},
"login": {},
"access": {},
"stack_access": {},
"git_push": {},
"push": {},
"initialization": {},
"plan": {},
"terraform_plan": {},
"task": {},
"task_run": {},
"trigger": {},
"approval": {},
"notification": {},
}
116 changes: 116 additions & 0 deletions templates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package policylib_test

import (
"io/fs"
"os"
"testing"
"testing/fstest"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

policylib "github.com/spacelift-io/spacelift-policies-example-library"
)

func TestTemplates(t *testing.T) {
require.NotPanics(t, func() {
templates := policylib.Templates()
require.Greater(t, len(templates), 1, "should have templates")
}, "embedded templates should not panic")
}

func TestTemplates_Parsing(t *testing.T) {
tests := []struct {
name string
fs fs.FS
wantErr bool
wantErrContains string
wantTemplates []policylib.Template
}{
{
name: "empty file system",
fs: fstest.MapFS{},
wantErr: false,
wantTemplates: nil,
},
{
name: "no templates",
fs: fstest.MapFS{
"noise.txt": {},
},
wantErr: false,
wantTemplates: nil,
},
{
name: "missing metadata",
fs: fstest.MapFS{
"foo.rego": {},
},
wantErr: false,
wantTemplates: nil,
},
{
name: "empty metadata",
fs: fstest.MapFS{
"foo.rego": {},
"foo.yml": {},
},
wantErr: true,
wantErrContains: "EOF",
},
{
name: "minimal",
fs: os.DirFS("./testdata/minimal"),
wantErr: false,
wantTemplates: []policylib.Template{
{
Name: "foo",
Type: "push",
Description: "",
Labels: nil,
Body: "package foo\n",
},
},
},
{
name: "full",
fs: os.DirFS("./testdata/full"),
wantErr: false,
wantTemplates: []policylib.Template{
{
Name: "foo",
Type: "push",
Description: "something\n",
Labels: []string{"foo", "test"},
Body: "package foo\n",
},
},
},
{
name: "invalid policy type",
fs: os.DirFS("./testdata/invalid-policy-type"),
wantErr: true,
wantErrContains: "type",
},
{
name: "missing name",
fs: os.DirFS("./testdata/missing-name"),
wantErr: true,
wantErrContains: "name",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

templates, err := policylib.EParse(tt.fs)

if tt.wantErr {
assert.ErrorContains(t, err, tt.wantErrContains, "error should contain expected text")
} else {
assert.NoError(t, err, "no error is expected")
assert.Equal(t, tt.wantTemplates, templates, "templates should match")
}
})
}
}
1 change: 1 addition & 0 deletions testdata/full/foo.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package foo
9 changes: 9 additions & 0 deletions testdata/full/foo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
name: foo
type: push
description: |
something
labels:
- foo
- test

1 change: 1 addition & 0 deletions testdata/invalid-policy-type/foo.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package foo
3 changes: 3 additions & 0 deletions testdata/invalid-policy-type/foo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
name: foo
type: bar
1 change: 1 addition & 0 deletions testdata/minimal/foo.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package foo
3 changes: 3 additions & 0 deletions testdata/minimal/foo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
name: foo
type: push
1 change: 1 addition & 0 deletions testdata/missing-name/foo.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package foo
2 changes: 2 additions & 0 deletions testdata/missing-name/foo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
type: push
3 changes: 3 additions & 0 deletions testdata/missing-template/foo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
name: foo
type: push

0 comments on commit d8beba6

Please sign in to comment.