diff --git a/.golangci.yaml b/.golangci.yaml index 823d137b..325c037a 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -77,6 +77,7 @@ linters: - gocheckcompilerdirectives - gochecknoglobals - gochecknoinits + - gochecksumtype - gocognit - goconst - gocritic @@ -95,6 +96,7 @@ linters: - grouper - importas - ineffassign + - inamedparam - lll - maintidx - makezero @@ -106,7 +108,7 @@ linters: - nlreturn - noctx - nolintlint - - nolintlint + - perfsprint - prealloc - predeclared - reassign @@ -163,7 +165,7 @@ issues: text: "package-comments" run: - go: '1.20' + go: '1.21' skip-dirs: - .private timeout: 5m @@ -172,4 +174,4 @@ output: uniq-by-line: false service: - golangci-lint-version: 1.52.x + golangci-lint-version: 1.55.x diff --git a/Makefile b/Makefile index d6e67ec1..67a1ae05 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,8 @@ fmt: ## Run formatters lint: fmt ## Run linters; runs with GOOS env var for linting on darwin golangci-lint run + helm lint --quiet charts/clabernetes + helm lint --quiet charts/clicker test: ## Run unit tests gotestsum --format testname --hide-summary=skipped -- -coverprofile=cover.out `go list ./... | grep -v e2e` @@ -29,6 +31,12 @@ test-e2e: ## Run e2e tests cov: ## Produce html coverage report go tool cover -html=cover.out +install-tools: ## Install lint/test tools + go install mvdan.cc/gofumpt@latest + go install golang.org/x/tools/cmd/goimports@latest + go install github.com/segmentio/golines@latest + go install gotest.tools/gotestsum@latest + install-code-generators: ## Install latest code-generator tools go install k8s.io/code-generator/cmd/deepcopy-gen@latest go install k8s.io/code-generator/cmd/openapi-gen@latest diff --git a/charts/clabernetes/.helmignore b/charts/clabernetes/.helmignore index f0c13194..0c975daa 100644 --- a/charts/clabernetes/.helmignore +++ b/charts/clabernetes/.helmignore @@ -19,3 +19,6 @@ .project .idea/ *.tmproj + +# ignore +tests \ No newline at end of file diff --git a/charts/clabernetes/tests/clicker/clicker_enabled_test.go b/charts/clabernetes/tests/clicker/clicker_enabled_test.go new file mode 100644 index 00000000..eba19474 --- /dev/null +++ b/charts/clabernetes/tests/clicker/clicker_enabled_test.go @@ -0,0 +1,33 @@ +package default_vaules_test + +import ( + "fmt" + "os" + "testing" + + clabernetesconstants "github.com/srl-labs/clabernetes/constants" + + clabernetestesthelper "github.com/srl-labs/clabernetes/testhelper" +) + +func TestMain(m *testing.M) { + clabernetestesthelper.Flags() + + os.Exit(m.Run()) +} + +// TestDefaultValues -- really just here to ensure that we dont accidentally break our charts; this +// will probably be *highly* irritating in times of lots of chart updates, but, once we know the +// template are in a good place we can always just re-generate the "golden" outputs. +func TestClickerEnabled(t *testing.T) { + t.Parallel() + + testName := "clicker-enabled" + + clabernetestesthelper.HelmTest( + t, + testName, + clabernetesconstants.Clabernetes, + fmt.Sprintf("%s-values.yaml", testName), + ) +} diff --git a/charts/clabernetes/tests/clicker/test-fixtures/clicker-enabled-values.yaml b/charts/clabernetes/tests/clicker/test-fixtures/clicker-enabled-values.yaml new file mode 100644 index 00000000..da2c82fb --- /dev/null +++ b/charts/clabernetes/tests/clicker/test-fixtures/clicker-enabled-values.yaml @@ -0,0 +1,19 @@ +--- +# ensure that "global" values (not actually helm globals, but values we pass via yaml anchor) +# are non-default and passed to the clicker cahrt and render properly +appName: &_appName clabernetes-plus-clicker + +# extra labels/annotations that are added to all objects +globalAnnotations: &_globalAnnotations + someannotation: someannotationvalue + annotherannotation: anotherannotationvalue +globalLabels: &_globalLabels + somelabel: somelabelvalue + anotherlabel: anotherlabelvalue + +# ensure that when enabled clicker stuff is rendered +clicker: + enabled: true + appName: *_appName + globalAnnotations: *_globalAnnotations + globalLabels: *_globalLabels \ No newline at end of file diff --git a/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-clusterrole.yaml b/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-clusterrole.yaml new file mode 100755 index 00000000..9aad0bbf --- /dev/null +++ b/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-clusterrole.yaml @@ -0,0 +1,35 @@ +--- +# Source: clabernetes/charts/clicker/templates/clusterrole.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-clicker-cluster-role" + clabernetes/component: cluster-role + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue + name: "clabernetes-plus-clicker-clicker-cluster-role" +rules: + - apiGroups: + - "" + resources: + - nodes + verbs: + - "*" + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - create + - watch + - delete diff --git a/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-clusterrolebinding.yaml b/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-clusterrolebinding.yaml new file mode 100755 index 00000000..517c0941 --- /dev/null +++ b/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-clusterrolebinding.yaml @@ -0,0 +1,47 @@ +--- +# Source: clabernetes/charts/clicker/templates/clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: clabernetes-clicker + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-clicker-cluster-role-binding" + clabernetes/component: cluster-role-binding + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue +subjects: + - kind: ServiceAccount + name: "clabernetes-plus-clicker-clicker-service-account" + namespace: clabernetes +roleRef: + kind: ClusterRole + name: "clabernetes-plus-clicker-cluster-role" + apiGroup: rbac.authorization.k8s.io +--- +# Source: clabernetes/charts/clicker/templates/clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: clabernetes-clicker-nodes + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-clicker-cluster-role-binding" + clabernetes/component: cluster-role-binding +subjects: + - kind: ServiceAccount + name: "clabernetes-plus-clicker-clicker-service-account" + namespace: clabernetes +roleRef: + kind: ClusterRole + name: "clabernetes-plus-clicker-clicker-cluster-role" + apiGroup: rbac.authorization.k8s.io diff --git a/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-job.yaml b/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-job.yaml new file mode 100755 index 00000000..353e6c73 --- /dev/null +++ b/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-job.yaml @@ -0,0 +1,74 @@ +--- +# Source: clabernetes/charts/clicker/templates/job.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: clabernetes-plus-clicker-clicker + namespace: clabernetes + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-clicker" + clabernetes/component: clicker + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue +spec: + template: + metadata: + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-clicker" + clabernetes/component: clicker + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue + spec: + containers: + - name: clicker + image: "ghcr.io/srl-labs/clabernetes/clabernetes-manager:0.0.16" + imagePullPolicy: IfNotPresent + command: [ + "/clabernetes/manager", + "clicker", + ] + env: + - name: APP_NAME + value: clabernetes-plus-clicker + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CLICKER_LOGGER_LEVEL + value: info + - name: CLICKER_WORKER_COMMAND + value: /bin/sh + - name: CLICKER_WORKER_SCRIPT + value: echo "hello, there" + - name: CLICKER_WORKER_RESOURCES + value: "requests:\n cpu: 50m\n memory: 128Mi" + - name: CLICKER_GLOBAL_ANNOTATIONS + value: "annotherannotation: anotherannotationvalue\nsomeannotation: someannotationvalue" + - name: CLICKER_GLOBAL_LABELS + value: "anotherlabel: anotherlabelvalue\nsomelabel: somelabelvalue" + resources: + requests: + memory: 128Mi + cpu: 50m + restartPolicy: Never + serviceAccountName: "clabernetes-plus-clicker-clicker-service-account" + backoffLimit: 4 + ttlSecondsAfterFinished: 300 diff --git a/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-serviceaccount.yaml b/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-serviceaccount.yaml new file mode 100755 index 00000000..33e62841 --- /dev/null +++ b/charts/clabernetes/tests/clicker/test-fixtures/golden/_subchart-clicker-serviceaccount.yaml @@ -0,0 +1,19 @@ +--- +# Source: clabernetes/charts/clicker/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: "clabernetes-plus-clicker-clicker-service-account" + namespace: clabernetes + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-clicker-service-account" + clabernetes/component: service-account + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue diff --git a/charts/clabernetes/tests/clicker/test-fixtures/golden/certificate-secret.yaml b/charts/clabernetes/tests/clicker/test-fixtures/golden/certificate-secret.yaml new file mode 100755 index 00000000..5ba46dc7 --- /dev/null +++ b/charts/clabernetes/tests/clicker/test-fixtures/golden/certificate-secret.yaml @@ -0,0 +1,20 @@ +--- +# Source: clabernetes/templates/certificate-secret.yaml +apiVersion: v1 +kind: Secret +metadata: + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-certificate" + clabernetes/component: certificate + clabernetes/part-of: manager + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue + name: "clabernetes-plus-clicker-certificate" +data: {} diff --git a/charts/clabernetes/tests/clicker/test-fixtures/golden/clusterrole.yaml b/charts/clabernetes/tests/clicker/test-fixtures/golden/clusterrole.yaml new file mode 100755 index 00000000..11b7ea33 --- /dev/null +++ b/charts/clabernetes/tests/clicker/test-fixtures/golden/clusterrole.yaml @@ -0,0 +1,70 @@ +--- +# Source: clabernetes/templates/clusterrole.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-cluster-role" + clabernetes/component: cluster-role + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue + name: "clabernetes-plus-clicker-cluster-role" +rules: + - apiGroups: + - topology.clabernetes + resources: + - "*" + verbs: + - "*" + - apiGroups: + - apiextensions.k8s.io + resources: + - "*" + verbs: + - "*" + - apiGroups: + - "" + resources: + - namespaces + - secrets + - configmaps + - services + verbs: + - get + - list + - create + - update + - delete + - patch + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - create + - update + - delete + - patch + - watch + - apiGroups: + - apps + resources: + - deployments + verbs: + - get + - list + - create + - update + - delete + - patch + - watch diff --git a/charts/clabernetes/tests/clicker/test-fixtures/golden/clusterrolebinding.yaml b/charts/clabernetes/tests/clicker/test-fixtures/golden/clusterrolebinding.yaml new file mode 100755 index 00000000..44e65644 --- /dev/null +++ b/charts/clabernetes/tests/clicker/test-fixtures/golden/clusterrolebinding.yaml @@ -0,0 +1,26 @@ +--- +# Source: clabernetes/templates/clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: clabernetes-manager + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-cluster-role-binding" + clabernetes/component: cluster-role-binding + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue +subjects: + - kind: ServiceAccount + name: "clabernetes-plus-clicker-service-account" + namespace: clabernetes +roleRef: + kind: ClusterRole + name: "clabernetes-plus-clicker-cluster-role" + apiGroup: rbac.authorization.k8s.io diff --git a/charts/clabernetes/tests/clicker/test-fixtures/golden/configmap.yaml b/charts/clabernetes/tests/clicker/test-fixtures/golden/configmap.yaml new file mode 100755 index 00000000..ab1b8bfc --- /dev/null +++ b/charts/clabernetes/tests/clicker/test-fixtures/golden/configmap.yaml @@ -0,0 +1,35 @@ +--- +# Source: clabernetes/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: clabernetes-plus-clicker-config + namespace: clabernetes + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-config" + clabernetes/component: config + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue +data: + globalAnnotations: |- + --- + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue + globalLabels: |- + --- + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + defaultResources: |- + --- + byContainerlabKind: {} + default: + requests: + cpu: 200m + memory: 512Mi diff --git a/charts/clabernetes/tests/clicker/test-fixtures/golden/deployment.yaml b/charts/clabernetes/tests/clicker/test-fixtures/golden/deployment.yaml new file mode 100755 index 00000000..c8bd4ee6 --- /dev/null +++ b/charts/clabernetes/tests/clicker/test-fixtures/golden/deployment.yaml @@ -0,0 +1,130 @@ +--- +# Source: clabernetes/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: clabernetes-plus-clicker-manager + namespace: clabernetes + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-manager" + clabernetes/component: manager + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue +spec: + selector: + matchLabels: + clabernetes/app: clabernetes-plus-clicker + release: release-name + replicas: 3 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-manager" + clabernetes/component: manager + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchLabels: + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-manager" + clabernetes/component: manager + topologyKey: kubernetes.io/hostname + - weight: 50 + podAffinityTerm: + labelSelector: + matchLabels: + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-manager" + clabernetes/component: manager + topologyKey: topology.kubernetes.io/zone + terminationGracePeriodSeconds: 10 + serviceAccountName: "clabernetes-plus-clicker-service-account" + initContainers: + - name: init + image: "ghcr.io/srl-labs/clabernetes/clabernetes-manager:0.0.16" + imagePullPolicy: IfNotPresent + command: ["/clabernetes/manager", "run", "--initializer"] + env: + - name: APP_NAME + value: clabernetes-plus-clicker + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MANAGER_LOGGER_LEVEL + value: info + resources: + requests: + memory: 128Mi + cpu: 50m + containers: + - name: manager + image: "ghcr.io/srl-labs/clabernetes/clabernetes-manager:0.0.16" + imagePullPolicy: IfNotPresent + command: ["/clabernetes/manager", "run"] + env: + - name: APP_NAME + value: clabernetes-plus-clicker + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CLIENT_OPERATION_TIMEOUT_MULTIPLIER + value: "1" + - name: IN_CLUSTER_DNS_SUFFIX + value: svc.cluster.local + - name: MANAGER_LOGGER_LEVEL + value: info + - name: CONTROLLER_LOGGER_LEVEL + value: info + - name: LAUNCHER_LOGGER_LEVEL + value: info + - name: LAUNCHER_PULL_POLICY + value: "IfNotPresent" + - name: LAUNCHER_IMAGE + value: "ghcr.io/srl-labs/clabernetes/clabernetes-launcher:0.0.16" + resources: + requests: + memory: 128Mi + cpu: 50m + ports: + - name: readiness-port + containerPort: 8080 + readinessProbe: + httpGet: + path: /ready + port: readiness-port + successThreshold: 1 diff --git a/charts/clabernetes/tests/clicker/test-fixtures/golden/serviceaccount.yaml b/charts/clabernetes/tests/clicker/test-fixtures/golden/serviceaccount.yaml new file mode 100755 index 00000000..606bfed9 --- /dev/null +++ b/charts/clabernetes/tests/clicker/test-fixtures/golden/serviceaccount.yaml @@ -0,0 +1,19 @@ +--- +# Source: clabernetes/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: "clabernetes-plus-clicker-service-account" + namespace: clabernetes + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes-plus-clicker + clabernetes/name: "clabernetes-plus-clicker-service-account" + clabernetes/component: service-account + anotherlabel: anotherlabelvalue + somelabel: somelabelvalue + annotations: + annotherannotation: anotherannotationvalue + someannotation: someannotationvalue diff --git a/charts/clabernetes/tests/default_vaules/default_values_test.go b/charts/clabernetes/tests/default_vaules/default_values_test.go new file mode 100644 index 00000000..288a6e9c --- /dev/null +++ b/charts/clabernetes/tests/default_vaules/default_values_test.go @@ -0,0 +1,27 @@ +package default_vaules_test + +import ( + "os" + "testing" + + clabernetesconstants "github.com/srl-labs/clabernetes/constants" + + clabernetestesthelper "github.com/srl-labs/clabernetes/testhelper" +) + +func TestMain(m *testing.M) { + clabernetestesthelper.Flags() + + os.Exit(m.Run()) +} + +// TestDefaultValues -- really just here to ensure that we dont accidentally break our charts; this +// will probably be *highly* irritating in times of lots of chart updates, but, once we know the +// template are in a good place we can always just re-generate the "golden" outputs. +func TestDefaultValues(t *testing.T) { + t.Parallel() + + testName := "default-values" + + clabernetestesthelper.HelmTest(t, testName, clabernetesconstants.Clabernetes, "") +} diff --git a/charts/clabernetes/tests/default_vaules/test-fixtures/golden/certificate-secret.yaml b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/certificate-secret.yaml new file mode 100755 index 00000000..32bdf5cf --- /dev/null +++ b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/certificate-secret.yaml @@ -0,0 +1,15 @@ +--- +# Source: clabernetes/templates/certificate-secret.yaml +apiVersion: v1 +kind: Secret +metadata: + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-certificate" + clabernetes/component: certificate + clabernetes/part-of: manager + name: "clabernetes-certificate" +data: {} diff --git a/charts/clabernetes/tests/default_vaules/test-fixtures/golden/clusterrole.yaml b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/clusterrole.yaml new file mode 100755 index 00000000..c19aff5a --- /dev/null +++ b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/clusterrole.yaml @@ -0,0 +1,65 @@ +--- +# Source: clabernetes/templates/clusterrole.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-cluster-role" + clabernetes/component: cluster-role + name: "clabernetes-cluster-role" +rules: + - apiGroups: + - topology.clabernetes + resources: + - "*" + verbs: + - "*" + - apiGroups: + - apiextensions.k8s.io + resources: + - "*" + verbs: + - "*" + - apiGroups: + - "" + resources: + - namespaces + - secrets + - configmaps + - services + verbs: + - get + - list + - create + - update + - delete + - patch + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - create + - update + - delete + - patch + - watch + - apiGroups: + - apps + resources: + - deployments + verbs: + - get + - list + - create + - update + - delete + - patch + - watch diff --git a/charts/clabernetes/tests/default_vaules/test-fixtures/golden/clusterrolebinding.yaml b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/clusterrolebinding.yaml new file mode 100755 index 00000000..97fe8a81 --- /dev/null +++ b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/clusterrolebinding.yaml @@ -0,0 +1,21 @@ +--- +# Source: clabernetes/templates/clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: clabernetes-manager + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-cluster-role-binding" + clabernetes/component: cluster-role-binding +subjects: + - kind: ServiceAccount + name: "clabernetes-service-account" + namespace: clabernetes +roleRef: + kind: ClusterRole + name: "clabernetes-cluster-role" + apiGroup: rbac.authorization.k8s.io diff --git a/charts/clabernetes/tests/default_vaules/test-fixtures/golden/configmap.yaml b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/configmap.yaml new file mode 100755 index 00000000..8ba2cc1e --- /dev/null +++ b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/configmap.yaml @@ -0,0 +1,28 @@ +--- +# Source: clabernetes/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: clabernetes-config + namespace: clabernetes + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-config" + clabernetes/component: config +data: + globalAnnotations: |- + --- + {} + globalLabels: |- + --- + {} + defaultResources: |- + --- + byContainerlabKind: {} + default: + requests: + cpu: 200m + memory: 512Mi diff --git a/charts/clabernetes/tests/default_vaules/test-fixtures/golden/deployment.yaml b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/deployment.yaml new file mode 100755 index 00000000..49646989 --- /dev/null +++ b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/deployment.yaml @@ -0,0 +1,120 @@ +--- +# Source: clabernetes/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: clabernetes-manager + namespace: clabernetes + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-manager" + clabernetes/component: manager +spec: + selector: + matchLabels: + clabernetes/app: clabernetes + release: release-name + replicas: 3 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-manager" + clabernetes/component: manager + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchLabels: + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-manager" + clabernetes/component: manager + topologyKey: kubernetes.io/hostname + - weight: 50 + podAffinityTerm: + labelSelector: + matchLabels: + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-manager" + clabernetes/component: manager + topologyKey: topology.kubernetes.io/zone + terminationGracePeriodSeconds: 10 + serviceAccountName: "clabernetes-service-account" + initContainers: + - name: init + image: "ghcr.io/srl-labs/clabernetes/clabernetes-manager:0.0.16" + imagePullPolicy: IfNotPresent + command: ["/clabernetes/manager", "run", "--initializer"] + env: + - name: APP_NAME + value: clabernetes + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MANAGER_LOGGER_LEVEL + value: info + resources: + requests: + memory: 128Mi + cpu: 50m + containers: + - name: manager + image: "ghcr.io/srl-labs/clabernetes/clabernetes-manager:0.0.16" + imagePullPolicy: IfNotPresent + command: ["/clabernetes/manager", "run"] + env: + - name: APP_NAME + value: clabernetes + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CLIENT_OPERATION_TIMEOUT_MULTIPLIER + value: "1" + - name: IN_CLUSTER_DNS_SUFFIX + value: svc.cluster.local + - name: MANAGER_LOGGER_LEVEL + value: info + - name: CONTROLLER_LOGGER_LEVEL + value: info + - name: LAUNCHER_LOGGER_LEVEL + value: info + - name: LAUNCHER_PULL_POLICY + value: "IfNotPresent" + - name: LAUNCHER_IMAGE + value: "ghcr.io/srl-labs/clabernetes/clabernetes-launcher:0.0.16" + resources: + requests: + memory: 128Mi + cpu: 50m + ports: + - name: readiness-port + containerPort: 8080 + readinessProbe: + httpGet: + path: /ready + port: readiness-port + successThreshold: 1 diff --git a/charts/clabernetes/tests/default_vaules/test-fixtures/golden/serviceaccount.yaml b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/serviceaccount.yaml new file mode 100755 index 00000000..81dd4361 --- /dev/null +++ b/charts/clabernetes/tests/default_vaules/test-fixtures/golden/serviceaccount.yaml @@ -0,0 +1,14 @@ +--- +# Source: clabernetes/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: "clabernetes-service-account" + namespace: clabernetes + labels: + chart: "clabernetes-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-service-account" + clabernetes/component: service-account diff --git a/charts/clabernetes/values.yaml b/charts/clabernetes/values.yaml index 232799dd..3f9199b6 100644 --- a/charts/clabernetes/values.yaml +++ b/charts/clabernetes/values.yaml @@ -2,6 +2,11 @@ # # global options # +# note the yaml anchors, we use these to easily pass the same values to the clicker sub-chart (if +# enabling); there is no magic here other than yaml anchor magic, if you expect the "global" things +# to be really global and applied to the clicker (or any other future) dependency then use anchors +# or pass things explicitly. this felt better than using actual helm "globals" +# appName: &_appName clabernetes diff --git a/charts/clicker/.helmignore b/charts/clicker/.helmignore index f0c13194..0c975daa 100644 --- a/charts/clicker/.helmignore +++ b/charts/clicker/.helmignore @@ -19,3 +19,6 @@ .project .idea/ *.tmproj + +# ignore +tests \ No newline at end of file diff --git a/charts/clicker/tests/default_vaules/default_values_test.go b/charts/clicker/tests/default_vaules/default_values_test.go new file mode 100644 index 00000000..7d45a65c --- /dev/null +++ b/charts/clicker/tests/default_vaules/default_values_test.go @@ -0,0 +1,90 @@ +package default_vaules_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + clabernetesconstants "github.com/srl-labs/clabernetes/constants" + + clabernetestesthelper "github.com/srl-labs/clabernetes/testhelper" +) + +func TestMain(m *testing.M) { + clabernetestesthelper.Flags() + + os.Exit(m.Run()) +} + +// TestDefaultValues -- really just here to ensure that we dont accidentally break our charts; this +// will probably be *highly* irritating in times of lots of chart updates, but, once we know the +// template are in a good place we can always just re-generate the "golden" outputs. +func TestDefaultValues(t *testing.T) { + t.Parallel() + + testName := "default-values" + + // we have to make the chartname/templates dir too since thats where helm wants to write things + actualRootDir := fmt.Sprintf("test-fixtures/%s-actual", testName) + actualDir := fmt.Sprintf("%s/clicker/templates", actualRootDir) + + err := os.MkdirAll(actualDir, clabernetesconstants.PermissionsEveryoneRead) + if err != nil { + t.Fatalf( + "failed creating actual output directory %q, error: %s", actualDir, err, + ) + } + + defer func() { + if !*clabernetestesthelper.SkipCleanup { + err = os.RemoveAll(actualRootDir) + if err != nil { + t.Logf("failed cleaning up actual output directory %q, error: %s", actualDir, err) + } + } + }() + + clabernetestesthelper.HelmCommand( + t, + "template", + "../../.", + "--output-dir", + actualRootDir, + ) + + var actualFileNames []string + + actualFileNames, err = filepath.Glob(fmt.Sprintf("%s/*.yaml", actualDir)) + if err != nil { + t.Fatalf("failed globbing actual files, error: '%s'", err) + } + + actualFileContents := map[string][]byte{} + + for _, actualFileName := range actualFileNames { + var actualFileContent []byte + + actualFileContent, err = os.ReadFile(actualFileName) //nolint:gosec + if err != nil { + t.Fatalf( + "failed reading contents of actual output file %q, error: %s", actualFileName, err, + ) + } + + actualFileContents[actualFileName] = actualFileContent + } + + if *clabernetestesthelper.Update { + for actualFileName, actualFileContent := range actualFileContents { + clabernetestesthelper.WriteTestFixtureFile( + t, + fmt.Sprintf("golden/%s", filepath.Base(actualFileName)), + actualFileContent, + ) + } + + // we just wrote the golden file of course it will match, no need to check + return + } +} diff --git a/charts/clicker/tests/default_vaules/test-fixtures/golden/clusterrole.yaml b/charts/clicker/tests/default_vaules/test-fixtures/golden/clusterrole.yaml new file mode 100755 index 00000000..6dfa747a --- /dev/null +++ b/charts/clicker/tests/default_vaules/test-fixtures/golden/clusterrole.yaml @@ -0,0 +1,30 @@ +--- +# Source: clicker/templates/clusterrole.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-clicker-cluster-role" + clabernetes/component: cluster-role + name: "clabernetes-clicker-cluster-role" +rules: + - apiGroups: + - "" + resources: + - nodes + verbs: + - "*" + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - create + - watch + - delete diff --git a/charts/clicker/tests/default_vaules/test-fixtures/golden/clusterrolebinding.yaml b/charts/clicker/tests/default_vaules/test-fixtures/golden/clusterrolebinding.yaml new file mode 100755 index 00000000..dc319bf8 --- /dev/null +++ b/charts/clicker/tests/default_vaules/test-fixtures/golden/clusterrolebinding.yaml @@ -0,0 +1,42 @@ +--- +# Source: clicker/templates/clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: clabernetes-clicker + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-clicker-cluster-role-binding" + clabernetes/component: cluster-role-binding +subjects: + - kind: ServiceAccount + name: "clabernetes-clicker-service-account" + namespace: clabernetes +roleRef: + kind: ClusterRole + name: "clabernetes-cluster-role" + apiGroup: rbac.authorization.k8s.io +--- +# Source: clicker/templates/clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: clabernetes-clicker-nodes + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-clicker-cluster-role-binding" + clabernetes/component: cluster-role-binding +subjects: + - kind: ServiceAccount + name: "clabernetes-clicker-service-account" + namespace: clabernetes +roleRef: + kind: ClusterRole + name: "clabernetes-clicker-cluster-role" + apiGroup: rbac.authorization.k8s.io diff --git a/charts/clicker/tests/default_vaules/test-fixtures/golden/job.yaml b/charts/clicker/tests/default_vaules/test-fixtures/golden/job.yaml new file mode 100755 index 00000000..55c47fea --- /dev/null +++ b/charts/clicker/tests/default_vaules/test-fixtures/golden/job.yaml @@ -0,0 +1,64 @@ +--- +# Source: clicker/templates/job.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: clabernetes-clicker + namespace: clabernetes + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-clicker" + clabernetes/component: clicker +spec: + template: + metadata: + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-clicker" + clabernetes/component: clicker + spec: + containers: + - name: clicker + image: "ghcr.io/srl-labs/clabernetes/clabernetes-manager:0.0.16" + imagePullPolicy: IfNotPresent + command: [ + "/clabernetes/manager", + "clicker", + ] + env: + - name: APP_NAME + value: clabernetes + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CLICKER_LOGGER_LEVEL + value: info + - name: CLICKER_WORKER_COMMAND + value: /bin/sh + - name: CLICKER_WORKER_SCRIPT + value: echo "hello, there" + - name: CLICKER_WORKER_RESOURCES + value: "requests:\n cpu: 50m\n memory: 128Mi" + - name: CLICKER_GLOBAL_ANNOTATIONS + value: "{}" + - name: CLICKER_GLOBAL_LABELS + value: "{}" + resources: + requests: + memory: 128Mi + cpu: 50m + restartPolicy: Never + serviceAccountName: "clabernetes-clicker-service-account" + backoffLimit: 4 + ttlSecondsAfterFinished: 300 diff --git a/charts/clicker/tests/default_vaules/test-fixtures/golden/serviceaccount.yaml b/charts/clicker/tests/default_vaules/test-fixtures/golden/serviceaccount.yaml new file mode 100755 index 00000000..e6cec107 --- /dev/null +++ b/charts/clicker/tests/default_vaules/test-fixtures/golden/serviceaccount.yaml @@ -0,0 +1,14 @@ +--- +# Source: clicker/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: "clabernetes-clicker-service-account" + namespace: clabernetes + labels: + chart: "clicker-0.0.16" + release: release-name + heritage: Helm + clabernetes/app: clabernetes + clabernetes/name: "clabernetes-clicker-service-account" + clabernetes/component: service-account diff --git a/controllers/base.go b/controllers/base.go index f0bc4594..57927524 100644 --- a/controllers/base.go +++ b/controllers/base.go @@ -29,7 +29,10 @@ type Controller interface { // SetupWithManager sets the given controller up with the controller-runtime manager. SetupWithManager(mgr ctrlruntime.Manager) error // Reconcile is the actual reconcile function of the controller. - Reconcile(context.Context, ctrlruntime.Request) (ctrlruntime.Result, error) + Reconcile( + ctx context.Context, + req ctrlruntime.Request, + ) (ctrlruntime.Result, error) } // NewBaseController returns a new BaseController object to embed in clabernetes controllers. diff --git a/e2e/topology/containerlab/basic/containerlab_basic_test.go b/e2e/topology/containerlab/basic/containerlab_basic_test.go index f064d52c..46b8bf82 100644 --- a/e2e/topology/containerlab/basic/containerlab_basic_test.go +++ b/e2e/topology/containerlab/basic/containerlab_basic_test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - clabernetese2esuite "github.com/srl-labs/clabernetes/e2e/suite" + clabernetestesthelpersuite "github.com/srl-labs/clabernetes/testhelper/suite" clabernetestesthelper "github.com/srl-labs/clabernetes/testhelper" ) @@ -21,12 +21,12 @@ func normalizeContainerlab(t *testing.T, objectData []byte) []byte { // unfortunately we need to remove the hash bits since any cluster may have no lb or get a // different lb address assigned than what we have stored in golden file(s) - objectData = clabernetese2esuite.YQCommand( + objectData = clabernetestesthelper.YQCommand( t, objectData, "del(.status.nodeExposedPortsHash)", ) - objectData = clabernetese2esuite.YQCommand( + objectData = clabernetestesthelper.YQCommand( t, objectData, "del(.status.nodeExposedPorts[].loadBalancerAddress)", @@ -39,14 +39,14 @@ func normalizeExposeService(t *testing.T, objectData []byte) []byte { t.Helper() // cluster ips obviously are going to be different all the time so we'll ignore them - objectData = clabernetese2esuite.YQCommand(t, objectData, "del(.spec.clusterIP)") - objectData = clabernetese2esuite.YQCommand(t, objectData, "del(.spec.clusterIPs)") + objectData = clabernetestesthelper.YQCommand(t, objectData, "del(.spec.clusterIP)") + objectData = clabernetestesthelper.YQCommand(t, objectData, "del(.spec.clusterIPs)") // remove node ports since they'll be random - objectData = clabernetese2esuite.YQCommand(t, objectData, "del(.spec.ports[].nodePort)") + objectData = clabernetestesthelper.YQCommand(t, objectData, "del(.spec.ports[].nodePort)") // and the lb ip in status because of course that may be different depending on cluster - objectData = clabernetese2esuite.YQCommand( + objectData = clabernetestesthelper.YQCommand( t, objectData, ".status.loadBalancer = {}", @@ -60,7 +60,7 @@ func TestContainerlabBasic(t *testing.T) { testName := "containerlab-basic" - steps := clabernetese2esuite.Steps{ + steps := clabernetestesthelpersuite.Steps{ { // this step, while obviously very "basic" does quite a bit of work for us... it ensures // that the default ports are allocated, the config is hashed and subdivided up, and our @@ -68,7 +68,7 @@ func TestContainerlabBasic(t *testing.T) { // setup as we'd expect. Index: 10, Description: "Create a simple containerlab topology with just one node", - AssertObjects: map[string][]clabernetese2esuite.AssertObject{ + AssertObjects: map[string][]clabernetestesthelpersuite.AssertObject{ "containerlab": { { Name: testName, @@ -89,5 +89,5 @@ func TestContainerlabBasic(t *testing.T) { }, } - clabernetese2esuite.Run(t, steps, testName) + clabernetestesthelpersuite.Run(t, steps, testName) } diff --git a/launcher/containerlab.go b/launcher/containerlab.go index c0d180d2..0597a5e8 100644 --- a/launcher/containerlab.go +++ b/launcher/containerlab.go @@ -6,6 +6,7 @@ import ( "net" "os" "os/exec" + "strconv" clabernetesconstants "github.com/srl-labs/clabernetes/constants" claberneteserrors "github.com/srl-labs/clabernetes/errors" @@ -70,11 +71,11 @@ func (c *clabernetes) runContainerlabVxlanTools( "--remote", resolvedVxlanRemote, "--id", - fmt.Sprint(vxlanID), + strconv.Itoa(vxlanID), "--link", fmt.Sprintf("%s-%s", localNodeName, cntLink), "--port", - fmt.Sprint(clabernetesconstants.VXLANServicePort), + strconv.Itoa(clabernetesconstants.VXLANServicePort), ) _, err = cmd.Output() diff --git a/testhelper/command.go b/testhelper/command.go new file mode 100644 index 00000000..4ee1e168 --- /dev/null +++ b/testhelper/command.go @@ -0,0 +1,21 @@ +package testhelper + +import ( + "os/exec" + "testing" +) + +// Execute executes a command in the context of a test. +func Execute(t *testing.T, cmd *exec.Cmd) []byte { + t.Helper() + + output, err := cmd.CombinedOutput() + if err != nil { + t.Logf("error executing command, error: %q", err) + t.Logf("errored command: %s", cmd.String()) + t.Logf("errored command combined output: %s", output) + t.FailNow() + } + + return output +} diff --git a/testhelper/fail.go b/testhelper/fail.go index aa2c07d0..8017dae3 100644 --- a/testhelper/fail.go +++ b/testhelper/fail.go @@ -20,7 +20,7 @@ func FailOutput(t *testing.T, actual, expected any) { "\n%s"+ "\n\033[0;36m<<< actual ***\033[0m"+ "\n\033[0;35m*** expected >>>\033[0m"+ - "\n%s"+ + "\n%s"+ //nolint:goconst "\n\033[0;35m<<< expected ***\033[0m", actual, expected, ) diff --git a/testhelper/helm.go b/testhelper/helm.go new file mode 100644 index 00000000..0e206f84 --- /dev/null +++ b/testhelper/helm.go @@ -0,0 +1,169 @@ +package testhelper + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + clabernetesconstants "github.com/srl-labs/clabernetes/constants" +) + +const ( + helm = "helm" +) + +// HelmTest executes a test against a helm chart -- this is a very simple/dumb test meant only to +// ensure that we don't accidentally screw up charts. We do this by storing the "golden" output of +// a rendered chart (and subcharts if applicable) with a given values file. +func HelmTest(t *testing.T, testName, namespace, valuesFileName string) { + t.Helper() + + // we have to make the chartname/templates dir too since thats where helm wants to write things + actualRootDir := fmt.Sprintf("test-fixtures/%s-actual", testName) + actualDir := fmt.Sprintf("%s/clabernetes/templates", actualRootDir) + + var valuesFile string + + if valuesFileName != "" { + var err error + + valuesFile, err = filepath.Abs(fmt.Sprintf("test-fixtures/%s-values.yaml", testName)) + if err != nil { + t.Fatalf( + "failed getting abspath for values file, error: %s", err, + ) + } + } + + err := os.MkdirAll(actualDir, clabernetesconstants.PermissionsEveryoneRead) + if err != nil { + t.Fatalf( + "failed creating actual output directory %q, error: %s", actualDir, err, + ) + } + + defer func() { + if !*SkipCleanup { + err = os.RemoveAll(actualRootDir) + if err != nil { + t.Logf("failed cleaning up actual output directory %q, error: %s", actualDir, err) + } + } + }() + + args := []string{ + "template", + "../../.", + "--namespace", + namespace, + "--output-dir", + actualRootDir, + } + + if valuesFile != "" { + args = append(args, "--values", valuesFile) + } + + HelmCommand( + t, + args..., + ) + + renderedTemplates := ReadAllRenderedTemplates(t, actualRootDir) + + if *Update { + for expectedFileName, expectedFileContent := range renderedTemplates { + WriteTestFixtureFile( + t, + fmt.Sprintf("golden/%s", filepath.Base(expectedFileName)), + expectedFileContent, + ) + } + + // we just wrote the golden file of course it will match, no need to check + return + } + + for expectedFileName, actualContents := range renderedTemplates { + expected := ReadTestFixtureFile(t, fmt.Sprintf("golden/%s", expectedFileName)) + + if !bytes.Equal( + actualContents, + expected, + ) { + FailOutput(t, actualContents, expected) + } + } +} + +// HelmCommand executes helm with the given arguments. +func HelmCommand(t *testing.T, args ...string) []byte { + t.Helper() + + cmd := exec.Command( + helm, + args..., + ) + + return Execute(t, cmd) +} + +// ReadAllRenderedTemplates loads all rendered template content into a map -- sub-charts are loaded +// with a "_subchart--" prefix. +func ReadAllRenderedTemplates(t *testing.T, rootRenderDir string) map[string][]byte { + t.Helper() + + renderedTemplates := map[string][]byte{} + + parentChartFileNames, err := filepath.Glob(fmt.Sprintf("%s/*/templates/*.yaml", rootRenderDir)) + if err != nil { + t.Fatalf("failed globbing parent chart files, error: '%s'", err) + } + + subChartFileNames, err := filepath.Glob( + fmt.Sprintf("%s/*/charts/*/templates/*.yaml", rootRenderDir), + ) + if err != nil { + t.Fatalf("failed globbing dependency chart files, error: '%s'", err) + } + + for _, parentChartFileName := range parentChartFileNames { + var contents []byte + + contents, err = os.ReadFile(parentChartFileName) //nolint:gosec + if err != nil { + t.Fatalf( + "failed reading contents of actual output file %q, error: %s", + parentChartFileName, + err, + ) + } + + renderedTemplates[filepath.Base(parentChartFileName)] = contents + } + + for _, subChartFileName := range subChartFileNames { + subChartPathComponents := strings.Split(subChartFileName, string(filepath.Separator)) + + subChartName := subChartPathComponents[4] + + var contents []byte + + contents, err = os.ReadFile(subChartFileName) //nolint:gosec + if err != nil { + t.Fatalf( + "failed reading contents of actual output file %q, error: %s", + subChartFileName, + err, + ) + } + + renderedTemplates[fmt.Sprintf("_subchart-%s-%s", subChartName, filepath.Base(subChartFileName))] = contents //nolint:lll + } + + return renderedTemplates +} diff --git a/e2e/suite/command.go b/testhelper/kubectl.go similarity index 63% rename from e2e/suite/command.go rename to testhelper/kubectl.go index b18e8b72..5097eb7b 100644 --- a/e2e/suite/command.go +++ b/testhelper/kubectl.go @@ -1,7 +1,6 @@ -package suite +package testhelper import ( - "fmt" "os/exec" "testing" ) @@ -10,19 +9,19 @@ const ( kubectl = "kubectl" ) -func execute(t *testing.T, cmd *exec.Cmd) []byte { - t.Helper() - - output, err := cmd.CombinedOutput() - if err != nil { - t.Logf("error executing command, error: %q", err) - t.Logf("errored command: %s", cmd.String()) - t.Logf("errored command combined output: %s", output) - t.FailNow() - } +// Operation represents a kubectl operation type, i.e. apply or delete. +type Operation string - return output -} +const ( + // Apply is the apply kubectl operation. + Apply Operation = "apply" + // Delete is the delete kubectl operation. + Delete Operation = "delete" + // Create is the create kubectl operation. + Create Operation = "create" + // Get is the get kubectl operation. + Get Operation = "get" +) func kubectlNamespace(t *testing.T, operation Operation, namespace string) { t.Helper() @@ -62,7 +61,7 @@ func KubectlFileOp(t *testing.T, operation Operation, namespace, fileName string fileName, ) - _ = execute(t, cmd) + _ = Execute(t, cmd) } // KubectlGetOp runs get on the given object, returning the yaml output. @@ -80,21 +79,5 @@ func KubectlGetOp(t *testing.T, kind, namespace, name string) []byte { "yaml", ) - return execute(t, cmd) -} - -// YQCommand accepts some yaml content and returns it after executing the given yqPattern against -// it. -func YQCommand(t *testing.T, content []byte, yqPattern string) []byte { - t.Helper() - - yqCmd := fmt.Sprintf("echo '%s' | yq '%s'", string(content), yqPattern) - - cmd := exec.Command( //nolint:gosec - "bash", - "-c", - yqCmd, - ) - - return execute(t, cmd) + return Execute(t, cmd) } diff --git a/e2e/suite/kubernetes.go b/testhelper/kubernetes.go similarity index 99% rename from e2e/suite/kubernetes.go rename to testhelper/kubernetes.go index cbe99ad9..68a9c243 100644 --- a/e2e/suite/kubernetes.go +++ b/testhelper/kubernetes.go @@ -1,4 +1,4 @@ -package suite +package testhelper import ( "testing" diff --git a/e2e/suite/eventually.go b/testhelper/suite/eventually.go similarity index 100% rename from e2e/suite/eventually.go rename to testhelper/suite/eventually.go diff --git a/e2e/suite/files.go b/testhelper/suite/files.go similarity index 100% rename from e2e/suite/files.go rename to testhelper/suite/files.go diff --git a/e2e/suite/logging.go b/testhelper/suite/logging.go similarity index 68% rename from e2e/suite/logging.go rename to testhelper/suite/logging.go index 02a1f989..7a68fa62 100644 --- a/e2e/suite/logging.go +++ b/testhelper/suite/logging.go @@ -4,7 +4,6 @@ import "fmt" const ( green = "\u001B[32m" - red = "\u001B[31m" colorStop = "\033[0m" ) @@ -17,8 +16,3 @@ func LogStepDescr(idx int, description string) string { func LogStepSuccess(idx int) string { return fmt.Sprintf("Step %d: %sSUCCESS%s", idx, green, colorStop) } - -// LogStepFailure sends a pretty string to the test logger for a step failure. -func LogStepFailure(idx int) string { - return fmt.Sprintf("Step %d: %sFAILURE%s", idx, red, colorStop) -} diff --git a/e2e/suite/run.go b/testhelper/suite/run.go similarity index 81% rename from e2e/suite/run.go rename to testhelper/suite/run.go index 236acb26..3e2d98b2 100644 --- a/e2e/suite/run.go +++ b/testhelper/suite/run.go @@ -16,13 +16,13 @@ const ( // Run executes a clabernetes e2e test. func Run(t *testing.T, steps []Step, testName string) { //nolint: thelper - namespace := NewTestNamespace(testName) + namespace := clabernetestesthelper.NewTestNamespace(testName) - KubectlCreateNamespace(t, namespace) + clabernetestesthelper.KubectlCreateNamespace(t, namespace) defer func() { if !*clabernetestesthelper.SkipCleanup { - KubectlDeleteNamespace(t, namespace) + clabernetestesthelper.KubectlDeleteNamespace(t, namespace) } }() @@ -34,7 +34,7 @@ func Run(t *testing.T, steps []Step, testName string) { //nolint: thelper for _, stepFixture := range stepFixtures { stepFixtureOperationType := GetStepFixtureType(t, stepFixture) - KubectlFileOp(t, stepFixtureOperationType, namespace, stepFixture) + clabernetestesthelper.KubectlFileOp(t, stepFixtureOperationType, namespace, stepFixture) } if *clabernetestesthelper.Update { @@ -77,10 +77,10 @@ func Run(t *testing.T, steps []Step, testName string) { //nolint: thelper func getter(t *testing.T, namespace, kind, objectName string, object AssertObject) []byte { t.Helper() - objectData := KubectlGetOp(t, kind, namespace, objectName) + objectData := clabernetestesthelper.KubectlGetOp(t, kind, namespace, objectName) if !object.SkipDefaultNormalize { - objectData = NormalizeKubernetesObject(t, objectData) + objectData = clabernetestesthelper.NormalizeKubernetesObject(t, objectData) } for _, normalizeF := range object.NormalizeFuncs { diff --git a/e2e/suite/steps.go b/testhelper/suite/steps.go similarity index 74% rename from e2e/suite/steps.go rename to testhelper/suite/steps.go index a7f17c70..5b99ef7d 100644 --- a/e2e/suite/steps.go +++ b/testhelper/suite/steps.go @@ -4,20 +4,8 @@ import ( "regexp" "sync" "testing" -) - -// Operation represents a kubectl operation type, i.e. apply or delete. -type Operation string -const ( - // Apply is the apply kubectl operation. - Apply Operation = "apply" - // Delete is the delete kubectl operation. - Delete Operation = "delete" - // Create is the create kubectl operation. - Create Operation = "create" - // Get is the get kubectl operation. - Get Operation = "get" + clabernetestesthelper "github.com/srl-labs/clabernetes/testhelper" ) // AssertObject represents an object that we are looking to assert the state of in a test -- the @@ -59,7 +47,7 @@ func getStepPatterns() *stepPatterns { } // GetStepFixtureType returns the Operation type of the given test step fixture file. -func GetStepFixtureType(t *testing.T, stepFixtureName string) Operation { +func GetStepFixtureType(t *testing.T, stepFixtureName string) clabernetestesthelper.Operation { t.Helper() patterns := getStepPatterns() @@ -67,10 +55,13 @@ func GetStepFixtureType(t *testing.T, stepFixtureName string) Operation { matches := patterns.stepFixtureType.FindStringSubmatch(stepFixtureName) opIndex := patterns.stepFixtureType.SubexpIndex("fixtureType") - resolved := Operation(matches[opIndex]) + resolved := clabernetestesthelper.Operation(matches[opIndex]) switch resolved { - case Apply, Delete, Create, Get: + case clabernetestesthelper.Apply, + clabernetestesthelper.Delete, + clabernetestesthelper.Create, + clabernetestesthelper.Get: default: t.Fatalf("fixture type '%s' invalid", resolved) } diff --git a/testhelper/yq.go b/testhelper/yq.go new file mode 100644 index 00000000..a39b2640 --- /dev/null +++ b/testhelper/yq.go @@ -0,0 +1,23 @@ +package testhelper + +import ( + "fmt" + "os/exec" + "testing" +) + +// YQCommand accepts some yaml content and returns it after executing the given yqPattern against +// it. +func YQCommand(t *testing.T, content []byte, yqPattern string) []byte { + t.Helper() + + yqCmd := fmt.Sprintf("echo '%s' | yq '%s'", string(content), yqPattern) + + cmd := exec.Command( //nolint:gosec + "bash", + "-c", + yqCmd, + ) + + return Execute(t, cmd) +} diff --git a/util/hash.go b/util/hash.go index ce16807c..f1db3975 100644 --- a/util/hash.go +++ b/util/hash.go @@ -2,7 +2,7 @@ package util import ( "crypto/sha256" - "fmt" + "encoding/hex" ) // HashBytes accepts a bytes object and returns a string sha256 hash representing that object. @@ -10,5 +10,5 @@ func HashBytes(b []byte) string { hash := sha256.New() hash.Write(b) - return fmt.Sprintf("%x", hash.Sum(nil)) + return hex.EncodeToString(hash.Sum(nil)) }