Repository: grafana/tanka
Pull request #259 (2020-04-16 08:41:19)

Implement sorting by NS and test object sorting.
- the original sort code in Reconcile() didn't take into account
  the case where two objects of the same kind and name might reside in
  different namespace, allowing the sort being non-deterministic at
  times.

- testing of the sort code is now implemented.

There might be some refactor pending, since `kubernetes_test.go` also
contains code testing `Reconcile()`, but this is outside the scope of
this PR.

mplzik: @sh0rez changes implemented; PTAL.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e056bc7..2f35acd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # Changelog
 
+## 0.9.2 (2020-04-19)
+
+Mini-release to fix an issue with our Makefile (required for packaging). No
+changes in functionality.
+
+### Bug Fixes
+
+- **build**: Enable `static` Makefile target on all operating systems
+  ([#262](https://github.com/grafana/tanka/pull/262))
+
 ## 0.9.1 (2020-04-08)
 
 Small patch release to fix a `panic` issue with `tk apply`.
diff --git a/Makefile b/Makefile
index 918a1c2a..d7f545bd 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ dev:
 
 LDFLAGS := '-s -w -extldflags "-static" -X main.Version=${VERSION}'
 static:
-	CGO_ENABLED=0 GOOS=linux go build -ldflags=${LDFLAGS} ./cmd/tk
+	CGO_ENABLED=0 go build -ldflags=${LDFLAGS} ./cmd/tk
 
 install:
 	CGO_ENABLED=0 go install -ldflags=${LDFLAGS} ./cmd/tk
diff --git a/docs/docs/directory-structure.md b/docs/docs/directory-structure.md
index 6ae95ffb..4642fc74 100644
--- a/docs/docs/directory-structure.md
+++ b/docs/docs/directory-structure.md
@@ -53,8 +53,8 @@ When talking about directories, Tanka uses the following terms:
 
 | Term      | Description                              | Identifier file                   |
 | --------- | ---------------------------------------- | --------------------------------- |
-| `baseDir` | The root of your project                 | `jsonnetfile.json` or `tkrc.yaml` |
-| `rootDir` | The directory of the current environment | `main.jsonnet`                    |
+| `rootDir` | The root of your project                 | `jsonnetfile.json` or `tkrc.yaml` |
+| `baseDir` | The directory of the current environment | `main.jsonnet`                    |
 
 Regardless what subdirectory of the project you are in, Tanka will always be
 able to identify both directories, by searching for the identifier files in the
diff --git a/pkg/kubernetes/reconcile.go b/pkg/kubernetes/reconcile.go
index 3d23b38a..ae397c4c 100644
--- a/pkg/kubernetes/reconcile.go
+++ b/pkg/kubernetes/reconcile.go
@@ -113,6 +113,11 @@ func Reconcile(raw map[string]interface{}, spec v1alpha1.Spec, targets []*regexp
 			return out[i].Kind() < out[j].Kind()
 		}
 
+		// If namespaces differ, sort by the namespace.
+		if out[i].Metadata().Namespace() != out[j].Metadata().Namespace() {
+			return out[i].Metadata().Namespace() < out[j].Metadata().Namespace()
+		}
+
 		// Otherwise, order the objects by name.
 		return out[i].Metadata().Name() < out[j].Metadata().Name()
 	})


Test patch:
diff --git a/pkg/kubernetes/reconcile_test.go b/pkg/kubernetes/reconcile_test.go
index 9a594091..cf89785a 100644
--- a/pkg/kubernetes/reconcile_test.go
+++ b/pkg/kubernetes/reconcile_test.go
@@ -1,10 +1,14 @@
 package kubernetes
 
 import (
+	"regexp"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+
+	"github.com/grafana/tanka/pkg/kubernetes/manifest"
+	"github.com/grafana/tanka/pkg/spec/v1alpha1"
 )
 
 func TestExtract(t *testing.T) {
@@ -54,3 +58,130 @@ func TestExtract(t *testing.T) {
 		})
 	}
 }
