diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f1685b1..5b2b7da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,19 +11,17 @@ jobs: id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - - name: Get dependencies & install + - name: Get dependencies, run go test & install run: | go get -v -t -d ./... if [ -f Gopkg.toml ]; then curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh dep ensure fi - go install -v . - curl -fsSLO https://github.com/open-policy-agent/conftest/releases/download/v0.30.0/conftest_0.30.0_Linux_x86_64.tar.gz - tar -C /usr/local/bin -xzvf conftest_0.30.0_Linux_x86_64.tar.gz - wget -q https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz - tar xf kubeval-linux-amd64.tar.gz - sudo cp kubeval /usr/local/bin + make test SHELL='bash -x' + make install SHELL='bash -x' + export PATH="$(go env GOPATH)/bin:$PATH" + kube-role-gen -help - name: Setup kind uses: engineerd/setup-kind@v0.5.0 with: @@ -31,6 +29,11 @@ jobs: image: kindest/node:v1.23.4@sha256:0e34f0d0fd448aa2f2819cfd74e99fe5793a6e4938b328f657c8e3f81ee0dfb9 - name: Run Kubernetes tests run: | + LATEST_VERSION=$(wget -O - "https://api.github.com/repos/open-policy-agent/conftest/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' | cut -c 2-) + wget "https://github.com/open-policy-agent/conftest/releases/download/v${LATEST_VERSION}/conftest_${LATEST_VERSION}_Linux_x86_64.tar.gz" + tar xzf conftest_${LATEST_VERSION}_Linux_x86_64.tar.gz + sudo mv conftest /usr/local/bin + go install github.com/yannh/kubeconform/cmd/kubeconform@latest kubectl cluster-info export PATH="$(go env GOPATH)/bin:$PATH" - tests/e2e_tests.sh + make e2e diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c9da322 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +SHELL := /bin/bash + +# The name of the executable (default is current directory name) +TARGET := $(shell echo "$${PWD##*/}" ) +MAIN := ./cmd/kube-role-gen +.DEFAULT_GOAL: $(TARGET) + +# These will be provided to the target +VERSION := 0.0.6 +BUILD := `git rev-parse HEAD` + +# Use linker flags to provide version/build settings to the target +LDFLAGS=-ldflags "-X=main.Version=$(VERSION) -X=main.Build=$(BUILD)" + +# go source files, ignore vendor directory +SRC = $(shell find . -type f -name '*.go' -not -path "./vendor/*") + +.PHONY: all build test clean install uninstall fmt simplify check run + +all: check install + +$(TARGET): $(SRC) + @go build $(LDFLAGS) -o $(TARGET) $(MAIN)/main.go + +build: $(TARGET) + @true + +test: + go test ./... + +e2e: test + tests/e2e_tests.sh + +clean: + @rm -f $(TARGET) + +install: + @go install $(LDFLAGS) $(MAIN) + +uninstall: clean + @rm -f $$(which ${TARGET}) + +fmt: + @gofmt -l -w $(SRC) + +simplify: + @gofmt -s -l -w $(SRC) + +check: + @test -z $(shell gofmt -l $(MAIN)/main.go | tee /dev/stderr) || echo "[WARN] Fix formatting issues with 'make fmt'" + @go vet ./... + +run: install + @$(TARGET) \ No newline at end of file diff --git a/README.md b/README.md index f1d716b..dc1ddfe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # kube-role-gen -[![Go](https://github.com/coopernetes/kube-role-gen/workflows/Go/badge.svg)](https://github.com/coopernetes/kube-role-gen/actions?query=workflow%3AGo) +[![build](https://github.com/coopernetes/kube-role-gen/workflows/build/test/badge.svg)](https://github.com/coopernetes/kube-role-gen/actions?query=workflow%3AGo) [![Go Report Card](https://goreportcard.com/badge/github.com/coopernetes/kube-role-gen)](https://goreportcard.com/report/github.com/coopernetes/kube-role-gen) +[![Go Reference](https://pkg.go.dev/badge/github.com/coopernetes/kube-role-gen.svg)](https://pkg.go.dev/github.com/coopernetes/kube-role-gen) _Create a complete Kubernetes RBAC role_ diff --git a/cmd/rolegen/main.go b/cmd/kube-role-gen/main.go similarity index 50% rename from cmd/rolegen/main.go rename to cmd/kube-role-gen/main.go index 20e3c61..a8b7258 100644 --- a/cmd/rolegen/main.go +++ b/cmd/kube-role-gen/main.go @@ -1,17 +1,19 @@ -package rolegen +// The main package for the kube-role-gen executable +package main import ( + "bytes" "flag" "fmt" - "os" - "log" - "bytes" + "github.com/coopernetes/kube-role-gen/pkg/k8s" jsonSer "k8s.io/apimachinery/pkg/runtime/serializer/json" - client "github.com/coopernetes/kube-role-gen/internal/k8s" - gen "github.com/coopernetes/kube-role-gen/pkg/k8s/rbac" + "log" + "os" ) -func Run() { +const Version = "v0.0.6" + +func main() { name := flag.String("name", "foo-clusterrole", "Override the name of the ClusterRole "+ "resource that is generated") verbose := flag.Bool("v", false, "Enable verbose logging.") @@ -20,24 +22,42 @@ func Run() { kubeconfig := flag.String("kubeconfig", "", "absolute path to the kubeconfig file. "+ "If set, this will override the default behavior and "+ "ignore KUBECONFIG environment variable and/or $HOME/.kube/config file location.") + printVersion := flag.Bool("version", false, "Print version info") flag.Parse() - d, err := client.SetupDiscoveryClient(*kubeconfig) + if *printVersion { + fmt.Println(Version) + os.Exit(0) + } + + d, err := k8s.SetupDiscoveryClient(*kubeconfig) + if err != nil { + log.Printf("Error during client setup: %s", err.Error()) + os.Exit(1) + } + _, list, err := d.ServerGroupsAndResources() if err != nil { - fmt.Errorf("Unable to setup client!") + log.Printf("Error during resource discovery: %s", err.Error()) os.Exit(1) } - cr := gen.GetOrderedResources(*d, *name, *verbose) - options := jsonSer.SerializerOptions{ - Yaml: !*json, - Pretty: *jsonPretty, + cr := k8s.CreateGranularRole(list, *name, *verbose) + if err != nil { + log.Printf("Error during role creation, %s", err.Error()) + os.Exit(1) } + options := serializerOptions(*json, *jsonPretty) serializer := jsonSer.NewSerializerWithOptions(jsonSer.DefaultMetaFactory, nil, nil, options) var writer = bytes.NewBufferString("") e := serializer.Encode(cr, writer) if e != nil { - log.Printf("Error encountered during YAML encoding, %s", e.Error()) + log.Printf("Error encountered during encoding, %s", e.Error()) os.Exit(1) } fmt.Println(writer.String()) +} +func serializerOptions(json bool, pretty bool) jsonSer.SerializerOptions { + if json { + return jsonSer.SerializerOptions{Yaml: false, Pretty: pretty} + } + return jsonSer.SerializerOptions{Yaml: true, Pretty: false} } diff --git a/cmd/rolegen/main_test.go b/cmd/rolegen/main_test.go deleted file mode 100644 index ac444bf..0000000 --- a/cmd/rolegen/main_test.go +++ /dev/null @@ -1,8 +0,0 @@ -package rolegen -import ( - "testing" -) - -func TestMain(m *testing.M) { - -} \ No newline at end of file diff --git a/go.mod b/go.mod index e3064ad..f427c0c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/coopernetes/kube-role-gen go 1.19 require ( - github.com/elliotchance/orderedmap v1.5.0 + github.com/elliotchance/orderedmap/v2 v2.2.0 k8s.io/api v0.26.1 k8s.io/apimachinery v0.26.1 k8s.io/client-go v0.26.1 diff --git a/go.sum b/go.sum index e141239..0dfe4c4 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elliotchance/orderedmap v1.5.0 h1:1IsExUsjv5XNBD3ZdC7jkAAqLWOOKdbPTmkHx63OsBg= -github.com/elliotchance/orderedmap v1.5.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys= +github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk= +github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -107,7 +107,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= diff --git a/internal/util/set.go b/internal/util/set.go deleted file mode 100644 index 79f3ba4..0000000 --- a/internal/util/set.go +++ /dev/null @@ -1,11 +0,0 @@ -package set - -func MakeSet(initialMap map[string]bool) []string { - list := make([]string, len(initialMap)) - i := 0 - for k := range initialMap { - list[i] = k - i++ - } - return list -} diff --git a/main.go b/main.go deleted file mode 100644 index 9cdbc7b..0000000 --- a/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - rolegen "github.com/coopernetes/kube-role-gen/cmd/rolegen" -) - -func main() { - rolegen.Run() -} diff --git a/internal/k8s/client.go b/pkg/k8s/discovery.go similarity index 57% rename from internal/k8s/client.go rename to pkg/k8s/discovery.go index 08da31b..9952b54 100644 --- a/internal/k8s/client.go +++ b/pkg/k8s/discovery.go @@ -1,12 +1,20 @@ package k8s import ( - "log" - "os" "k8s.io/client-go/discovery" "k8s.io/client-go/tools/clientcmd" + "log" + "os" ) +// SetupDiscoveryClient will create a new DiscoveryClient. When the kubeconfig arg is unset, the +// client setup uses the usual default behaviour to load either from KUBECONFIG environment variable +// or the default location (usually $HOME/.kube/config). This is provided via client-go package via +// clientcmd.NewDefaultClientConfigLoadingRules. +// +// If kubeconfig string is non-empty, the client will attempt to load the configuration using this value +// by setting the ExplicitPath field on clientcmd.ClientConfigLoadingRules to override the default +// loading rules. func SetupDiscoveryClient(kubeconfig string) (*discovery.DiscoveryClient, error) { loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() if kubeconfig != "" { diff --git a/pkg/k8s/rbac.go b/pkg/k8s/rbac.go new file mode 100644 index 0000000..b68f881 --- /dev/null +++ b/pkg/k8s/rbac.go @@ -0,0 +1,129 @@ +// Package k8s provides functions to create Kubernetes RBAC roles objects based +// on discovered API resources. It also provides utility functions to setup a +// discovery client for a provided kubeconfig and obtain the list of discovered +// resources for use in this package. +package k8s + +import ( + "github.com/elliotchance/orderedmap/v2" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/strings/slices" + "log" + "sort" + "strings" +) + +// CreateGranularRole creates a ClusterRole where each rules entry contains only the specific combination of API group +// and supported verbs for each resource. Resources with matching verbs are grouped together in a single PolicyRule. +// This differs from other implementations such as `kubectl create clusterrole` which will group together resources +// with verbs that are not applicable or supported. +// +// All PolicyRules in the ClusterRole this function returns represents a "matrix" of all resources available on the API +// and contains only the list of the supported verbs that resource handles. +func CreateGranularRole(apiResourceList []*metav1.APIResourceList, name string, verbose bool) *rbacv1.ClusterRole { + oMap := orderedmap.NewOrderedMap[string, map[string][]string]() + for _, resourceList := range apiResourceList { + if verbose { + log.Printf("Group %s contains %d resources", resourceList.GroupVersion, len(resourceList.APIResources)) + } + groupName := extractGroupFromVersion(resourceList.GroupVersion) + if slices.Contains(oMap.Keys(), groupName) { + left, _ := oMap.Get(groupName) + right := convertToVerbMap(resourceList.APIResources, verbose) + oMap.Set(groupName, mergeVerbMaps(left, right)) + } else { + oMap.Set(groupName, convertToVerbMap(resourceList.APIResources, verbose)) + } + } + policyRules := policyRuleByOrderedMap(*oMap) + return &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterRole", + APIVersion: "rbac.authorization.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Rules: policyRules, + } +} + +func extractGroupFromVersion(groupVersion string) string { + if groupVersion == "v1" { + return "" + } + return strings.Split(groupVersion, "/")[0] +} + +func convertToVerbMap(resList []metav1.APIResource, verbose bool) map[string][]string { + verbMap := make(map[string][]string) + for _, res := range resList { + if verbose { + log.Printf("Resource: %s - Verbs: %s", + res.Name, + res.Verbs.String()) + } + verbs := make([]string, len(res.Verbs)) + copy(verbs, res.Verbs) + sort.Strings(verbs) + verbKey := strings.Join(verbs, ",") + if val, ok := verbMap[verbKey]; ok { + verbMap[verbKey] = append(val, res.Name) + } else { + verbMap[verbKey] = []string{res.Name} + } + } + for k := range verbMap { + sort.Strings(verbMap[k]) + } + return verbMap +} + +func mergeVerbMaps(left map[string][]string, right map[string][]string) map[string][]string { + merged := make(map[string][]string) + for k := range left { + merged[k] = left[k] + } + for k := range right { + if val, ok := merged[k]; ok { + set := make(map[string]bool) + for _, v := range right[k] { + set[v] = true + } + for _, v := range val { + set[v] = true + } + merged[k] = mapToSet(set) + } else { + merged[k] = right[k] + } + } + return merged +} + +func policyRuleByOrderedMap(oMap orderedmap.OrderedMap[string, map[string][]string]) []rbacv1.PolicyRule { + policyRules := make([]rbacv1.PolicyRule, 0) + for _, group := range oMap.Keys() { + verbMap, _ := oMap.Get(group) + for verbStr := range verbMap { + verbs := strings.Split(verbStr, ",") + groupName := group + pr := &rbacv1.PolicyRule{ + APIGroups: []string{groupName}, + Verbs: verbs, + Resources: verbMap[verbStr], + } + policyRules = append(policyRules, *pr) + } + } + return policyRules +} + +func mapToSet(m map[string]bool) []string { + s := make([]string, 0) + for k := range m { + s = append(s, k) + } + return s +} diff --git a/pkg/k8s/rbac/gen.go b/pkg/k8s/rbac/gen.go deleted file mode 100644 index 31be8d0..0000000 --- a/pkg/k8s/rbac/gen.go +++ /dev/null @@ -1,107 +0,0 @@ -package gen - -import ( - "strings" - "log" - "os" - "sort" - "github.com/coopernetes/kube-role-gen/internal/util" - "github.com/elliotchance/orderedmap" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/discovery" -) - -func GetOrderedResources(d discovery.DiscoveryClient, name string, verbose bool) *rbacv1.ClusterRole { - _, resourceListArr, err := d.ServerGroupsAndResources() - if err != nil { - log.Printf("Error during resource discovery, %s", err.Error()) - os.Exit(1) - } - - ordered := orderedmap.NewOrderedMap() - for _, resourceList := range resourceListArr { - if verbose { - log.Printf("Group: %s", resourceList.GroupVersion) - } - // rbac rules only look at API group names, not name & version - groupOnly := strings.Split(resourceList.GroupVersion, "/")[0] - // core API doesn't have a group "name". We set to "core" and replace at the end with a blank string in the rbac policy rule - if resourceList.GroupVersion == "v1" { - groupOnly = "core" - } - - resourceVerbMap := make(map[string][]string) - for _, resource := range resourceList.APIResources { - if verbose { - log.Printf("Resource: %s - Verbs: %s", - resource.Name, - resource.Verbs.String()) - } - - verbs := make([]string, 0) - for _, v := range resource.Verbs { - verbs = append(verbs, v) - } - sort.Strings(verbs) - verbString := strings.Join(verbs[:], ",") - if value, ok := resourceVerbMap[verbString]; ok { - resourceVerbMap[verbString] = append(value, resource.Name) - } else { - resourceVerbMap[verbString] = []string{resource.Name} - } - } - - for k := range resourceVerbMap { - var sb strings.Builder - sb.WriteString(groupOnly) - sb.WriteString("!") - sb.WriteString(k) - if resourceVal, exists := ordered.Get(sb.String()); exists { - resourceSetMap := make(map[string]bool) - for _, r := range resourceVal.([]string) { - resourceSetMap[r] = true - } - for _, r := range resourceVerbMap[k] { - resourceSetMap[r] = true - } - resourceSet := set.MakeSet(resourceSetMap) - ordered.Set(sb.String(), resourceSet) - } else { - ordered.Set(sb.String(), resourceVerbMap[k]) - } - } - } - - computedPolicyRules := make([]rbacv1.PolicyRule, 0) - for _, k := range ordered.Keys() { - splitKey := strings.Split(k.(string), "!") - if len(splitKey) != 2 { - log.Fatalf("Unexpected output from API: %s", k) - } - splitVerbList := strings.Split(splitKey[1], ",") - apiGroup := splitKey[0] - if splitKey[0] == "core" { - apiGroup = "" - } - - value, _ := ordered.Get(k) - - newPolicyRule := &rbacv1.PolicyRule{ - APIGroups: []string{apiGroup}, - Verbs: splitVerbList, - Resources: value.([]string), - } - computedPolicyRules = append(computedPolicyRules, *newPolicyRule) - } - return &rbacv1.ClusterRole{ - TypeMeta: metav1.TypeMeta{ - Kind: "ClusterRole", - APIVersion: "rbac.authorization.k8s.io/v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Rules: computedPolicyRules, - } -} diff --git a/pkg/k8s/rbac_test.go b/pkg/k8s/rbac_test.go new file mode 100644 index 0000000..ccc6f01 --- /dev/null +++ b/pkg/k8s/rbac_test.go @@ -0,0 +1,95 @@ +package k8s + +import ( + "github.com/elliotchance/orderedmap/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/strings/slices" + "reflect" + "testing" +) + +func TestExtractGroupForCore(t *testing.T) { + in := "v1" + out := extractGroupFromVersion(in) + if out != "" { + t.Fatalf("Expected blank string, got %s", out) + } +} + +func TestExtractGroupForGroupVersion(t *testing.T) { + in := "batch/v1" + out := extractGroupFromVersion(in) + if out != "batch" { + t.Fatalf("Expected core, got %s", out) + } +} + +func TestGatherResources(t *testing.T) { + r1 := &metav1.APIResource{ + Verbs: []string{ + "get", + "patch", + }, + Name: "test", + } + r2 := &metav1.APIResource{ + Verbs: []string{ + "get", + "list", + }, + Name: "test2", + } + rList := []metav1.APIResource{*r1, *r2} + + actual := convertToVerbMap(rList, true) + expected := map[string][]string{"get,patch": {"test"}, "get,list": {"test2"}} + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Expected value %s does not match %s", expected, actual) + } +} + +func TestPolicyRuleByMap(t *testing.T) { + o := orderedmap.NewOrderedMap[string, map[string][]string]() + coreMap := make(map[string][]string) + coreMap["get,list"] = []string{"pods"} + coreMap["get"] = []string{"pods/exec"} + o.Set("", coreMap) + actual := policyRuleByOrderedMap(*o) + if len(actual) != 2 { + t.Fatalf("Expected 2 policy rules, got %d (%s)", len(actual), actual) + } +} + +func TestMergeVerbMapsRightHasMore(t *testing.T) { + l := map[string][]string{"create,get,list": {"cronjobs"}} + r := map[string][]string{"create,get,list": {"cronjobs", "jobs"}} + actual := mergeVerbMaps(l, r) + if !slices.Equal(actual["create,get,list"], []string{"cronjobs", "jobs"}) { + t.Fatalf("Expected merged maps and got %s", actual["create,get,list"]) + } +} + +func TestMergeVerbMapsLeftHasMore(t *testing.T) { + l := map[string][]string{"create,get,list": {"cronjobs", "jobs"}} + r := map[string][]string{"create,get,list": {"cronjobs"}} + actual := mergeVerbMaps(l, r) + if !slices.Equal(actual["create,get,list"], []string{"cronjobs", "jobs"}) { + t.Fatalf("Expected merged maps and got %s", actual["create,get,list"]) + } +} + +func TestMergeVerbMapsHandlesBatchApi(t *testing.T) { + l := map[string][]string{ + "create delete deletecollection get list patch update watch": {"cronjobs", "jobs"}, + "get patch update": {"cronjobs/status", "jobs/status"}, + } + r := map[string][]string{ + "create delete deletecollection get list patch update watch": {"cronjobs"}, + "get patch update": {"cronjobs/status"}, + } + actual := mergeVerbMaps(l, r) + if !reflect.DeepEqual(l, actual) { + t.Fatalf("Expected merged map to equal %s, got %s", l, actual) + } +} diff --git a/tests/e2e_tests.sh b/tests/e2e_tests.sh index 97b208f..7aaee25 100755 --- a/tests/e2e_tests.sh +++ b/tests/e2e_tests.sh @@ -2,18 +2,23 @@ set -euo pipefail IFS=$'\n\t' -kube-role-gen | kubeval - +kube-role-gen | kubeconform -summary kube-role-gen | kubectl apply --validate -f - kube-role-gen | conftest test --policy tests/gh-11.rego - # https://github.com/coopernetes/kube-role-gen/issues/8 -kube-role-gen -json | python -m json.tool 2>&1 > /dev/null +if command -v python &> /dev/null +then + kube-role-gen -json | python -m json.tool > /dev/null 2>&1 +else + kube-role-gen -json | python3 -m json.tool > /dev/null 2>&1 +fi # https://github.com/coopernetes/kube-role-gen/issues/14 if [ -f "$HOME/.kube/config" ]; then cp $HOME/.kube/config /tmp/test-kubecfg - KUBECONFIG=/tmp/test-kubecfg kube-role-gen | kubeval - - kube-role-gen -kubeconfig /tmp/test-kubecfg | kubeval - + KUBECONFIG=/tmp/test-kubecfg kube-role-gen | kubeconform -summary + kube-role-gen -kubeconfig /tmp/test-kubecfg | kubeconform -summary fi kubectl apply --validate=false -f tests/crd.yaml