Repository: doitintl/kube-no-trouble (go)

Issue:
Use go embed instead of static
Want to avoid using generate and load the rules into the go binary at either build time or run time.

See https://tip.golang.org/pkg/embed/

over static

Pull request:
ref: Replace statik with Go embed
Closes #107 
Signed-off-by: david-doit-intl <david@doit-intl.com>

Patch:
diff --git a/Dockerfile b/Dockerfile
index b24ee0a..6789d2a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,6 +16,8 @@ RUN make all
 
 FROM scratch
 
+USER 1000
+
 COPY --from=builder /src/bin/kubent-linux-amd64 /app/kubent
 
 WORKDIR /app
diff --git a/Makefile b/Makefile
index 685c3d2..57ca484 100644
--- a/Makefile
+++ b/Makefile
@@ -27,7 +27,6 @@ CGO_ENABLED ?= 0
 GOOS ?= $(shell go env GOOS)
 GOARCH ?= $(shell go env GOARCH)
 
-GENERATE_DIR ?= generated
 BIN_DIR ?= bin
 CMD_DIR ?= cmd
 RELEASE_DIR ?= release-artifacts
@@ -44,7 +43,6 @@ SRC ?= $(shell find . -iname '*.go')
 
 GOCMD ?= go
 GOBUILD ?= $(GOCMD) build
-GOGENERATE ?= $(GOCMD) generate
 UPXCMD ?= upx
 
 REQ_BINS = upx go opa
@@ -66,21 +64,12 @@ help:
 build: $(BINS)
 .PHONY: build
 
