Skip to content

Commit

Permalink
Build out more unit tests and wrap that up
Browse files Browse the repository at this point in the history
  • Loading branch information
nabsul committed Jun 10, 2020
1 parent 4917616 commit 941cd5f
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 88 deletions.
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,23 +152,17 @@ If it is not provided, it will fall back to the `default` namespace.

Since this tool is mostly "glue" between AWS and Kuberenetes,
I've decided that unit tests are not so useful.
Instead, the tests in this code are designed to run against a real Kubernetes cluster.
Instead, the tests here are designed to run against a real Kubernetes cluster.
It will auto-detect the cluster to use,
and will refuse to run if the namespaces it uses already exist
(to avoid accidentally overriding real configurations).

Running these tests locally has two prerequisites:

- A secret with the needed AWS parameters
- A built image of the tool named `test-ecr-renew`

You can build the tool locally with this command:

```shell script
docker build -t test-ecr-renew .
```
> note: I have only tried running these unit tests on my local Windows machine with Docker Desktop.
This can be done with the following command:
Running these tests locally has the following prerequisites:

- Build an image of the tool: `docker build -t test-ecr-renew .`
- Create a secret with the needed AWS parameters:

```shell script
kubectl create secret test-ecr-renew-aws-settings \
Expand All @@ -181,5 +175,11 @@ kubectl create secret test-ecr-renew-aws-settings \
You can then run the tests by typing:

```shell script
go test -v
go test -v ./test/...
```

## Test Limitation

One of the biggest things I am currently not testing are permissions.
This is mostly due to laziness on my part:
I couldn't figure out how to get Docker Desktop to enforce RBAC permissions.
15 changes: 14 additions & 1 deletion src/k8s/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,20 @@ func GetNamespaces(envVar string) ([]string, error) {
return nil, err
}

return append(single, matchedNamespaces...), nil
return unique(append(single, matchedNamespaces...)), nil
}

func unique(values []string) []string {
result := make([]string, 0, len(values))
check := map[string]bool{}
for _, val := range values {
_, ok := check[val]
if !ok {
check[val] = true
result = append(result, val)
}
}
return result
}

func hasWildCard(val string) bool {
Expand Down
27 changes: 27 additions & 0 deletions test/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,33 @@ const ConstNamespaceRoleBinding = "test-ecr-renew-cluster-role-binding"
const ConstAwsSecretName = "test-ecr-renew-aws"
const ConstDockerSecretName = "test-ecr-renew-docker-login"

type config struct {
t *testing.T
successNamespaces []string
targetNamespace string
}

var spaces = map[string]string {
"_": ConstSvcNamespace,
"1": "test-ecr-renew-ns1",
"2": "test-ecr-renew-ns2",
"3": "test-ecr-renew-ns3",
"11": "test-ecr-renew-ns11",
"12": "test-ecr-renew-ns12",
"13": "test-ecr-renew-ns13",
"21": "test-ecr-renew-ns21",
"22": "test-ecr-renew-ns22",
"23": "test-ecr-renew-ns23",
}

func allNamespaces() []string {
result := make([]string, 0, len(spaces))
for _, v := range spaces {
result = append(result, v)
}
return result
}

func printError(t *testing.T, err error) {
t.Errorf("Error: %v", err)
}
89 changes: 64 additions & 25 deletions test/main_test.go
Original file line number Diff line number Diff line change
@@ -1,49 +1,88 @@
package test

import (
"strings"
"testing"
)

var spaces = map[string]string {
"_": ConstSvcNamespace,
"1": "test-ecr-renew-ns1",
"2": "test-ecr-renew-ns2",
"3": "test-ecr-renew-ns3",
"11": "test-ecr-renew-ns11",
"12": "test-ecr-renew-ns12",
"13": "test-ecr-renew-ns13",
"21": "test-ecr-renew-ns21",
"22": "test-ecr-renew-ns22",
"23": "test-ecr-renew-ns23",
func Test_NoTargetNamespace(t *testing.T) {
runTest(config{
t: t,
successNamespaces: []string{"default"},
targetNamespace: "",
})
}

func Test_BasicFunction(t *testing.T) {
func Test_SingleNamespace(t *testing.T) {
runTest(config{
t: t,
createdNamespaces: []string{ConstSvcNamespace},
successNamespaces: []string{ConstSvcNamespace},
failNamespaces: []string{},
canGetNamespaces: false,
targetNamespace: ConstSvcNamespace,
})
}

func Test_DeployToAll(t *testing.T) {
func Test_SingleNamespace2(t *testing.T) {
runTest(config{
t: t,
successNamespaces: []string{spaces["22"]},
targetNamespace: spaces["22"],
})
}

func Test_TwoNamespaces(t *testing.T) {
namespaces := []string{spaces["3"], spaces["12"]}
runTest(config{
t: t,
successNamespaces: namespaces,
targetNamespace: strings.Join(namespaces, ","),
})
}

func Test_WithStar(t *testing.T) {
namespaces := allNamespaces()
runTest(config{
t: t,
createdNamespaces: namespaces,
successNamespaces: namespaces,
failNamespaces: []string{},
canGetNamespaces: false,
targetNamespace: "test-ecr-renew-*",
})
}

func allNamespaces() []string {
result := make([]string, 0, len(spaces))
for _, v := range spaces {
result = append(result, v)
}
return result
func Test_WithQuestionMark(t *testing.T) {
runTest(config{
t: t,
successNamespaces: []string{spaces["12"], spaces["22"]},
targetNamespace: "test-ecr-renew-ns?2",
})
}

func Test_WithStarAndQuestionMark(t *testing.T) {
runTest(config{
t: t,
successNamespaces: []string{spaces["12"], spaces["22"]},
targetNamespace: "test-ecr-*-ns?2",
})
}

func Test_OverlappingResults1(t *testing.T) {
runTest(config{
t: t,
successNamespaces: allNamespaces(),
targetNamespace: "test-ecr-renew-ns13,test-ecr-renew-ns?2,test-ecr-renew-*",
})
}

func Test_OverlappingResults2(t *testing.T) {
runTest(config{
t: t,
successNamespaces: allNamespaces(),
targetNamespace: "test-ecr-renew-ns?2,test-ecr-renew-*,test-ecr-renew-ns13",
})
}

func Test_OverlappingResults3(t *testing.T) {
runTest(config{
t: t,
successNamespaces: allNamespaces(),
targetNamespace: "test-ecr-renew-*,test-ecr-renew-ns13,test-ecr-renew-ns?2",
})
}
49 changes: 22 additions & 27 deletions test/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,11 @@ import (
"github.com/nabsul/k8s-ecr-login-renew/src/k8s"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
"testing"
"time"
)

type config struct {
t *testing.T
createdNamespaces []string
successNamespaces []string
failNamespaces []string
canGetNamespaces bool
targetNamespace string
}

func runTest(cfg config) {
cleanup(cfg.createdNamespaces)
time.Sleep(10 * time.Second)
//cfg.t.Cleanup(func(){cleanup(cfg.createdNamespaces)})
cfg.t.Cleanup(func(){cleanup(cfg)})

t := cfg.t
c, err := k8s.GetClient()
Expand All @@ -31,23 +19,23 @@ func runTest(cfg config) {
return
}

err = checkNamespaces(c, cfg.createdNamespaces)
err = checkNamespaces(c, allNamespaces())
if err != nil {
printError(t, err)
return
}

t.Log("Creating namespaces")
for _, ns := range cfg.createdNamespaces {
t.Log("creating namespaces")
for _, ns := range allNamespaces() {
_, err := createNamespace(c, ns)
if err != nil {
printError(t, err)
return
}
}

t.Log("Creating service and permissions")
err = createServiceAccount(c, cfg.successNamespaces, cfg.canGetNamespaces)
t.Log("creating service and permissions")
err = createServiceAccount(c, []string{}, false)
if err != nil {
printError(t, err)
return
Expand All @@ -64,66 +52,73 @@ func runTest(cfg config) {
return
}

t.Log("Creating cron job")
t.Log("creating cron job")
err = initCronJob(c, cfg.targetNamespace, awsRegion, awsId, awsSecret)
if nil != err {
printError(t, err)
return
}

t.Log("Running the job")
t.Log("running the job")
logs, err := runCronJob(c)
if nil != err {
printError(t, err)
return
}

t.Log("Checking job logs")
t.Log("checking job logs")
if !strings.Contains(logs, "Fetching auth data from AWS... Success.") {
printError(t, errors.New(fmt.Sprintf("no AWS success message found")))
}

expectedUpdates := len(cfg.successNamespaces) + len(cfg.failNamespaces)
expectedUpdates := len(cfg.successNamespaces)
actualUpdates := strings.Count(logs, "Updating secret in namespace")
if actualUpdates != expectedUpdates {
msg := fmt.Sprintf("unexpected number of updates %d != %d", expectedUpdates, actualUpdates)
printError(t, errors.New(msg))
}

for _, ns := range cfg.successNamespaces {
t.Logf("Checking for success: %s", ns)
t.Logf("checking for success: %s", ns)
msg := fmt.Sprintf("Updating secret in namespace [%s]... success", ns)
if !strings.Contains(logs, msg) {
msg = fmt.Sprintf("no success message found for namespace [%s]", ns)
printError(t, errors.New(msg))
}
}

/* Not currently implemented
for _, ns := range cfg.failNamespaces {
t.Logf("Checking for failure: %s", ns)
t.Logf("checking for failure: %s", ns)
msg := fmt.Sprintf("Updating secret in namespace [%s]... failed:", ns)
if !strings.Contains(logs, msg) {
msg = fmt.Sprintf("no fail message found for namespace [%s]", ns)
printError(t, errors.New(msg))
}
}
*/

if t.Failed() {
printError(t, errors.New(logs))
}
}

// as long as we only create stuff in namespaces, deleting the namespaces should
func cleanup(namespaces []string) {
func cleanup(cfg config) {
cfg.t.Log("cleaning up...")

c, err := k8s.GetClient()
if nil != err {
return
}

for _, ns := range namespaces {
for _, ns := range allNamespaces() {
err = c.CoreV1().Namespaces().Delete(ns, &metaV1.DeleteOptions{})
if err != nil {
fmt.Printf("Failed to cleanup namespace [%s]: [%s}\n", ns, err)
fmt.Printf("failed to cleanup namespace [%s]: [%s}\n", ns, err)
}
}

cfg.t.Log("giving namespaces time to go away...")
time.Sleep(10 * time.Second)
}
Loading

0 comments on commit 941cd5f

Please sign in to comment.