+
+func mkobj(kind string, name string, ns string) map[string]interface{} {
+	ret := map[string]interface{}{
+		"kind":       kind,
+		"apiVersion": "apiversion",
+		"metadata": map[string]interface{}{
+			"name": name,
+		},
+	}
+	if ns != "" {
+		ret["metadata"].(map[string]interface{})["namespace"] = ns
+	}
+
+	return ret
+}
+
+func TestReconcileSorting(t *testing.T) {
+	tests := []struct {
+		raw     map[string]interface{}
+		targets []*regexp.Regexp
+		state   manifest.List
+		err     error
+	}{
+		{
+			// sorting by kinds in the `kindOrder` list
+			raw: map[string]interface{}{
+				"a": mkobj("Service", "service", "default"),
+				"b": mkobj("Deployment", "deployment", "default"),
+				"c": mkobj("CustomResourceDefinition", "crd", ""),
+			},
+			state: manifest.List{
+				mkobj("CustomResourceDefinition", "crd", ""),
+				mkobj("Service", "service", "default"),
+				mkobj("Deployment", "deployment", "default"),
+			},
+		},
+		{
+			// alphabtical sorting by kinds outside `kindOrder` list
+			raw: map[string]interface{}{
+				"a": mkobj("B", "b", "default"),
+				"b": mkobj("C", "c", "default"),
+				"c": mkobj("A", "a", "default"),
+			},
+			state: manifest.List{
+				mkobj("A", "a", "default"),
+				mkobj("B", "b", "default"),
+				mkobj("C", "c", "default"),
+			},
+		},
+		{
+			// sorting by the namespace if kinds match
+			raw: map[string]interface{}{
+				"a": mkobj("Service", "service", "default2"),
+				"b": mkobj("Service", "service", "default"),
+				"c": mkobj("Service", "service", "default1"),
+			},
+			state: manifest.List{
+				mkobj("Service", "service", "default"),
+				mkobj("Service", "service", "default1"),
+				mkobj("Service", "service", "default2"),
+			},
+		},
+		{
+			// sorting by the names if both kinds and namespaces match
+			raw: map[string]interface{}{
+				"a": mkobj("Service", "service2", "default"),
+				"b": mkobj("Service", "service", "default"),
+				"c": mkobj("Service", "service1", "default"),
+			},
+			state: manifest.List{
+				mkobj("Service", "service", "default"),
+				mkobj("Service", "service1", "default"),
+				mkobj("Service", "service2", "default"),
+			},
+		},
+		{
+			// sorting by the names if both kinds match and there are no namespaces
+			raw: map[string]interface{}{
+				"a": mkobj("CustomResourceDefinition", "crd2", ""),
+				"b": mkobj("CustomResourceDefinition", "crd", ""),
+				"c": mkobj("CustomResourceDefinition", "crd1", ""),
+			},
+			state: manifest.List{
+				mkobj("CustomResourceDefinition", "crd", ""),
+				mkobj("CustomResourceDefinition", "crd1", ""),
+				mkobj("CustomResourceDefinition", "crd2", ""),
+			},
+		},
+		{
+			raw: map[string]interface{}{
+				"a": mkobj("Deployment", "b", "a"),
+				"b": mkobj("ConfigMap", "a", "a"),
+				"c": mkobj("Issuer", "a", "a"),
+				"d": mkobj("Service", "b", "a"),
+				"e": mkobj("Service", "a", "a"),
+				"f": mkobj("Deployment", "a", "a"),
+				"g": mkobj("ConfigMap", "a", "b"),
+				"h": mkobj("Issuer", "b", "a"),
+				"i": mkobj("Service", "b", "b"),
+				"j": mkobj("Deployment", "a", "b"),
+				"k": mkobj("ConfigMap", "b", "a"),
+				"l": mkobj("Issuer", "a", "b"),
+			},
+			state: manifest.List{
+				mkobj("ConfigMap", "a", "a"),
+				mkobj("ConfigMap", "b", "a"),
+				mkobj("ConfigMap", "a", "b"),
+				mkobj("Service", "a", "a"),
+				mkobj("Service", "b", "a"),
+				mkobj("Service", "b", "b"),
+				mkobj("Deployment", "a", "a"),
+				mkobj("Deployment", "b", "a"),
+				mkobj("Deployment", "a", "b"),
+				mkobj("Issuer", "a", "a"),
+				mkobj("Issuer", "b", "a"),
+				mkobj("Issuer", "a", "b"),
+			},
+		},
+	}
+
+	for _, test := range tests {
+		res, err := Reconcile(test.raw, v1alpha1.New().Spec, test.targets)
+
+		require.NoError(t, err)
+		require.Equal(t, test.state, res)
+	}
+}