-$(BIN_DIR)/%-$(BIN_ARCH): generated/* $(SRC) go.mod go.sum
+$(BIN_DIR)/%-$(BIN_ARCH): $(SRC) go.mod go.sum
 	mkdir -p $(BIN_DIR)
 	$(GOBUILD) -ldflags="-s -w -X main.version=$(GIT_REF) -X main.gitSha=$(GIT_SHA)" \
 	-o "$@" \
 	"./$(CMD_DIR)/$(*)"
 
-## Go generate
-generate: generated/*
-	mkdir -p $(GENERATE_DIR)
-.PHONY: generate
-
-generated/*: rules/*
-	$(GOGENERATE)
-	go fmt "./generated/..."
-
 ## Pack binaries with upx
 pack: $(PACKED_BINS)
 .PHONY: pack
@@ -99,12 +88,12 @@ $(RELEASE_DIR)/%-$(RELEASE_SUFFIX): $(PACKED_DIR)/%-$(BIN_ARCH)
 	$(TAR) -cvz --transform 's,$(PACKED_DIR)/$(*)-$(BIN_ARCH),$(*),gi' -f "$@" "$<"
 
 ## Run Go tests
-test: generate test-fmt test-git
+test: test-fmt test-git
 	go test -v -coverprofile fmtcoverage.html ./...
 .PHONY: test
 
 ## Run go and opt fmt checks
-test-fmt: generate
+test-fmt:
 	test -z "$$(opa fmt -l rules/*)"
 	test -z "$$(go fmt ./...)"
 .PHONY: test-fmt
@@ -116,7 +105,6 @@ test-git:
 
 ## Clean build artifacts
 clean:
-	rm -rf $(GENERATE_DIR)
 	rm -rf $(BIN_DIR)
 .PHONY: clean
 
diff --git a/README.md b/README.md
index 6fccccf..d67c8e0 100644
--- a/README.md
+++ b/README.md
@@ -127,10 +127,6 @@ The simplest way to build `kubent` is:
 # Clone the repository
 git clone https://github.com/doitintl/kube-no-trouble.git
 cd kube-no-trouble/
-# We require statik for generating static embedded files
-go get github.com/rakyll/statik
-# Generate
-go generate
 # Build
 go build -o bin/kubent cmd/kubent/main.go
 ```
diff --git a/cmd/kubent/main.go b/cmd/kubent/main.go
index 390ee46..772c6de 100644
--- a/cmd/kubent/main.go
+++ b/cmd/kubent/main.go
@@ -8,6 +8,7 @@ import (
 	"github.com/doitintl/kube-no-trouble/pkg/config"
 	"github.com/doitintl/kube-no-trouble/pkg/judge"
 	"github.com/doitintl/kube-no-trouble/pkg/printer"
+	"github.com/doitintl/kube-no-trouble/pkg/rules"
 
 	"github.com/rs/zerolog"
 	"github.com/rs/zerolog/log"
@@ -96,7 +97,12 @@ func main() {
 	initCollectors := initCollectors(config)
 	collectors := getCollectors(initCollectors)
 
-	judge, err := judge.NewRegoJudge(&judge.RegoOpts{})
+	loadedRules, err := rules.FetchRegoRules()
+	if err != nil {
+		log.Fatal().Err(err).Str("name", "Rules").Msg("Failed to load rules")
+	}
+
+	judge, err := judge.NewRegoJudge(&judge.RegoOpts{}, loadedRules)
 	if err != nil {
 		log.Fatal().Err(err).Str("name", "Rego").Msg("Failed to initialize decision engine")
 	}
diff --git a/generated.go b/generated.go
deleted file mode 100644
index 161c303..0000000
--- a/generated.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package dummy
-
-//go:generate statik -src=./rules -dest=./generated -f
diff --git a/generated/statik/statik.go b/generated/statik/statik.go
deleted file mode 100644
index 0970e6a..0000000
--- a/generated/statik/statik.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// Code generated by statik. DO NOT EDIT.
-
-package statik
-
-import (
-	"github.com/rakyll/statik/fs"
-)
-
-func init() {
-	data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\xc9\x84'R\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00	\x00deprecated-1-16.regoUT\x05\x00\x01\xfa8\xf7_\xacTMk\xdc0\x10=[\xbfbP/Y0n\xddCH\x97n!\x90K)\x84\xd0\x85\xf6\xb0,fb\x8f\x13aY\x12\x96\xect	\xfb\xdf\xcb\xf8c\xe3$\x1bZ\xc2\xde\xec\x997\xf3\xde<i\xe40\xaf\xf0\x8e\xa0 \xd7P\x8e\x81\x8a4=\x17\xa2Fe6\x0d\x85\xb61[x\x14QC\xde\xb6MN\xb0\\\x812\xae\x0d\x9bl+\"t\x8a\x03O\xb5\xd9\x84;\x9b>\x16\\\xcbm\x18\xf8(\xa2H^cMr	\x13 \xa9)`\x81\x01\x13\x835\xc5\"\x8a>\x00C\xbc\xc3\x9c\xa0\xb0\xe4\xc1\xd8\x00\xf7\xd8\x11\x04\x0b\xb7,\xb5T\x86\nP\x06r\xf4\x04\xb6\x04ms\xd4P\xa3Q%\xf9\xe0'\x9a\xbe\x87\\\xc2\x1d\x85\xac\xa0\x12[\x1d\xce^\xf1\xc6 \xcd\x01\x1b\x83\xfc\xda\x9a\x91\xe1\x9b\\\xb0\x1e\xf9C\x99b\xae\xb8R\xa6\xe8\x13\x97N\xfd\xa2\xc6+k\xe4\x12\xd0\xa9\xc4\xea!\xf1\x93\x9c\xc6\x9c~\xabp?f\x0c=\x0c\x99V\xd3\x9a\x82\\\x82\xbc:\xd8\x06\x977\xdf=4T\xdbn\x98+M\xd2s\xd9\xe3\xd7\xca\xf4#p\x0f\xcf\xdf\xb1\x88\xf6b/\xc4Q\xd3\x17\xb0b$\x1b\xfd\xfal\xd0\xa9\xb3f\x10\x0fM\x82\x07\xed\x8b\x17\xed\x187\xa0\xd0\xa9\xac\x1bAO\x9d\x9fC=\x8c\xe7zEN\xdb]M\x86\x87\xe3H$\xadf\xdf6\x92\xfe\x042\xdc\xc5\x7f\xec\xd2[\n\x98\xb2\xd1\xe8\xdc[\xff\x9f\xe56\xee;\x18z`\xab\xc6\x94\x1c\x82~\xf4D\xa6\xc9\x97>\xb4\xef\xad\xba\xa6\xf0`\x9b\xea\xc6j\x95\xef\xfeG\xc3s\x0e3\x94+s\x97T\x17>Q\xf6(\xe1\xc5\x8c\xf0\xc6\x16k\xca\xdbF\x85\xdd{I]_\xf7\xe4\xc2K\xba\xf4\xd3\x8c\xef\n\xa9\xb6f\xb8=\xff\xe49\x81\xa1\xeb\x80\x81\xcaV\x1fc<\xf5\xe1\xf1\xc6\xa8\x1c\xdf5\xdc	\x04\xec\xc5\xab{\xbd\xe1\x1d\xd8\xf2Jo\xb2-\xacV\xf3m8\xacW\x7f\xf3\x07\xa1\xb3t?\xd1@~\xbc\xe9\xf4\x1aLZ\x8e\xa3\x9e\xed\xfb\xfc\x0d\xebP\xc7P\xd1.\x86\x8c\xf7\xb2C\xbd\xa9h\xb7}\x0bT\xa2\xd6\xb7\x98W\x8c\x9d\xbeY9\xbf\xab\x87\xda\xbd\xf8\x1b\x00\x00\xff\xffPK\x07\x08q\xe1s\xb6\x1f\x02\x00\x00\x0b\x06\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xc9\x84'R\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00	\x00deprecated-1-22.regoUT\x05\x00\x01\xfa8\xf7_t\x93Q\x8b\x9b@\x10\xc7\x9f\xddO1l_. \xb6\x1e}(\xd2\x14\x0e\xfar\x14J\xe9A\xfb \"\x13\x9d\xe4\x16\xd7]\xd9]M\x8f\x90\xef^F\xa3\xe75\xe9\xdb\xb0\xf3\x9b\x99\xff\xfcG;\xac\x1a<\x10\xd4\xd49\xaa0P\x9d\xde\x7f\x10\xa2EerG\xa1w\xa6\x80\x93\x88\x1cy\xdb\xbb\x8a \xdb\x822]\x1f\xf2\xb2\x10\x11v\x8a\x1f^k\xcb\x99\xbb\x9b\x83\x0d\xd7r\x1b\x06O\"\x8a\xe4wlIf0\x03IK\x01k\x0c\x98\x18l)\x16Q\xf4\x0e\x18\xf1\x1dV\x04\xb5%\x0f\xc6\x06x\xc6\x81 X\xd8\xb1\xd4\xbd2T\x832P\xa1'\xb0{\xd0\xb6B\x0d-\x1a\xb5'\x1f\xfc<f\xec!38P(k\xdac\xaf\xc3\xdd\xd5\xdc\x18\xa4Y\xd8\x18\xe4\xe7\xde\\&|\x91\x1b\xd6#\xbf)S\xaf\x157\xca\xd4c\xe2\xa1S\xbf\xc8ye\x8d\xcc\x00;\x95X=%~R\xa7\xb1\xa2\xdf*<_2\x86\x8eS\xa6\xd7\xf4DAf \xbf.\xb6\xc1\xc3\x8fG\x0f\x8eZ;L{\xa5\xc9\xfd\xbd\x1c\xf9'e\xc6\x15\xb8\x87\xe78\x16\xd1Y\x9c\x85\xb8i\xfa\x06\xb6L\xb2\xd1\xd7\xb7\xc1N\xdd\xb9I<\xb8\x04\x17\xed\x9b\x7f\xda17Q\xd8\xa9r\xb8@\xaf\x9d\xdf\xa2\x1e\xb6p\x92\x8f\xe6\xe0\xc8{\x99M7\xb6\x9a\x0d\xcb%\xfd	d\xb8\xdc\xbf\x1f\xd2\x1d\x05Le1\xaee\xe8\xc8\x16\x18\nG\xeb\x1ae\x0eI\xf3\xc9'\xca.\xd8H\xf9\xcb\xf22M\xd2\x8f\xfct>\x8b\xab\xf99k-\xd8\xfa\xbc,`\xbb]\xab\x16\x8b\x0f+Y\xab\xfcJ\xcb\xed\xae\xf3\xd9f%\xb7\xa97\x87Y\x7fl\x03\xea\x18\x1az\x89\xa1d\x03\x07\xd4yC/\xc5\xff\xa0=j\xbd\xc3\xaaav\x8eY9\xff\x00K\xedY\xfc\x0d\x00\x00\xff\xffPK\x07\x08\xe9\x8a\x04\xd1\xc3\x01\x00\x00\xb4\x03\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xc9\x84'Rq\xe1s\xb6\x1f\x02\x00\x00\x0b\x06\x00\x00\x14\x00	\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00deprecated-1-16.regoUT\x05\x00\x01\xfa8\xf7_PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xc9\x84'R\xe9\x8a\x04\xd1\xc3\x01\x00\x00\xb4\x03\x00\x00\x14\x00	\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81j\x02\x00\x00deprecated-1-22.regoUT\x05\x00\x01\xfa8\xf7_PK\x05\x06\x00\x00\x00\x00\x02\x00\x02\x00\x96\x00\x00\x00x\x04\x00\x00\x00\x00"
-	fs.Register(data)
-}
diff --git a/go.mod b/go.mod
index 20d2911..7f4cdc4 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,6 @@ require (
 	github.com/jmoiron/sqlx v1.2.0 // indirect
 	github.com/lib/pq v1.3.0 // indirect
 	github.com/open-policy-agent/opa v0.26.0
-	github.com/rakyll/statik v0.1.7
 	github.com/rs/zerolog v1.20.0
 	github.com/rubenv/sql-migrate v0.0.0-20200402132117-435005d389bc // indirect
 	github.com/spf13/pflag v1.0.5
diff --git a/go.sum b/go.sum
index 3b3d1e5..b0f5efa 100644
--- a/go.sum
+++ b/go.sum
@@ -528,8 +528,6 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=
-github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
diff --git a/pkg/judge/rego.go b/pkg/judge/rego.go
index 727db79..3fbbb7a 100644
--- a/pkg/judge/rego.go
+++ b/pkg/judge/rego.go
@@ -2,14 +2,10 @@ package judge
 
 import (
 	"context"
-	"io/ioutil"
-	"os"
 
+	"github.com/doitintl/kube-no-trouble/pkg/rules"
 	"github.com/open-policy-agent/opa/rego"
-	"github.com/rakyll/statik/fs"
 	"github.com/rs/zerolog/log"
-
-	_ "github.com/doitintl/kube-no-trouble/generated/statik"
 )
 
 type RegoJudge struct {
@@ -19,34 +15,17 @@ type RegoJudge struct {
 type RegoOpts struct {
 }
 
-func NewRegoJudge(opts *RegoOpts) (*RegoJudge, error) {
+func NewRegoJudge(opts *RegoOpts, rules []rules.Rule) (*RegoJudge, error) {
 	ctx := context.Background()
 
 	r := rego.New(
 		rego.Query("data[_].main"),
 	)
 
-	statikFS, err := fs.New()
-
-	fs.Walk(statikFS, "/",
-		func(path string, info os.FileInfo, err error) error {
-			if !info.IsDir() {
-				if err != nil {
-					return err
-				}
-				f, err := statikFS.Open(path)
-				if err != nil {
-					return err
-				}
-				c, err := ioutil.ReadAll(f)
-				if err != nil {
-					return err
-				}
-				rego.Module(info.Name(), string(c))(r)
-				log.Info().Str("name", info.Name()).Msg("Loaded ruleset")
-			}
-			return nil
-		})
+	for _, info := range rules {
+		rego.Module(info.Name, info.Rule)(r)
+		log.Info().Str("name", info.Name).Msg("Loaded ruleset")
+	}
 
 	pq, err := r.PrepareForEval(ctx)
 	if err != nil {
diff --git a/rules/deprecated-1-16.rego b/pkg/rules/rego/deprecated-1-16.rego
similarity index 100%
rename from rules/deprecated-1-16.rego
rename to pkg/rules/rego/deprecated-1-16.rego
diff --git a/rules/deprecated-1-22.rego b/pkg/rules/rego/deprecated-1-22.rego
similarity index 100%
rename from rules/deprecated-1-22.rego
rename to pkg/rules/rego/deprecated-1-22.rego
diff --git a/pkg/rules/rules.go b/pkg/rules/rules.go
new file mode 100644
index 0000000..05a9fcd
--- /dev/null
+++ b/pkg/rules/rules.go
@@ -0,0 +1,35 @@
+package rules
+
+import (
+	"embed"
+	"path"
+)
+
+//go:embed rego
+var local embed.FS
+
+type Rule struct {
+	Name string
+	Rule string
+}
+
+func FetchRegoRules() ([]Rule, error) {
+	fis, err := local.ReadDir("rego")
+	if err != nil {
+		return nil, err
+	}
+
+	rules := []Rule{}
+	for _, info := range fis {
+		data, err := local.ReadFile(path.Join("rego", info.Name()))
+		if err != nil {
+			return nil, err
+		}
+		rules = append(rules, Rule{
+			Name: info.Name(),
+			Rule: string(data),
+		})
+	}
+
+	return rules, nil
+}
diff --git a/scripts/alpine-setup.sh b/scripts/alpine-setup.sh
index 76e70d1..c772b45 100755
--- a/scripts/alpine-setup.sh
+++ b/scripts/alpine-setup.sh
@@ -21,5 +21,4 @@ wget -qO- "https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UP
 wget -q -O "/usr/local/bin/opa" "https://github.com/open-policy-agent/opa/releases/download/v${OPA_VERSION}/opa_linux_amd64"
 chmod +x "/usr/local/bin/opa"
 
-go get github.com/rakyll/statik
 go get github.com/paultyng/changelog-gen


Test patch:
diff --git a/pkg/judge/rego_test.go b/pkg/judge/rego_test.go
index a0919fb..346dae6 100644
--- a/pkg/judge/rego_test.go
+++ b/pkg/judge/rego_test.go
@@ -1,13 +1,14 @@
 package judge
 
 import (
+	"github.com/doitintl/kube-no-trouble/pkg/rules"
 	"github.com/ghodss/yaml"
 	"io/ioutil"
 	"testing"
 )
 
 func TestNewRegoJudge(t *testing.T) {
-	_, err := NewRegoJudge(&RegoOpts{})
+	_, err := NewRegoJudge(&RegoOpts{}, []rules.Rule{})
 	if err != nil {
 		t.Errorf("failed to create judge instance: %s", err)
 	}
@@ -16,7 +17,7 @@ func TestNewRegoJudge(t *testing.T) {
 func TestEvalEmpty(t *testing.T) {
 	inputs := []map[string]interface{}{}
 
-	judge, err := NewRegoJudge(&RegoOpts{})
+	judge, err := NewRegoJudge(&RegoOpts{}, []rules.Rule{})
 	if err != nil {
 		t.Errorf("failed to create judge instance: %s", err)
 	}
@@ -64,7 +65,12 @@ func TestEvalRules(t *testing.T) {
 				manifests = append(manifests, manifest)
 			}
 
-			judge, err := NewRegoJudge(&RegoOpts{})
+			loadedRules, err := rules.FetchRegoRules()
+			if err != nil {
+				t.Errorf("Failed to load rules")
+			}
+
+			judge, err := NewRegoJudge(&RegoOpts{}, loadedRules)
 			if err != nil {
 				t.Errorf("failed to create judge instance: %s", err)
 			}
diff --git a/pkg/rules/rules_test.go b/pkg/rules/rules_test.go
new file mode 100644
index 0000000..166cbe8
--- /dev/null
+++ b/pkg/rules/rules_test.go
@@ -0,0 +1,28 @@
+package rules
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+func TestFetchRules(t *testing.T) {
+	var expected []string
+	root := "rego/"
+	err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+		if info.Name() != "rego" {
+			expected = append(expected, info.Name())
+		}
+		return nil
+	})
+
+	rules, err := FetchRegoRules()
+	if err != nil {
+		t.Errorf("Failed to load rules with: %s", err)
+	}
+	for i, rule := range rules {
+		if rule.Name != expected[i] {
+			t.Errorf("expected to get %s finding, instead got: %s", expected[i], rule.Name)
+		}
+	}
+}