From 5344fe16a61501b54d0d4fca87ce8a881ae6636f Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Mon, 30 Aug 2021 12:49:19 -0400 Subject: [PATCH 1/5] Create Admin Partitions when enabled --- charts/consul/templates/client-daemonset.yaml | 3 + .../templates/partition-init-cleanup-job.yaml | 61 +++++ ...tition-init-cleanup-podsecuritypolicy.yaml | 35 +++ .../partition-init-cleanup-role.yaml | 25 +++ .../partition-init-cleanup-rolebinding.yaml | 20 ++ ...partition-init-cleanup-serviceaccount.yaml | 19 ++ .../consul/templates/partition-init-job.yaml | 109 +++++++++ .../partition-init-podsecuritypolicy.yaml | 35 +++ .../consul/templates/partition-init-role.yaml | 37 ++++ .../templates/partition-init-rolebinding.yaml | 20 ++ .../partition-init-serviceaccount.yaml | 19 ++ charts/consul/test/unit/client-daemonset.bats | 23 ++ .../test/unit/partition-init-cleanup-job.bats | 48 ++++ ...tition-init-cleanup-podsecuritypolicy.bats | 48 ++++ .../unit/partition-init-cleanup-role.bats | 48 ++++ .../partition-init-cleanup-rolebinding.bats | 48 ++++ ...partition-init-cleanup-serviceaccount.bats | 48 ++++ .../consul/test/unit/partition-init-job.bats | 203 +++++++++++++++++ .../partition-init-podsecuritypolicy.bats | 48 ++++ .../consul/test/unit/partition-init-role.bats | 48 ++++ .../test/unit/partition-init-rolebinding.bats | 48 ++++ .../unit/partition-init-serviceaccount.bats | 48 ++++ charts/consul/values.yaml | 4 + control-plane/commands.go | 5 + control-plane/go.mod | 2 +- control-plane/go.sum | 5 +- .../subcommand/partition-init/command.go | 208 ++++++++++++++++++ .../partition-init/command_ent_test.go | 74 +++++++ 28 files changed, 1336 insertions(+), 3 deletions(-) create mode 100644 charts/consul/templates/partition-init-cleanup-job.yaml create mode 100644 charts/consul/templates/partition-init-cleanup-podsecuritypolicy.yaml create mode 100644 charts/consul/templates/partition-init-cleanup-role.yaml create mode 100644 charts/consul/templates/partition-init-cleanup-rolebinding.yaml create mode 100644 charts/consul/templates/partition-init-cleanup-serviceaccount.yaml create mode 100644 charts/consul/templates/partition-init-job.yaml create mode 100644 charts/consul/templates/partition-init-podsecuritypolicy.yaml create mode 100644 charts/consul/templates/partition-init-role.yaml create mode 100644 charts/consul/templates/partition-init-rolebinding.yaml create mode 100644 charts/consul/templates/partition-init-serviceaccount.yaml create mode 100644 charts/consul/test/unit/partition-init-cleanup-job.bats create mode 100644 charts/consul/test/unit/partition-init-cleanup-podsecuritypolicy.bats create mode 100644 charts/consul/test/unit/partition-init-cleanup-role.bats create mode 100644 charts/consul/test/unit/partition-init-cleanup-rolebinding.bats create mode 100644 charts/consul/test/unit/partition-init-cleanup-serviceaccount.bats create mode 100644 charts/consul/test/unit/partition-init-job.bats create mode 100644 charts/consul/test/unit/partition-init-podsecuritypolicy.bats create mode 100644 charts/consul/test/unit/partition-init-role.bats create mode 100644 charts/consul/test/unit/partition-init-rolebinding.bats create mode 100644 charts/consul/test/unit/partition-init-serviceaccount.bats create mode 100644 control-plane/subcommand/partition-init/command.go create mode 100644 control-plane/subcommand/partition-init/command_ent_test.go diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index 9b07d4ae7b..56268eb142 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -233,6 +233,9 @@ spec: {{- if (and .Values.global.metrics.enabled .Values.global.metrics.enableAgentMetrics) }} -hcl='telemetry { prometheus_retention_time = "{{ .Values.global.metrics.agentMetricsRetentionTime }}" }' \ {{- end }} + {{- if .Values.global.adminPartitions.enabled }} + -hcl='partition = "{{ .Values.global.adminPartitions.name }}"' \ + {{- end }} -config-dir=/consul/config \ {{- if .Values.global.acls.manageSystemACLs }} -config-dir=/consul/aclconfig \ diff --git a/charts/consul/templates/partition-init-cleanup-job.yaml b/charts/consul/templates/partition-init-cleanup-job.yaml new file mode 100644 index 0000000000..8ee302d315 --- /dev/null +++ b/charts/consul/templates/partition-init-cleanup-job.yaml @@ -0,0 +1,61 @@ +{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} +{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} +# This job deletes the partition-init job once it completes successfully. +# It runs as a helm hook because it only needs to run when the partition-init +# Job gets recreated which only happens during an install or upgrade. +# We also utilize the helm hook-delete-policy to delete this job itself. +# We want to delete the partition-init job because once it runs successfully +# it's not needed and also because if it stays around then when users run +# helm upgrade with values that change the spec of the job, Kubernetes errors +# because the job spec is immutable. If the job is deleted, then a new job +# is created and there's no error. +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "consul.fullname" . }}-partition-init-cleanup + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "-1" + # If the hook fails then all that happens is we didn't delete the job. + # There's no reason for *this* job to stay around in that case so delete + # regardless of success. + "helm.sh/hook-delete-policy": hook-succeeded,hook-failed +spec: + template: + metadata: + name: {{ template "consul.fullname" . }}-partition-init-cleanup + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: partition-init-cleanup + annotations: + "consul.hashicorp.com/connect-inject": "false" + spec: + restartPolicy: Never + serviceAccountName: {{ template "consul.fullname" . }}-partition-init-cleanup + containers: + - name: partition-init-cleanup + image: {{ .Values.global.imageK8S }} + command: + - consul-k8s-control-plane + args: + - delete-completed-job + - -log-level={{ .Values.global.logLevel }} + - -log-json={{ .Values.global.logJSON }} + - -k8s-namespace={{ .Release.Namespace }} + - {{ template "consul.fullname" . }}-partition-init + resources: + requests: + memory: "50Mi" + cpu: "50m" + limits: + memory: "50Mi" + cpu: "50m" +{{- end }} diff --git a/charts/consul/templates/partition-init-cleanup-podsecuritypolicy.yaml b/charts/consul/templates/partition-init-cleanup-podsecuritypolicy.yaml new file mode 100644 index 0000000000..e0bfad8350 --- /dev/null +++ b/charts/consul/templates/partition-init-cleanup-podsecuritypolicy.yaml @@ -0,0 +1,35 @@ +{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} +{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "consul.fullname" . }}-partition-init-cleanup + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +spec: + privileged: false + # Allow core volume types. + volumes: + - 'secret' + allowPrivilegeEscalation: false + # This is redundant with non-root + disallow privilege escalation, + # but we can provide it for defense in depth. + requiredDropCapabilities: + - ALL + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false +{{- end }} diff --git a/charts/consul/templates/partition-init-cleanup-role.yaml b/charts/consul/templates/partition-init-cleanup-role.yaml new file mode 100644 index 0000000000..9e57f1de47 --- /dev/null +++ b/charts/consul/templates/partition-init-cleanup-role.yaml @@ -0,0 +1,25 @@ +{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} +{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "consul.fullname" . }}-partition-init-cleanup + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +rules: + - apiGroups: ["batch"] + resources: ["jobs"] + verbs: ["get", "delete"] +{{- if .Values.global.enablePodSecurityPolicies }} + - apiGroups: ["policy"] + resources: ["podsecuritypolicies"] + resourceNames: + - {{ template "consul.fullname" . }}-partition-init-cleanup + verbs: + - use +{{- end }} +{{- end }} diff --git a/charts/consul/templates/partition-init-cleanup-rolebinding.yaml b/charts/consul/templates/partition-init-cleanup-rolebinding.yaml new file mode 100644 index 0000000000..15773d58a8 --- /dev/null +++ b/charts/consul/templates/partition-init-cleanup-rolebinding.yaml @@ -0,0 +1,20 @@ +{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} +{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "consul.fullname" . }}-partition-init-cleanup + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "consul.fullname" . }}-partition-init-cleanup +subjects: + - kind: ServiceAccount + name: {{ template "consul.fullname" . }}-partition-init-cleanup +{{- end }} diff --git a/charts/consul/templates/partition-init-cleanup-serviceaccount.yaml b/charts/consul/templates/partition-init-cleanup-serviceaccount.yaml new file mode 100644 index 0000000000..7917941b3a --- /dev/null +++ b/charts/consul/templates/partition-init-cleanup-serviceaccount.yaml @@ -0,0 +1,19 @@ +{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} +{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "consul.fullname" . }}-partition-init-cleanup + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +{{- with .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range . }} + - name: {{ .name }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml new file mode 100644 index 0000000000..a572415dd2 --- /dev/null +++ b/charts/consul/templates/partition-init-job.yaml @@ -0,0 +1,109 @@ +{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} +{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "consul.fullname" . }}-partition-init + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +spec: + template: + metadata: + name: {{ template "consul.fullname" . }}-partition-init + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + release: {{ .Release.Name }} + component: partition-init + annotations: + "consul.hashicorp.com/connect-inject": "false" + spec: + restartPolicy: Never + serviceAccountName: {{ template "consul.fullname" . }}-partition-init + {{- if (or .Values.global.tls.enabled (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey)) }} + volumes: + {{- if .Values.global.tls.enabled }} + - name: consul-ca-cert + secret: + {{- if .Values.global.tls.caCert.secretName }} + secretName: {{ .Values.global.tls.caCert.secretName }} + {{- else }} + secretName: {{ template "consul.fullname" . }}-ca-cert + {{- end }} + items: + - key: {{ default "tls.crt" .Values.global.tls.caCert.secretKey }} + path: tls.crt + {{- end }} + {{- if (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey) }} + - name: bootstrap-token + secret: + secretName: {{ .Values.global.acls.bootstrapToken.secretName }} + items: + - key: {{ .Values.global.acls.bootstrapToken.secretKey }} + path: bootstrap-token + {{- end }} + {{- end }} + containers: + - name: post-install-job + image: {{ .Values.global.imageK8S }} + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if (or .Values.global.tls.enabled (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey)) }} + volumeMounts: + {{- if .Values.global.tls.enabled }} + - name: consul-ca-cert + mountPath: /consul/tls/ca + readOnly: true + {{- end }} + {{- if (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey) }} + - name: bootstrap-token + mountPath: /consul/acl/tokens + readOnly: true + {{- end }} + {{- end }} + command: + - "/bin/sh" + - "-ec" + - | + CONSUL_FULLNAME="{{template "consul.fullname" . }}" + + consul-k8s-control-plane partition-init \ + -log-level={{ .Values.global.logLevel }} \ + -log-json={{ .Values.global.logJSON }} \ + + {{- if and .Values.externalServers.enabled (not .Values.externalServers.hosts) }}{{ fail "externalServers.hosts must be set if externalServers.enabled is true" }}{{ end -}} + {{- range .Values.externalServers.hosts }} + -server-address={{ quote . }} \ + {{- end }} + -server-port={{ .Values.externalServers.httpsPort }} \ + + {{- if .Values.global.tls.enabled }} + -use-https \ + -consul-ca-cert=/consul/tls/ca/tls.crt \ + {{- if not .Values.externalServers.enabled }} + -server-port=8501 \ + {{- end }} + {{- if .Values.externalServers.tlsServerName }} + -consul-tls-server-name={{ .Values.externalServers.tlsServerName }} \ + {{- end }} + {{- end }} + + {{- if (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey) }} + -bootstrap-token-file=/consul/acl/tokens/bootstrap-token \ + {{- end }} + -partition-name={{ .Values.global.adminPartitions.name }} + resources: + requests: + memory: "50Mi" + cpu: "50m" + limits: + memory: "50Mi" + cpu: "50m" +{{- end }} diff --git a/charts/consul/templates/partition-init-podsecuritypolicy.yaml b/charts/consul/templates/partition-init-podsecuritypolicy.yaml new file mode 100644 index 0000000000..ed50ac0c5b --- /dev/null +++ b/charts/consul/templates/partition-init-podsecuritypolicy.yaml @@ -0,0 +1,35 @@ +{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} +{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "consul.fullname" . }}-partition-init + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +spec: + privileged: false + # Allow core volume types. + volumes: + - 'secret' + allowPrivilegeEscalation: false + # This is redundant with non-root + disallow privilege escalation, + # but we can provide it for defense in depth. + requiredDropCapabilities: + - ALL + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' + readOnlyRootFilesystem: false +{{- end }} diff --git a/charts/consul/templates/partition-init-role.yaml b/charts/consul/templates/partition-init-role.yaml new file mode 100644 index 0000000000..f227d4f616 --- /dev/null +++ b/charts/consul/templates/partition-init-role.yaml @@ -0,0 +1,37 @@ +{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} +{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "consul.fullname" . }}-partition-init + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +rules: + - apiGroups: [""] + resources: + - secrets + verbs: + - create + - get +{{- if .Values.connectInject.enabled }} + - apiGroups: [""] + resources: + - serviceaccounts + resourceNames: + - {{ template "consul.fullname" . }}-connect-injector-authmethod-svc-account + verbs: + - get +{{- end }} +{{- if .Values.global.enablePodSecurityPolicies }} + - apiGroups: ["policy"] + resources: ["podsecuritypolicies"] + resourceNames: + - {{ template "consul.fullname" . }}-partition-init + verbs: + - use +{{- end }} +{{- end }} diff --git a/charts/consul/templates/partition-init-rolebinding.yaml b/charts/consul/templates/partition-init-rolebinding.yaml new file mode 100644 index 0000000000..5ad369a37f --- /dev/null +++ b/charts/consul/templates/partition-init-rolebinding.yaml @@ -0,0 +1,20 @@ +{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} +{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "consul.fullname" . }}-partition-init + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "consul.fullname" . }}-partition-init +subjects: + - kind: ServiceAccount + name: {{ template "consul.fullname" . }}-partition-init +{{- end }} diff --git a/charts/consul/templates/partition-init-serviceaccount.yaml b/charts/consul/templates/partition-init-serviceaccount.yaml new file mode 100644 index 0000000000..41c6ca4e2a --- /dev/null +++ b/charts/consul/templates/partition-init-serviceaccount.yaml @@ -0,0 +1,19 @@ +{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} +{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "consul.fullname" . }}-partition-init + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "consul.name" . }} + chart: {{ template "consul.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +{{- with .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range . }} + - name: {{ .name }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index bdaff30659..a2895afd4a 100755 --- a/charts/consul/test/unit/client-daemonset.bats +++ b/charts/consul/test/unit/client-daemonset.bats @@ -1332,3 +1332,26 @@ rollingUpdate: yq -c -r '.spec.template.spec.containers[0].command | join(" ") | contains("-recursor=\"1.2.3.4\"")' | tee /dev/stderr) [ "${actual}" = "true" ] } +#-------------------------------------------------------------------- +# partitions + +@test "client/DaemonSet: -partitions can be set by global.adminPartition.enabled" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.adminPartitions.enabled=true' \ + . | tee /dev/stderr | + yq -c -r '.spec.template.spec.containers[0].command | join(" ") | contains("partition = \"default\"")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "client/DaemonSet: -partitions can be overridden by global.adminPartition.name" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/client-daemonset.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.adminPartitions.name=test' \ + . | tee /dev/stderr | + yq -c -r '.spec.template.spec.containers[0].command | join(" ") | contains("partition = \"test\"")' | tee /dev/stderr) + [ "${actual}" = "true" ] +} diff --git a/charts/consul/test/unit/partition-init-cleanup-job.bats b/charts/consul/test/unit/partition-init-cleanup-job.bats new file mode 100644 index 0000000000..efba755073 --- /dev/null +++ b/charts/consul/test/unit/partition-init-cleanup-job.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load _helpers + +@test "partitionInitCleanup/Job: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-job.yaml \ + . +} + +@test "partitionInitCleanup/Job: enabled with global.adminPartitions.enabled=true and servers = false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-cleanup-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInitCleanup/Job: disabled with global.adminPartitions.enabled=true and servers = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} + +@test "partitionInitCleanup/Job: disabled with global.adminPartitions.enabled=true and global.enabled = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enabled=true' \ + . +} + +@test "partitionInitCleanup/Job: disabled with global.adminPartitions.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-cleanup-podsecuritypolicy.bats b/charts/consul/test/unit/partition-init-cleanup-podsecuritypolicy.bats new file mode 100644 index 0000000000..6388bf2279 --- /dev/null +++ b/charts/consul/test/unit/partition-init-cleanup-podsecuritypolicy.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load _helpers + +@test "partitionInitCleanup/PodSecurityPolicy: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-podsecuritypolicy.yaml \ + . +} + +@test "partitionInitCleanup/PodSecurityPolicy: enabled with global.adminPartitions.enabled=true and servers = false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-cleanup-podsecuritypolicy.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInitCleanup/PodSecurityPolicy: disabled with global.adminPartitions.enabled=true and servers = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-podsecuritypolicy.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} + +@test "partitionInitCleanup/PodSecurityPolicy: disabled with global.adminPartitions.enabled=true and global.enabled = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-podsecuritypolicy.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enabled=true' \ + . +} + +@test "partitionInitCleanup/PodSecurityPolicy: disabled with global.adminPartitions.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-podsecuritypolicy.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-cleanup-role.bats b/charts/consul/test/unit/partition-init-cleanup-role.bats new file mode 100644 index 0000000000..ff2eef108a --- /dev/null +++ b/charts/consul/test/unit/partition-init-cleanup-role.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load _helpers + +@test "partitionInitCleanup/Role: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-role.yaml \ + . +} + +@test "partitionInitCleanup/Role: enabled with global.adminPartitions.enabled=true and servers = false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-cleanup-role.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInitCleanup/Role: disabled with global.adminPartitions.enabled=true and servers = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-role.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} + +@test "partitionInitCleanup/Role: disabled with global.adminPartitions.enabled=true and global.enabled = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-role.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enabled=true' \ + . +} + +@test "partitionInitCleanup/Role: disabled with global.adminPartitions.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-role.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-cleanup-rolebinding.bats b/charts/consul/test/unit/partition-init-cleanup-rolebinding.bats new file mode 100644 index 0000000000..5202297011 --- /dev/null +++ b/charts/consul/test/unit/partition-init-cleanup-rolebinding.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load _helpers + +@test "partitionInitCleanup/RoleBinding: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-rolebinding.yaml \ + . +} + +@test "partitionInitCleanup/RoleBinding: enabled with global.adminPartitions.enabled=true and servers = false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-cleanup-rolebinding.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInitCleanup/RoleBinding: disabled with global.adminPartitions.enabled=true and servers = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-rolebinding.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} + +@test "partitionInitCleanup/RoleBinding: disabled with global.adminPartitions.enabled=true and global.enabled = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-rolebinding.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enabled=true' \ + . +} + +@test "partitionInitCleanup/RoleBinding: disabled with global.adminPartitions.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-rolebinding.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-cleanup-serviceaccount.bats b/charts/consul/test/unit/partition-init-cleanup-serviceaccount.bats new file mode 100644 index 0000000000..94ec5c0dd4 --- /dev/null +++ b/charts/consul/test/unit/partition-init-cleanup-serviceaccount.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load _helpers + +@test "partitionInitCleanup/ServiceAccount: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-serviceaccount.yaml \ + . +} + +@test "partitionInitCleanup/ServiceAccount: enabled with global.adminPartitions.enabled=true and servers = false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-cleanup-serviceaccount.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInitCleanup/ServiceAccount: disabled with global.adminPartitions.enabled=true and servers = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-serviceaccount.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} + +@test "partitionInitCleanup/ServiceAccount: disabled with global.adminPartitions.enabled=true and global.enabled = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-serviceaccount.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enabled=true' \ + . +} + +@test "partitionInitCleanup/ServiceAccount: disabled with global.adminPartitions.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-cleanup-serviceaccount.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats new file mode 100644 index 0000000000..e093a4197b --- /dev/null +++ b/charts/consul/test/unit/partition-init-job.bats @@ -0,0 +1,203 @@ +#!/usr/bin/env bats + +load _helpers + +@test "partitionInit/Job: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-job.yaml \ + . +} + +@test "partitionInit/Job: enabled with global.adminPartitions.enabled=true and servers = false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInit/Job: disabled with global.adminPartitions.enabled=true and servers = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} + +@test "partitionInit/Job: disabled with global.adminPartitions.enabled=true and global.enabled = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enabled=true' \ + . +} + +@test "partitionInit/Job: disabled with global.adminPartitions.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} + +#-------------------------------------------------------------------- +# global.tls.enabled + +@test "partitionInit/Job: sets TLS flags when global.tls.enabled" { + cd `chart_dir` + local command=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.enabled=false' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq -r '.spec.template.spec.containers[0].command' | tee /dev/stderr) + + local actual + actual=$(echo $command | jq -r '. | any(contains("-use-https"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + actual=$(echo $command | jq -r '. | any(contains("-consul-ca-cert=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + actual=$(echo $command | jq -r '. | any(contains("-server-port=8501"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInit/Job: can overwrite CA secret with the provided one" { + cd `chart_dir` + local ca_cert_volume=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.enabled=false' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo-ca-cert' \ + --set 'global.tls.caCert.secretKey=key' \ + --set 'global.tls.caKey.secretName=foo-ca-key' \ + --set 'global.tls.caKey.secretKey=key' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[] | select(.name=="consul-ca-cert")' | tee /dev/stderr) + + # check that the provided ca cert secret is attached as a volume + local actual + actual=$(echo $ca_cert_volume | jq -r '.secret.secretName' | tee /dev/stderr) + [ "${actual}" = "foo-ca-cert" ] + + # check that the volume uses the provided secret key + actual=$(echo $ca_cert_volume | jq -r '.secret.items[0].key' | tee /dev/stderr) + [ "${actual}" = "key" ] +} + +#-------------------------------------------------------------------- +# global.acls.bootstrapToken + +@test "partitionInit/Job: -bootstrap-token-file is not set by default" { + cd `chart_dir` + local object=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.enabled=false' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr) + + # Test the flag is not set. + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file"))' | tee /dev/stderr) + [ "${actual}" = "false" ] + + # Test the volume doesn't exist + local actual=$(echo "$object" | + yq '.spec.template.spec.volumes | length == 0' | tee /dev/stderr) + [ "${actual}" = "true" ] + + # Test the volume mount doesn't exist + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].volumeMounts | length == 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInit/Job: -bootstrap-token-file is not set when acls.bootstrapToken.secretName is set but secretKey is not" { + cd `chart_dir` + local object=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.enabled=false' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.acls.bootstrapToken.secretName=name' \ + . | tee /dev/stderr) + + # Test the flag is not set. + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file"))' | tee /dev/stderr) + [ "${actual}" = "false" ] + + # Test the volume doesn't exist + local actual=$(echo "$object" | + yq '.spec.template.spec.volumes | length == 0' | tee /dev/stderr) + [ "${actual}" = "true" ] + + # Test the volume mount doesn't exist + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].volumeMounts | length == 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInit/Job: -bootstrap-token-file is not set when acls.bootstrapToken.secretKey is set but secretName is not" { + cd `chart_dir` + local object=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.enabled=false' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.acls.bootstrapToken.secretKey=key' \ + . | tee /dev/stderr) + + # Test the flag is not set. + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file"))' | tee /dev/stderr) + [ "${actual}" = "false" ] + + # Test the volume doesn't exist + local actual=$(echo "$object" | + yq '.spec.template.spec.volumes | length == 0' | tee /dev/stderr) + [ "${actual}" = "true" ] + + # Test the volume mount doesn't exist + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].volumeMounts | length == 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInit/Job: -bootstrap-token-file is set when acls.bootstrapToken.secretKey and secretName are set" { + cd `chart_dir` + local object=$(helm template \ + -s templates/partition-init-job.yaml \ + --set 'global.enabled=false' \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.acls.manageSystemACLs=true' \ + --set 'global.acls.bootstrapToken.secretName=name' \ + --set 'global.acls.bootstrapToken.secretKey=key' \ + . | tee /dev/stderr) + + # Test the -bootstrap-token-file flag is set. + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file=/consul/acl/tokens/bootstrap-token"))' | tee /dev/stderr) + [ "${actual}" = "true" ] + + # Test the volume exists + local actual=$(echo "$object" | + yq '.spec.template.spec.volumes | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) + [ "${actual}" = "true" ] + + # Test the volume mount exists + local actual=$(echo "$object" | + yq '.spec.template.spec.containers[0].volumeMounts | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) + [ "${actual}" = "true" ] +} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-podsecuritypolicy.bats b/charts/consul/test/unit/partition-init-podsecuritypolicy.bats new file mode 100644 index 0000000000..3dd1a1e5c4 --- /dev/null +++ b/charts/consul/test/unit/partition-init-podsecuritypolicy.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load _helpers + +@test "partitionInit/PodSecurityPolicy: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-podsecuritypolicy.yaml \ + . +} + +@test "partitionInit/PodSecurityPolicy: enabled with global.adminPartitions.enabled=true and servers = false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-podsecuritypolicy.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInit/PodSecurityPolicy: disabled with global.adminPartitions.enabled=true and servers = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-podsecuritypolicy.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} + +@test "partitionInit/PodSecurityPolicy: disabled with global.adminPartitions.enabled=true and global.enabled = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-podsecuritypolicy.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enabled=true' \ + . +} + +@test "partitionInit/PodSecurityPolicy: disabled with global.adminPartitions.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-podsecuritypolicy.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-role.bats b/charts/consul/test/unit/partition-init-role.bats new file mode 100644 index 0000000000..5ea6bf86f0 --- /dev/null +++ b/charts/consul/test/unit/partition-init-role.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load _helpers + +@test "partitionInit/Role: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-role.yaml \ + . +} + +@test "partitionInit/Role: enabled with global.adminPartitions.enabled=true and servers = false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-role.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInit/Role: disabled with global.adminPartitions.enabled=true and servers = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-role.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} + +@test "partitionInit/Role: disabled with global.adminPartitions.enabled=true and global.enabled = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-role.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enabled=true' \ + . +} + +@test "partitionInit/Role: disabled with global.adminPartitions.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-role.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-rolebinding.bats b/charts/consul/test/unit/partition-init-rolebinding.bats new file mode 100644 index 0000000000..562c7d0f56 --- /dev/null +++ b/charts/consul/test/unit/partition-init-rolebinding.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load _helpers + +@test "partitionInit/RoleBinding: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-rolebinding.yaml \ + . +} + +@test "partitionInit/RoleBinding: enabled with global.adminPartitions.enabled=true and servers = false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-rolebinding.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInit/RoleBinding: disabled with global.adminPartitions.enabled=true and servers = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-rolebinding.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} + +@test "partitionInit/RoleBinding: disabled with global.adminPartitions.enabled=true and global.enabled = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-rolebinding.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enabled=true' \ + . +} + +@test "partitionInit/RoleBinding: disabled with global.adminPartitions.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-rolebinding.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-serviceaccount.bats b/charts/consul/test/unit/partition-init-serviceaccount.bats new file mode 100644 index 0000000000..02ce1a3c41 --- /dev/null +++ b/charts/consul/test/unit/partition-init-serviceaccount.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load _helpers + +@test "partitionInit/ServiceAccount: disabled by default" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-serviceaccount.yaml \ + . +} + +@test "partitionInit/ServiceAccount: enabled with global.adminPartitions.enabled=true and servers = false" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/partition-init-serviceaccount.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=false' \ + . | tee /dev/stderr | + yq 'length > 0' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +@test "partitionInit/ServiceAccount: disabled with global.adminPartitions.enabled=true and servers = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-serviceaccount.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} + +@test "partitionInit/ServiceAccount: disabled with global.adminPartitions.enabled=true and global.enabled = true" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-serviceaccount.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'global.enabled=true' \ + . +} + +@test "partitionInit/ServiceAccount: disabled with global.adminPartitions.enabled=false" { + cd `chart_dir` + assert_empty helm template \ + -s templates/partition-init-serviceaccount.yaml \ + --set 'global.adminPartitions.enabled=true' \ + --set 'server.enabled=true' \ + . +} \ No newline at end of file diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 0703aab48b..cdf22746a1 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -29,6 +29,10 @@ global: # Consul into Kubernetes will have, e.g. `service-name.service.consul`. domain: consul + adminPartitions: + enabled: false + name: "default" + # The name (and tag) of the Consul Docker image for clients and servers. # This can be overridden per component. This should be pinned to a specific # version tag, otherwise you may inadvertently upgrade your Consul version. diff --git a/control-plane/commands.go b/control-plane/commands.go index a08c15b611..465ccbdd50 100644 --- a/control-plane/commands.go +++ b/control-plane/commands.go @@ -11,6 +11,7 @@ import ( cmdDeleteCompletedJob "github.com/hashicorp/consul-k8s/control-plane/subcommand/delete-completed-job" cmdGetConsulClientCA "github.com/hashicorp/consul-k8s/control-plane/subcommand/get-consul-client-ca" cmdInjectConnect "github.com/hashicorp/consul-k8s/control-plane/subcommand/inject-connect" + cmdPartitionInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/partition-init" cmdServerACLInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/server-acl-init" cmdServiceAddress "github.com/hashicorp/consul-k8s/control-plane/subcommand/service-address" cmdSyncCatalog "github.com/hashicorp/consul-k8s/control-plane/subcommand/sync-catalog" @@ -48,6 +49,10 @@ func init() { return &cmdServerACLInit.Command{UI: ui}, nil }, + "partition-init": func() (cli.Command, error) { + return &cmdPartitionInit.Command{UI: ui}, nil + }, + "sync-catalog": func() (cli.Command, error) { return &cmdSyncCatalog.Command{UI: ui}, nil }, diff --git a/control-plane/go.mod b/control-plane/go.mod index cff0ca7070..b15a09db4b 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/go-cmp v0.5.6 github.com/google/go-querystring v1.0.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/hashicorp/consul/api v1.9.0 + github.com/hashicorp/consul/api v1.4.1-0.20210827004034-d2e50fd130ae github.com/hashicorp/consul/sdk v0.8.0 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f diff --git a/control-plane/go.sum b/control-plane/go.sum index 9b7dcb4397..c3d4b34654 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -284,9 +284,10 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.9.0 h1:T6dKIWcaihG2c21YUi0BMAHbJanVXiYuz+mPgqxY3N4= -github.com/hashicorp/consul/api v1.9.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.4.1-0.20210827004034-d2e50fd130ae h1:eWcikXQgBN6yrsbANCTieR1uT+a7WoYYvGUFj8enPow= +github.com/hashicorp/consul/api v1.4.1-0.20210827004034-d2e50fd130ae/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.7.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= github.com/hashicorp/consul/sdk v0.8.0 h1:OJtKBtEjboEZvG6AOUdh4Z1Zbyu0WcxQ0qatRrZHTVU= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= diff --git a/control-plane/subcommand/partition-init/command.go b/control-plane/subcommand/partition-init/command.go new file mode 100644 index 0000000000..093411a952 --- /dev/null +++ b/control-plane/subcommand/partition-init/command.go @@ -0,0 +1,208 @@ +package partition_init + +import ( + "context" + "errors" + "flag" + "fmt" + "strings" + "sync" + "time" + + "github.com/hashicorp/consul-k8s/control-plane/consul" + godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + k8sflags "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/go-discover" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" +) + +type Command struct { + UI cli.Ui + + flags *flag.FlagSet + k8s *k8sflags.K8SFlags + + flagPartitionName string + + // Flags to configure Consul connection + flagServerAddresses []string + flagServerPort uint + flagConsulCACert string + flagConsulTLSServerName string + flagUseHTTPS bool + + flagLogLevel string + flagLogJSON bool + flagTimeout time.Duration + + // cmdTimeout is cancelled when the command timeout is reached. + cmdTimeout context.Context + retryDuration time.Duration + + // log + log hclog.Logger + + once sync.Once + help string + + providers map[string]discover.Provider +} + +func (c *Command) init() { + c.flags = flag.NewFlagSet("", flag.ContinueOnError) + + c.flags.StringVar(&c.flagPartitionName, "partition-name", "", "The name of the partition being created.") + + c.flags.Var((*flags.AppendSliceValue)(&c.flagServerAddresses), "server-address", + "The IP, DNS name or the cloud auto-join string of the Consul server(s). If providing IPs or DNS names, may be specified multiple times. "+ + "At least one value is required.") + c.flags.UintVar(&c.flagServerPort, "server-port", 8500, "The HTTP or HTTPS port of the Consul server. Defaults to 8500.") + c.flags.StringVar(&c.flagConsulCACert, "consul-ca-cert", "", + "Path to the PEM-encoded CA certificate of the Consul cluster.") + c.flags.StringVar(&c.flagConsulTLSServerName, "consul-tls-server-name", "", + "The server name to set as the SNI header when sending HTTPS requests to Consul.") + c.flags.BoolVar(&c.flagUseHTTPS, "use-https", false, + "Toggle for using HTTPS for all API calls to Consul.") + + c.flags.DurationVar(&c.flagTimeout, "timeout", 10*time.Minute, + "How long we'll try to bootstrap ACLs for before timing out, e.g. 1ms, 2s, 3m") + c.flags.StringVar(&c.flagLogLevel, "log-level", "info", + "Log verbosity level. Supported values (in order of detail) are \"trace\", "+ + "\"debug\", \"info\", \"warn\", and \"error\".") + c.flags.BoolVar(&c.flagLogJSON, "log-json", false, + "Enable or disable JSON output format for logging.") + + c.k8s = &k8sflags.K8SFlags{} + flags.Merge(c.flags, c.k8s.Flags()) + c.help = flags.Usage(help, c.flags) + + // Default retry to 1s. This is exposed for setting in tests. + if c.retryDuration == 0 { + c.retryDuration = 1 * time.Second + } +} + +func (c *Command) Synopsis() string { return synopsis } + +func (c *Command) Help() string { + c.once.Do(c.init) + return c.help +} + +// Run bootstraps ACLs on Consul servers and writes the bootstrap token to a +// Kubernetes secret. +// Given various flags, it will also create policies and associated ACL tokens +// and store the tokens as Kubernetes Secrets. +// The function will retry its tasks indefinitely until they are complete. +func (c *Command) Run(args []string) int { + c.once.Do(c.init) + if err := c.flags.Parse(args); err != nil { + return 1 + } + if len(c.flags.Args()) > 0 { + c.UI.Error("Should have no non-flag arguments.") + return 1 + } + + // Validate flags + if err := c.validateFlags(); err != nil { + c.UI.Error(err.Error()) + return 1 + } + var cancel context.CancelFunc + c.cmdTimeout, cancel = context.WithTimeout(context.Background(), c.flagTimeout) + // The context will only ever be intentionally ended by the timeout. + defer cancel() + + var err error + c.log, err = common.Logger(c.flagLogLevel, c.flagLogJSON) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + + serverAddresses := c.flagServerAddresses + // Check if the provided addresses contain a cloud-auto join string. + // If yes, call godiscover to discover addresses of the Consul servers. + if len(c.flagServerAddresses) == 1 && strings.Contains(c.flagServerAddresses[0], "provider=") { + var err error + serverAddresses, err = godiscover.ConsulServerAddresses(c.flagServerAddresses[0], c.providers, c.log) + if err != nil { + c.UI.Error(fmt.Sprintf("Unable to discover any Consul addresses from %q: %s", c.flagServerAddresses[0], err)) + return 1 + } + } + + scheme := "http" + if c.flagUseHTTPS { + scheme = "https" + } + + serverAddr := fmt.Sprintf("%s:%d", serverAddresses[0], c.flagServerPort) + consulClient, err := consul.NewClient(&api.Config{ + Address: serverAddr, + Scheme: scheme, + Token: "", // TODO: Figure out if a token is required here and which one that is. + TLSConfig: api.TLSConfig{ + Address: c.flagConsulTLSServerName, + CAFile: c.flagConsulCACert, + }, + }) + if err != nil { + c.log.Error(fmt.Sprintf("Error creating Consul client for addr %q: %s", serverAddr, err)) + return 1 + } + + partition, _, err := consulClient.Partitions().Read(c.cmdTimeout, c.flagPartitionName, &api.QueryOptions{}) + // The API does not return an error if the Partition does not exist. It returns a nil Partition. + if partition == nil { + for { + // Retry Admin Partition creation until it succeeds, or we reach the command timeout. + _, _, err = consulClient.Partitions().Create(c.cmdTimeout, &api.AdminPartition{Name: c.flagPartitionName}, &api.WriteOptions{}) + if err == nil { + c.log.Info(fmt.Sprintf("Successfully created Admin Partition '%s'", c.flagPartitionName)) + return 0 + } + c.log.Error(fmt.Sprintf("Error creating partition '%s': '%s'", c.flagPartitionName, err)) + c.log.Info("Retrying in " + c.retryDuration.String()) + // Wait on either the retry duration (in which case we continue) or the + // overall command timeout. + select { + case <-time.After(c.retryDuration): + continue + case <-c.cmdTimeout.Done(): + return 1 + } + } + } else if err != nil { + c.log.Error(fmt.Sprintf("Error reading Partition '%s' from Consul: %s", c.flagPartitionName, err)) + return 1 + } + c.log.Info(fmt.Sprintf("Admin Partition '%s' exists. Exiting.", c.flagPartitionName)) + return 0 +} + +func (c *Command) validateFlags() error { + if len(c.flagServerAddresses) == 0 { + return errors.New("-server-address must be set at least once") + } + + if c.flagPartitionName == "" { + return errors.New("-partition-name must be set") + } + return nil +} + +const synopsis = "Initialize an Admin Partition on Consul." +const help = ` +Usage: consul-k8s-control-plane partition-init [options] + + Bootstraps Consul with non-default Admin Partitions. + It will run indefinitely the partition has been created. It is idempotent + and safe to run multiple times. + +` diff --git a/control-plane/subcommand/partition-init/command_ent_test.go b/control-plane/subcommand/partition-init/command_ent_test.go new file mode 100644 index 0000000000..3d09ae5df0 --- /dev/null +++ b/control-plane/subcommand/partition-init/command_ent_test.go @@ -0,0 +1,74 @@ +//go:build enterprise +// +build enterprise + +package partition_init + +import ( + "strings" + "testing" + + "github.com/hashicorp/consul/sdk/testutil" + "github.com/mitchellh/cli" + "github.com/stretchr/testify/require" +) + +func TestRun_FlagValidation(t *testing.T) { + t.Parallel() + + cases := []struct { + flags []string + expErr string + }{ + { + flags: nil, + expErr: "-server-address must be set at least once", + }, + { + flags: []string{"-server-address", "foo"}, + expErr: "-partition-name must be set", + }, + { + flags: []string{"-server-address", "foo", "-partition-name", "bar", "-log-level", "invalid"}, + expErr: "unknown log level: invalid", + }, + } + + for _, c := range cases { + t.Run(c.expErr, func(tt *testing.T) { + ui := cli.NewMockUi() + cmd := Command{UI: ui} + exitCode := cmd.Run(c.flags) + require.Equal(tt, 1, exitCode, ui.ErrorWriter.String()) + require.Contains(tt, ui.ErrorWriter.String(), c.expErr) + }) + } +} + +func TestRun_PartitionCreate(t *testing.T) { + partitionName := "test-partition" + + a, err := testutil.NewTestServerConfigT(t, nil) + require.NoError(t, err) + a.WaitForLeader(t) + defer func() { + err := a.Stop() + require.NoError(t, err) + }() + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + } + cmd.init() + args := []string{ + "-server-address=" + strings.Split(a.HTTPAddr, ":")[0], + "-server-port=" + strings.Split(a.HTTPAddr, ":")[1], + "-partition-name", partitionName, + } + + responseCode := cmd.Run(args) + + require.Equal(t, 0, responseCode) +} + +// TODO: Write tests with ACLs enabled From 854c25ae0fd1dfec212e3a0afa821fa194cc9c8f Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Mon, 30 Aug 2021 17:23:05 -0400 Subject: [PATCH 2/5] Migrate partition-init to pre-install hook. - This removes any requirement on a post-install cleanup job. --- .../templates/partition-init-cleanup-job.yaml | 61 ------------------- ...tition-init-cleanup-podsecuritypolicy.yaml | 35 ----------- .../partition-init-cleanup-role.yaml | 25 -------- .../partition-init-cleanup-rolebinding.yaml | 20 ------ ...partition-init-cleanup-serviceaccount.yaml | 19 ------ .../consul/templates/partition-init-job.yaml | 4 ++ .../partition-init-podsecuritypolicy.yaml | 3 + .../consul/templates/partition-init-role.yaml | 3 + .../templates/partition-init-rolebinding.yaml | 3 + .../partition-init-serviceaccount.yaml | 3 + .../test/unit/partition-init-cleanup-job.bats | 48 --------------- ...tition-init-cleanup-podsecuritypolicy.bats | 48 --------------- .../unit/partition-init-cleanup-role.bats | 48 --------------- .../partition-init-cleanup-rolebinding.bats | 48 --------------- ...partition-init-cleanup-serviceaccount.bats | 48 --------------- 15 files changed, 16 insertions(+), 400 deletions(-) delete mode 100644 charts/consul/templates/partition-init-cleanup-job.yaml delete mode 100644 charts/consul/templates/partition-init-cleanup-podsecuritypolicy.yaml delete mode 100644 charts/consul/templates/partition-init-cleanup-role.yaml delete mode 100644 charts/consul/templates/partition-init-cleanup-rolebinding.yaml delete mode 100644 charts/consul/templates/partition-init-cleanup-serviceaccount.yaml delete mode 100644 charts/consul/test/unit/partition-init-cleanup-job.bats delete mode 100644 charts/consul/test/unit/partition-init-cleanup-podsecuritypolicy.bats delete mode 100644 charts/consul/test/unit/partition-init-cleanup-role.bats delete mode 100644 charts/consul/test/unit/partition-init-cleanup-rolebinding.bats delete mode 100644 charts/consul/test/unit/partition-init-cleanup-serviceaccount.bats diff --git a/charts/consul/templates/partition-init-cleanup-job.yaml b/charts/consul/templates/partition-init-cleanup-job.yaml deleted file mode 100644 index 8ee302d315..0000000000 --- a/charts/consul/templates/partition-init-cleanup-job.yaml +++ /dev/null @@ -1,61 +0,0 @@ -{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} -{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} -# This job deletes the partition-init job once it completes successfully. -# It runs as a helm hook because it only needs to run when the partition-init -# Job gets recreated which only happens during an install or upgrade. -# We also utilize the helm hook-delete-policy to delete this job itself. -# We want to delete the partition-init job because once it runs successfully -# it's not needed and also because if it stays around then when users run -# helm upgrade with values that change the spec of the job, Kubernetes errors -# because the job spec is immutable. If the job is deleted, then a new job -# is created and there's no error. -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ template "consul.fullname" . }}-partition-init-cleanup - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - annotations: - "helm.sh/hook": post-install,post-upgrade - "helm.sh/hook-weight": "-1" - # If the hook fails then all that happens is we didn't delete the job. - # There's no reason for *this* job to stay around in that case so delete - # regardless of success. - "helm.sh/hook-delete-policy": hook-succeeded,hook-failed -spec: - template: - metadata: - name: {{ template "consul.fullname" . }}-partition-init-cleanup - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - release: {{ .Release.Name }} - component: partition-init-cleanup - annotations: - "consul.hashicorp.com/connect-inject": "false" - spec: - restartPolicy: Never - serviceAccountName: {{ template "consul.fullname" . }}-partition-init-cleanup - containers: - - name: partition-init-cleanup - image: {{ .Values.global.imageK8S }} - command: - - consul-k8s-control-plane - args: - - delete-completed-job - - -log-level={{ .Values.global.logLevel }} - - -log-json={{ .Values.global.logJSON }} - - -k8s-namespace={{ .Release.Namespace }} - - {{ template "consul.fullname" . }}-partition-init - resources: - requests: - memory: "50Mi" - cpu: "50m" - limits: - memory: "50Mi" - cpu: "50m" -{{- end }} diff --git a/charts/consul/templates/partition-init-cleanup-podsecuritypolicy.yaml b/charts/consul/templates/partition-init-cleanup-podsecuritypolicy.yaml deleted file mode 100644 index e0bfad8350..0000000000 --- a/charts/consul/templates/partition-init-cleanup-podsecuritypolicy.yaml +++ /dev/null @@ -1,35 +0,0 @@ -{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} -{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} -apiVersion: policy/v1beta1 -kind: PodSecurityPolicy -metadata: - name: {{ template "consul.fullname" . }}-partition-init-cleanup - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -spec: - privileged: false - # Allow core volume types. - volumes: - - 'secret' - allowPrivilegeEscalation: false - # This is redundant with non-root + disallow privilege escalation, - # but we can provide it for defense in depth. - requiredDropCapabilities: - - ALL - hostNetwork: false - hostIPC: false - hostPID: false - runAsUser: - rule: 'RunAsAny' - seLinux: - rule: 'RunAsAny' - supplementalGroups: - rule: 'RunAsAny' - fsGroup: - rule: 'RunAsAny' - readOnlyRootFilesystem: false -{{- end }} diff --git a/charts/consul/templates/partition-init-cleanup-role.yaml b/charts/consul/templates/partition-init-cleanup-role.yaml deleted file mode 100644 index 9e57f1de47..0000000000 --- a/charts/consul/templates/partition-init-cleanup-role.yaml +++ /dev/null @@ -1,25 +0,0 @@ -{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} -{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ template "consul.fullname" . }}-partition-init-cleanup - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -rules: - - apiGroups: ["batch"] - resources: ["jobs"] - verbs: ["get", "delete"] -{{- if .Values.global.enablePodSecurityPolicies }} - - apiGroups: ["policy"] - resources: ["podsecuritypolicies"] - resourceNames: - - {{ template "consul.fullname" . }}-partition-init-cleanup - verbs: - - use -{{- end }} -{{- end }} diff --git a/charts/consul/templates/partition-init-cleanup-rolebinding.yaml b/charts/consul/templates/partition-init-cleanup-rolebinding.yaml deleted file mode 100644 index 15773d58a8..0000000000 --- a/charts/consul/templates/partition-init-cleanup-rolebinding.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} -{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ template "consul.fullname" . }}-partition-init-cleanup - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ template "consul.fullname" . }}-partition-init-cleanup -subjects: - - kind: ServiceAccount - name: {{ template "consul.fullname" . }}-partition-init-cleanup -{{- end }} diff --git a/charts/consul/templates/partition-init-cleanup-serviceaccount.yaml b/charts/consul/templates/partition-init-cleanup-serviceaccount.yaml deleted file mode 100644 index 7917941b3a..0000000000 --- a/charts/consul/templates/partition-init-cleanup-serviceaccount.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- $serverEnabled := (or (and (ne (.Values.server.enabled | toString) "-") .Values.server.enabled) (and (eq (.Values.server.enabled | toString) "-") .Values.global.enabled)) -}} -{{- if (and .Values.global.adminPartitions.enabled (not $serverEnabled)) }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ template "consul.fullname" . }}-partition-init-cleanup - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -{{- with .Values.global.imagePullSecrets }} -imagePullSecrets: -{{- range . }} - - name: {{ .name }} -{{- end }} -{{- end }} -{{- end }} diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index a572415dd2..0d1b43f115 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -10,6 +10,10 @@ metadata: chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "2" + "helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation spec: template: metadata: diff --git a/charts/consul/templates/partition-init-podsecuritypolicy.yaml b/charts/consul/templates/partition-init-podsecuritypolicy.yaml index ed50ac0c5b..9e2ec994cb 100644 --- a/charts/consul/templates/partition-init-podsecuritypolicy.yaml +++ b/charts/consul/templates/partition-init-podsecuritypolicy.yaml @@ -10,6 +10,9 @@ metadata: chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation spec: privileged: false # Allow core volume types. diff --git a/charts/consul/templates/partition-init-role.yaml b/charts/consul/templates/partition-init-role.yaml index f227d4f616..68f60f58f7 100644 --- a/charts/consul/templates/partition-init-role.yaml +++ b/charts/consul/templates/partition-init-role.yaml @@ -10,6 +10,9 @@ metadata: chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation rules: - apiGroups: [""] resources: diff --git a/charts/consul/templates/partition-init-rolebinding.yaml b/charts/consul/templates/partition-init-rolebinding.yaml index 5ad369a37f..61038ecfa6 100644 --- a/charts/consul/templates/partition-init-rolebinding.yaml +++ b/charts/consul/templates/partition-init-rolebinding.yaml @@ -10,6 +10,9 @@ metadata: chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation roleRef: apiGroup: rbac.authorization.k8s.io kind: Role diff --git a/charts/consul/templates/partition-init-serviceaccount.yaml b/charts/consul/templates/partition-init-serviceaccount.yaml index 41c6ca4e2a..f39e08a30f 100644 --- a/charts/consul/templates/partition-init-serviceaccount.yaml +++ b/charts/consul/templates/partition-init-serviceaccount.yaml @@ -10,6 +10,9 @@ metadata: chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation {{- with .Values.global.imagePullSecrets }} imagePullSecrets: {{- range . }} diff --git a/charts/consul/test/unit/partition-init-cleanup-job.bats b/charts/consul/test/unit/partition-init-cleanup-job.bats deleted file mode 100644 index efba755073..0000000000 --- a/charts/consul/test/unit/partition-init-cleanup-job.bats +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "partitionInitCleanup/Job: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-job.yaml \ - . -} - -@test "partitionInitCleanup/Job: enabled with global.adminPartitions.enabled=true and servers = false" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/partition-init-cleanup-job.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=false' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "partitionInitCleanup/Job: disabled with global.adminPartitions.enabled=true and servers = true" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-job.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=true' \ - . -} - -@test "partitionInitCleanup/Job: disabled with global.adminPartitions.enabled=true and global.enabled = true" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-job.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.enabled=true' \ - . -} - -@test "partitionInitCleanup/Job: disabled with global.adminPartitions.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-job.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=true' \ - . -} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-cleanup-podsecuritypolicy.bats b/charts/consul/test/unit/partition-init-cleanup-podsecuritypolicy.bats deleted file mode 100644 index 6388bf2279..0000000000 --- a/charts/consul/test/unit/partition-init-cleanup-podsecuritypolicy.bats +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "partitionInitCleanup/PodSecurityPolicy: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-podsecuritypolicy.yaml \ - . -} - -@test "partitionInitCleanup/PodSecurityPolicy: enabled with global.adminPartitions.enabled=true and servers = false" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/partition-init-cleanup-podsecuritypolicy.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=false' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "partitionInitCleanup/PodSecurityPolicy: disabled with global.adminPartitions.enabled=true and servers = true" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-podsecuritypolicy.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=true' \ - . -} - -@test "partitionInitCleanup/PodSecurityPolicy: disabled with global.adminPartitions.enabled=true and global.enabled = true" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-podsecuritypolicy.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.enabled=true' \ - . -} - -@test "partitionInitCleanup/PodSecurityPolicy: disabled with global.adminPartitions.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-podsecuritypolicy.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=true' \ - . -} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-cleanup-role.bats b/charts/consul/test/unit/partition-init-cleanup-role.bats deleted file mode 100644 index ff2eef108a..0000000000 --- a/charts/consul/test/unit/partition-init-cleanup-role.bats +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "partitionInitCleanup/Role: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-role.yaml \ - . -} - -@test "partitionInitCleanup/Role: enabled with global.adminPartitions.enabled=true and servers = false" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/partition-init-cleanup-role.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=false' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "partitionInitCleanup/Role: disabled with global.adminPartitions.enabled=true and servers = true" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-role.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=true' \ - . -} - -@test "partitionInitCleanup/Role: disabled with global.adminPartitions.enabled=true and global.enabled = true" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-role.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.enabled=true' \ - . -} - -@test "partitionInitCleanup/Role: disabled with global.adminPartitions.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-role.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=true' \ - . -} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-cleanup-rolebinding.bats b/charts/consul/test/unit/partition-init-cleanup-rolebinding.bats deleted file mode 100644 index 5202297011..0000000000 --- a/charts/consul/test/unit/partition-init-cleanup-rolebinding.bats +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "partitionInitCleanup/RoleBinding: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-rolebinding.yaml \ - . -} - -@test "partitionInitCleanup/RoleBinding: enabled with global.adminPartitions.enabled=true and servers = false" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/partition-init-cleanup-rolebinding.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=false' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "partitionInitCleanup/RoleBinding: disabled with global.adminPartitions.enabled=true and servers = true" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-rolebinding.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=true' \ - . -} - -@test "partitionInitCleanup/RoleBinding: disabled with global.adminPartitions.enabled=true and global.enabled = true" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-rolebinding.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.enabled=true' \ - . -} - -@test "partitionInitCleanup/RoleBinding: disabled with global.adminPartitions.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-rolebinding.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=true' \ - . -} \ No newline at end of file diff --git a/charts/consul/test/unit/partition-init-cleanup-serviceaccount.bats b/charts/consul/test/unit/partition-init-cleanup-serviceaccount.bats deleted file mode 100644 index 94ec5c0dd4..0000000000 --- a/charts/consul/test/unit/partition-init-cleanup-serviceaccount.bats +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "partitionInitCleanup/ServiceAccount: disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-serviceaccount.yaml \ - . -} - -@test "partitionInitCleanup/ServiceAccount: enabled with global.adminPartitions.enabled=true and servers = false" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/partition-init-cleanup-serviceaccount.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=false' \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "partitionInitCleanup/ServiceAccount: disabled with global.adminPartitions.enabled=true and servers = true" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-serviceaccount.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=true' \ - . -} - -@test "partitionInitCleanup/ServiceAccount: disabled with global.adminPartitions.enabled=true and global.enabled = true" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-serviceaccount.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.enabled=true' \ - . -} - -@test "partitionInitCleanup/ServiceAccount: disabled with global.adminPartitions.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/partition-init-cleanup-serviceaccount.yaml \ - --set 'global.adminPartitions.enabled=true' \ - --set 'server.enabled=true' \ - . -} \ No newline at end of file From 7d058e1fb6a57f9ab406101f94bcdf6633c42950 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Mon, 30 Aug 2021 17:31:19 -0400 Subject: [PATCH 3/5] Remove reference to ACLs as it cannot be tested presently --- .../consul/templates/partition-init-job.yaml | 25 +--- .../consul/test/unit/partition-init-job.bats | 107 ------------------ 2 files changed, 2 insertions(+), 130 deletions(-) diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index 0d1b43f115..97a2d83edf 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -28,9 +28,8 @@ spec: spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-partition-init - {{- if (or .Values.global.tls.enabled (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey)) }} + {{- if .Values.global.tls.enabled }} volumes: - {{- if .Values.global.tls.enabled }} - name: consul-ca-cert secret: {{- if .Values.global.tls.caCert.secretName }} @@ -41,15 +40,6 @@ spec: items: - key: {{ default "tls.crt" .Values.global.tls.caCert.secretKey }} path: tls.crt - {{- end }} - {{- if (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey) }} - - name: bootstrap-token - secret: - secretName: {{ .Values.global.acls.bootstrapToken.secretName }} - items: - - key: {{ .Values.global.acls.bootstrapToken.secretKey }} - path: bootstrap-token - {{- end }} {{- end }} containers: - name: post-install-job @@ -59,18 +49,11 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - {{- if (or .Values.global.tls.enabled (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey)) }} + {{- if .Values.global.tls.enabled }} volumeMounts: - {{- if .Values.global.tls.enabled }} - name: consul-ca-cert mountPath: /consul/tls/ca readOnly: true - {{- end }} - {{- if (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey) }} - - name: bootstrap-token - mountPath: /consul/acl/tokens - readOnly: true - {{- end }} {{- end }} command: - "/bin/sh" @@ -98,10 +81,6 @@ spec: -consul-tls-server-name={{ .Values.externalServers.tlsServerName }} \ {{- end }} {{- end }} - - {{- if (and .Values.global.acls.bootstrapToken.secretName .Values.global.acls.bootstrapToken.secretKey) }} - -bootstrap-token-file=/consul/acl/tokens/bootstrap-token \ - {{- end }} -partition-name={{ .Values.global.adminPartitions.name }} resources: requests: diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats index e093a4197b..8276255713 100644 --- a/charts/consul/test/unit/partition-init-job.bats +++ b/charts/consul/test/unit/partition-init-job.bats @@ -93,111 +93,4 @@ load _helpers # check that the volume uses the provided secret key actual=$(echo $ca_cert_volume | jq -r '.secret.items[0].key' | tee /dev/stderr) [ "${actual}" = "key" ] -} - -#-------------------------------------------------------------------- -# global.acls.bootstrapToken - -@test "partitionInit/Job: -bootstrap-token-file is not set by default" { - cd `chart_dir` - local object=$(helm template \ - -s templates/partition-init-job.yaml \ - --set 'global.enabled=false' \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr) - - # Test the flag is not set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file"))' | tee /dev/stderr) - [ "${actual}" = "false" ] - - # Test the volume doesn't exist - local actual=$(echo "$object" | - yq '.spec.template.spec.volumes | length == 0' | tee /dev/stderr) - [ "${actual}" = "true" ] - - # Test the volume mount doesn't exist - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].volumeMounts | length == 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "partitionInit/Job: -bootstrap-token-file is not set when acls.bootstrapToken.secretName is set but secretKey is not" { - cd `chart_dir` - local object=$(helm template \ - -s templates/partition-init-job.yaml \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.enabled=false' \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.acls.bootstrapToken.secretName=name' \ - . | tee /dev/stderr) - - # Test the flag is not set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file"))' | tee /dev/stderr) - [ "${actual}" = "false" ] - - # Test the volume doesn't exist - local actual=$(echo "$object" | - yq '.spec.template.spec.volumes | length == 0' | tee /dev/stderr) - [ "${actual}" = "true" ] - - # Test the volume mount doesn't exist - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].volumeMounts | length == 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "partitionInit/Job: -bootstrap-token-file is not set when acls.bootstrapToken.secretKey is set but secretName is not" { - cd `chart_dir` - local object=$(helm template \ - -s templates/partition-init-job.yaml \ - --set 'global.enabled=false' \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.acls.bootstrapToken.secretKey=key' \ - . | tee /dev/stderr) - - # Test the flag is not set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file"))' | tee /dev/stderr) - [ "${actual}" = "false" ] - - # Test the volume doesn't exist - local actual=$(echo "$object" | - yq '.spec.template.spec.volumes | length == 0' | tee /dev/stderr) - [ "${actual}" = "true" ] - - # Test the volume mount doesn't exist - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].volumeMounts | length == 0' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "partitionInit/Job: -bootstrap-token-file is set when acls.bootstrapToken.secretKey and secretName are set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/partition-init-job.yaml \ - --set 'global.enabled=false' \ - --set 'global.adminPartitions.enabled=true' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.acls.bootstrapToken.secretName=name' \ - --set 'global.acls.bootstrapToken.secretKey=key' \ - . | tee /dev/stderr) - - # Test the -bootstrap-token-file flag is set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].command | any(contains("-bootstrap-token-file=/consul/acl/tokens/bootstrap-token"))' | tee /dev/stderr) - [ "${actual}" = "true" ] - - # Test the volume exists - local actual=$(echo "$object" | - yq '.spec.template.spec.volumes | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) - [ "${actual}" = "true" ] - - # Test the volume mount exists - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[0].volumeMounts | map(select(.name == "bootstrap-token")) | length == 1' | tee /dev/stderr) - [ "${actual}" = "true" ] } \ No newline at end of file From 3f95619976b1a9cacf9193deaffda30d5d936297 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Thu, 2 Sep 2021 16:35:19 -0400 Subject: [PATCH 4/5] Address Code Review comments --- .../consul/test/unit/partition-init-job.bats | 6 +- .../partition-init-podsecuritypolicy.bats | 6 +- .../consul/test/unit/partition-init-role.bats | 6 +- .../test/unit/partition-init-rolebinding.bats | 6 +- .../unit/partition-init-serviceaccount.bats | 6 +- charts/consul/values.yaml | 8 ++ control-plane/subcommand/common/common.go | 10 ++ .../subcommand/common/common_test.go | 36 +++++++ .../subcommand/partition-init/command.go | 85 ++++++++--------- .../partition-init/command_ent_test.go | 94 +++++++++++++++++-- .../subcommand/server-acl-init/command.go | 18 +--- 11 files changed, 197 insertions(+), 84 deletions(-) diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats index 8276255713..ac6d5ccd27 100644 --- a/charts/consul/test/unit/partition-init-job.bats +++ b/charts/consul/test/unit/partition-init-job.bats @@ -22,7 +22,7 @@ load _helpers @test "partitionInit/Job: disabled with global.adminPartitions.enabled=true and servers = true" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-job.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'server.enabled=true' \ @@ -31,7 +31,7 @@ load _helpers @test "partitionInit/Job: disabled with global.adminPartitions.enabled=true and global.enabled = true" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-job.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'global.enabled=true' \ @@ -40,7 +40,7 @@ load _helpers @test "partitionInit/Job: disabled with global.adminPartitions.enabled=false" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-job.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'server.enabled=true' \ diff --git a/charts/consul/test/unit/partition-init-podsecuritypolicy.bats b/charts/consul/test/unit/partition-init-podsecuritypolicy.bats index 3dd1a1e5c4..d00c915f6e 100644 --- a/charts/consul/test/unit/partition-init-podsecuritypolicy.bats +++ b/charts/consul/test/unit/partition-init-podsecuritypolicy.bats @@ -22,7 +22,7 @@ load _helpers @test "partitionInit/PodSecurityPolicy: disabled with global.adminPartitions.enabled=true and servers = true" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-podsecuritypolicy.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'server.enabled=true' \ @@ -31,7 +31,7 @@ load _helpers @test "partitionInit/PodSecurityPolicy: disabled with global.adminPartitions.enabled=true and global.enabled = true" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-podsecuritypolicy.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'global.enabled=true' \ @@ -40,7 +40,7 @@ load _helpers @test "partitionInit/PodSecurityPolicy: disabled with global.adminPartitions.enabled=false" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-podsecuritypolicy.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'server.enabled=true' \ diff --git a/charts/consul/test/unit/partition-init-role.bats b/charts/consul/test/unit/partition-init-role.bats index 5ea6bf86f0..c434aa3d87 100644 --- a/charts/consul/test/unit/partition-init-role.bats +++ b/charts/consul/test/unit/partition-init-role.bats @@ -22,7 +22,7 @@ load _helpers @test "partitionInit/Role: disabled with global.adminPartitions.enabled=true and servers = true" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-role.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'server.enabled=true' \ @@ -31,7 +31,7 @@ load _helpers @test "partitionInit/Role: disabled with global.adminPartitions.enabled=true and global.enabled = true" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-role.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'global.enabled=true' \ @@ -40,7 +40,7 @@ load _helpers @test "partitionInit/Role: disabled with global.adminPartitions.enabled=false" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-role.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'server.enabled=true' \ diff --git a/charts/consul/test/unit/partition-init-rolebinding.bats b/charts/consul/test/unit/partition-init-rolebinding.bats index 562c7d0f56..d96f6e6cd3 100644 --- a/charts/consul/test/unit/partition-init-rolebinding.bats +++ b/charts/consul/test/unit/partition-init-rolebinding.bats @@ -22,7 +22,7 @@ load _helpers @test "partitionInit/RoleBinding: disabled with global.adminPartitions.enabled=true and servers = true" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-rolebinding.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'server.enabled=true' \ @@ -31,7 +31,7 @@ load _helpers @test "partitionInit/RoleBinding: disabled with global.adminPartitions.enabled=true and global.enabled = true" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-rolebinding.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'global.enabled=true' \ @@ -40,7 +40,7 @@ load _helpers @test "partitionInit/RoleBinding: disabled with global.adminPartitions.enabled=false" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-rolebinding.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'server.enabled=true' \ diff --git a/charts/consul/test/unit/partition-init-serviceaccount.bats b/charts/consul/test/unit/partition-init-serviceaccount.bats index 02ce1a3c41..6195969686 100644 --- a/charts/consul/test/unit/partition-init-serviceaccount.bats +++ b/charts/consul/test/unit/partition-init-serviceaccount.bats @@ -22,7 +22,7 @@ load _helpers @test "partitionInit/ServiceAccount: disabled with global.adminPartitions.enabled=true and servers = true" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-serviceaccount.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'server.enabled=true' \ @@ -31,7 +31,7 @@ load _helpers @test "partitionInit/ServiceAccount: disabled with global.adminPartitions.enabled=true and global.enabled = true" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-serviceaccount.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'global.enabled=true' \ @@ -40,7 +40,7 @@ load _helpers @test "partitionInit/ServiceAccount: disabled with global.adminPartitions.enabled=false" { cd `chart_dir` - assert_empty helm template \ + assert_empty helm template \ -s templates/partition-init-serviceaccount.yaml \ --set 'global.adminPartitions.enabled=true' \ --set 'server.enabled=true' \ diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index cdf22746a1..699b6ebff4 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -29,8 +29,16 @@ global: # Consul into Kubernetes will have, e.g. `service-name.service.consul`. domain: consul + # [Enterprise Only] Enabling `adminPartitions` allows creation of Admin Partitions in Kubernetes clusters. + # It additionally indicates that you are running Consul Enterprise v1.11+ with a valid Consul Enterprise + # license and would like to make use of configuration beyond registering everything into + # the `default` Consul partition. adminPartitions: + # If true, the Helm chart will enable Admin Partitions for the cluster. The clients in the server cluster + # must be installed in the default partition. enabled: false + # The name of the Admin Partition. Must be "default" in the server cluster ie the Kubernetes cluster that + # the Consul server pods are deployed onto. name: "default" # The name (and tag) of the Consul Docker image for clients and servers. diff --git a/control-plane/subcommand/common/common.go b/control-plane/subcommand/common/common.go index 6eaa14a78a..c0818b7982 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -9,7 +9,9 @@ import ( "strings" "github.com/go-logr/logr" + godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" "github.com/hashicorp/consul/api" + "github.com/hashicorp/go-discover" "github.com/hashicorp/go-hclog" "go.uber.org/zap/zapcore" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -115,3 +117,11 @@ func WriteFileWithPerms(outputFile, payload string, mode os.FileMode) error { } return os.Chmod(outputFile, mode) } + +// GetResolvedServerAddresses resolves the Consul server address if it has been provided a provider else it returns the server addresses that were input to it. +func GetResolvedServerAddresses(serverAddresses []string, providers map[string]discover.Provider, logger hclog.Logger) ([]string, error) { + if len(serverAddresses) != 1 || !strings.Contains(serverAddresses[0], "provider=") { + return serverAddresses, nil + } + return godiscover.ConsulServerAddresses(serverAddresses[0], providers, logger) +} diff --git a/control-plane/subcommand/common/common_test.go b/control-plane/subcommand/common/common_test.go index 65268defe1..e2bac00e15 100644 --- a/control-plane/subcommand/common/common_test.go +++ b/control-plane/subcommand/common/common_test.go @@ -11,7 +11,11 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover/mocks" "github.com/hashicorp/consul/api" + "github.com/hashicorp/go-discover" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -168,6 +172,38 @@ func TestWriteFileWithPerms(t *testing.T) { require.Equal(t, payload, string(data)) } +func TestGetResolvedServerAddresses(t *testing.T) { + cases := map[string]struct { + inputServerAddresses []string + providerMap func() map[string]discover.Provider + expectedServerAddresses []string + }{ + "without providers": { + inputServerAddresses: []string{"foo.bar", "hello.car"}, + providerMap: func() map[string]discover.Provider { + return nil + }, + expectedServerAddresses: []string{"foo.bar", "hello.car"}, + }, + "mock provider": { + inputServerAddresses: []string{"provider=mock"}, + providerMap: func() map[string]discover.Provider { + provider := new(mocks.MockProvider) + provider.On("Addrs", mock.Anything, mock.Anything).Return([]string{"127.0.0.1", "foo.bar"}, nil) + providers := make(map[string]discover.Provider) + providers["mock"] = provider + return providers + }, + expectedServerAddresses: []string{"127.0.0.1", "foo.bar"}, + }, + } + for _, testCase := range cases { + addresses, err := GetResolvedServerAddresses(testCase.inputServerAddresses, testCase.providerMap(), hclog.NewNullLogger()) + require.NoError(t, err) + require.Equal(t, testCase.expectedServerAddresses, addresses) + } +} + // startMockServer starts an httptest server used to mock a Consul server's // /v1/acl/login endpoint. apiCallCounter will be incremented on each call to /v1/acl/login. // It returns a consul client pointing at the server. diff --git a/control-plane/subcommand/partition-init/command.go b/control-plane/subcommand/partition-init/command.go index 093411a952..bdbb4df21d 100644 --- a/control-plane/subcommand/partition-init/command.go +++ b/control-plane/subcommand/partition-init/command.go @@ -5,12 +5,10 @@ import ( "errors" "flag" "fmt" - "strings" "sync" "time" "github.com/hashicorp/consul-k8s/control-plane/consul" - godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" k8sflags "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" @@ -39,8 +37,8 @@ type Command struct { flagLogJSON bool flagTimeout time.Duration - // cmdTimeout is cancelled when the command timeout is reached. - cmdTimeout context.Context + // ctx is cancelled when the command timeout is reached. + ctx context.Context retryDuration time.Duration // log @@ -69,7 +67,7 @@ func (c *Command) init() { "Toggle for using HTTPS for all API calls to Consul.") c.flags.DurationVar(&c.flagTimeout, "timeout", 10*time.Minute, - "How long we'll try to bootstrap ACLs for before timing out, e.g. 1ms, 2s, 3m") + "How long we'll try to bootstrap Partitions for before timing out, e.g. 1ms, 2s, 3m") c.flags.StringVar(&c.flagLogLevel, "log-level", "info", "Log verbosity level. Supported values (in order of detail) are \"trace\", "+ "\"debug\", \"info\", \"warn\", and \"error\".") @@ -93,11 +91,8 @@ func (c *Command) Help() string { return c.help } -// Run bootstraps ACLs on Consul servers and writes the bootstrap token to a -// Kubernetes secret. -// Given various flags, it will also create policies and associated ACL tokens -// and store the tokens as Kubernetes Secrets. -// The function will retry its tasks indefinitely until they are complete. +// Run bootstraps Admin Partitions on Consul servers. +// The function will retry its tasks until success, or it exceeds its timeout. func (c *Command) Run(args []string) int { c.once.Do(c.init) if err := c.flags.Parse(args); err != nil { @@ -114,7 +109,7 @@ func (c *Command) Run(args []string) int { return 1 } var cancel context.CancelFunc - c.cmdTimeout, cancel = context.WithTimeout(context.Background(), c.flagTimeout) + c.ctx, cancel = context.WithTimeout(context.Background(), c.flagTimeout) // The context will only ever be intentionally ended by the timeout. defer cancel() @@ -125,65 +120,61 @@ func (c *Command) Run(args []string) int { return 1 } - serverAddresses := c.flagServerAddresses - // Check if the provided addresses contain a cloud-auto join string. - // If yes, call godiscover to discover addresses of the Consul servers. - if len(c.flagServerAddresses) == 1 && strings.Contains(c.flagServerAddresses[0], "provider=") { - var err error - serverAddresses, err = godiscover.ConsulServerAddresses(c.flagServerAddresses[0], c.providers, c.log) - if err != nil { - c.UI.Error(fmt.Sprintf("Unable to discover any Consul addresses from %q: %s", c.flagServerAddresses[0], err)) - return 1 - } + serverAddresses, err := common.GetResolvedServerAddresses(c.flagServerAddresses, c.providers, c.log) + if err != nil { + c.UI.Error(fmt.Sprintf("Unable to discover any Consul addresses from %q: %s", c.flagServerAddresses[0], err)) + return 1 } scheme := "http" if c.flagUseHTTPS { scheme = "https" } - + // For all of the next operations we'll need a Consul client. serverAddr := fmt.Sprintf("%s:%d", serverAddresses[0], c.flagServerPort) consulClient, err := consul.NewClient(&api.Config{ Address: serverAddr, Scheme: scheme, - Token: "", // TODO: Figure out if a token is required here and which one that is. TLSConfig: api.TLSConfig{ Address: c.flagConsulTLSServerName, CAFile: c.flagConsulCACert, }, }) if err != nil { - c.log.Error(fmt.Sprintf("Error creating Consul client for addr %q: %s", serverAddr, err)) + c.UI.Error(fmt.Sprintf("Error creating Consul client for addr %q: %s", serverAddr, err)) return 1 } - - partition, _, err := consulClient.Partitions().Read(c.cmdTimeout, c.flagPartitionName, &api.QueryOptions{}) - // The API does not return an error if the Partition does not exist. It returns a nil Partition. - if partition == nil { - for { + for { + partition, _, err := consulClient.Partitions().Read(c.ctx, c.flagPartitionName, nil) + // The API does not return an error if the Partition does not exist. It returns a nil Partition. + if err != nil { + c.log.Error("Error reading Partition from Consul", "name", c.flagPartitionName, "error", err.Error()) + } else if partition == nil { // Retry Admin Partition creation until it succeeds, or we reach the command timeout. - _, _, err = consulClient.Partitions().Create(c.cmdTimeout, &api.AdminPartition{Name: c.flagPartitionName}, &api.WriteOptions{}) + _, _, err = consulClient.Partitions().Create(c.ctx, &api.AdminPartition{ + Name: c.flagPartitionName, + Description: "Created by Helm installation", + }, nil) if err == nil { - c.log.Info(fmt.Sprintf("Successfully created Admin Partition '%s'", c.flagPartitionName)) + c.log.Info("Successfully created Admin Partition", "name", c.flagPartitionName) return 0 } - c.log.Error(fmt.Sprintf("Error creating partition '%s': '%s'", c.flagPartitionName, err)) - c.log.Info("Retrying in " + c.retryDuration.String()) - // Wait on either the retry duration (in which case we continue) or the - // overall command timeout. - select { - case <-time.After(c.retryDuration): - continue - case <-c.cmdTimeout.Done(): - return 1 - } + c.log.Error("Error creating partition", "name", c.flagPartitionName, "error", err.Error()) + } else { + c.log.Info("Admin Partition already exists", "name", c.flagPartitionName) + return 0 + } + // Wait on either the retry duration (in which case we continue) or the + // overall command timeout. + c.log.Info("Retrying in " + c.retryDuration.String()) + select { + case <-time.After(c.retryDuration): + continue + case <-c.ctx.Done(): + c.log.Error("Timed out attempting to create partition", "name", c.flagPartitionName) + return 1 } - } else if err != nil { - c.log.Error(fmt.Sprintf("Error reading Partition '%s' from Consul: %s", c.flagPartitionName, err)) - return 1 } - c.log.Info(fmt.Sprintf("Admin Partition '%s' exists. Exiting.", c.flagPartitionName)) - return 0 } func (c *Command) validateFlags() error { @@ -202,7 +193,7 @@ const help = ` Usage: consul-k8s-control-plane partition-init [options] Bootstraps Consul with non-default Admin Partitions. - It will run indefinitely the partition has been created. It is idempotent + It will run until the partition has been created or the operation times out. It is idempotent and safe to run multiple times. ` diff --git a/control-plane/subcommand/partition-init/command_ent_test.go b/control-plane/subcommand/partition-init/command_ent_test.go index 3d09ae5df0..39e031a8b7 100644 --- a/control-plane/subcommand/partition-init/command_ent_test.go +++ b/control-plane/subcommand/partition-init/command_ent_test.go @@ -1,12 +1,14 @@ -//go:build enterprise // +build enterprise package partition_init import ( + "context" "strings" "testing" + "time" + "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" @@ -47,13 +49,53 @@ func TestRun_FlagValidation(t *testing.T) { func TestRun_PartitionCreate(t *testing.T) { partitionName := "test-partition" - a, err := testutil.NewTestServerConfigT(t, nil) + server, err := testutil.NewTestServerConfigT(t, nil) + require.NoError(t, err) + server.WaitForLeader(t) + defer server.Stop() + + consul, err := api.NewClient(&api.Config{ + Address: server.HTTPAddr, + }) + require.NoError(t, err) + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + } + cmd.init() + args := []string{ + "-server-address=" + strings.Split(server.HTTPAddr, ":")[0], + "-server-port=" + strings.Split(server.HTTPAddr, ":")[1], + "-partition-name", partitionName, + } + + responseCode := cmd.Run(args) + + require.Equal(t, 0, responseCode) + + partition, _, err := consul.Partitions().Read(context.Background(), partitionName, nil) + require.NoError(t, err) + require.NotNil(t, partition) + require.Equal(t, partitionName, partition.Name) +} + +func TestRun_PartitionExists(t *testing.T) { + partitionName := "test-partition" + + server, err := testutil.NewTestServerConfigT(t, nil) + require.NoError(t, err) + server.WaitForLeader(t) + defer server.Stop() + + consul, err := api.NewClient(&api.Config{ + Address: server.HTTPAddr, + }) + require.NoError(t, err) + + // Create the Admin Partition before the test runs. + _, _, err = consul.Partitions().Create(context.Background(), &api.AdminPartition{Name: partitionName, Description: "Created before test"}, nil) require.NoError(t, err) - a.WaitForLeader(t) - defer func() { - err := a.Stop() - require.NoError(t, err) - }() ui := cli.NewMockUi() cmd := Command{ @@ -61,14 +103,48 @@ func TestRun_PartitionCreate(t *testing.T) { } cmd.init() args := []string{ - "-server-address=" + strings.Split(a.HTTPAddr, ":")[0], - "-server-port=" + strings.Split(a.HTTPAddr, ":")[1], + "-server-address=" + strings.Split(server.HTTPAddr, ":")[0], + "-server-port=" + strings.Split(server.HTTPAddr, ":")[1], "-partition-name", partitionName, } responseCode := cmd.Run(args) require.Equal(t, 0, responseCode) + + partition, _, err := consul.Partitions().Read(context.Background(), partitionName, nil) + require.NoError(t, err) + require.NotNil(t, partition) + require.Equal(t, partitionName, partition.Name) + require.Equal(t, "Created before test", partition.Description) +} + +func TestRun_ExitsAfterTimeout(t *testing.T) { + partitionName := "test-partition" + + server, err := testutil.NewTestServerConfigT(t, nil) + require.NoError(t, err) + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + } + cmd.init() + args := []string{ + "-server-address=" + strings.Split(server.HTTPAddr, ":")[0], + "-server-port=" + strings.Split(server.HTTPAddr, ":")[1], + "-partition-name", partitionName, + "-timeout", "500ms", + } + server.Stop() + startTime := time.Now() + responseCode := cmd.Run(args) + completeTime := time.Now() + + require.Equal(t, 1, responseCode) + // While the timeout is 500ms, adding a buffer of 500ms ensures we account for + // some buffer time required for the task to run and assignments to occur. + require.WithinDuration(t, completeTime, startTime, 1*time.Second) } // TODO: Write tests with ACLs enabled diff --git a/control-plane/subcommand/server-acl-init/command.go b/control-plane/subcommand/server-acl-init/command.go index 0625b056b9..8dfdee21c8 100644 --- a/control-plane/subcommand/server-acl-init/command.go +++ b/control-plane/subcommand/server-acl-init/command.go @@ -12,7 +12,6 @@ import ( "time" "github.com/hashicorp/consul-k8s/control-plane/consul" - godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" "github.com/hashicorp/consul-k8s/control-plane/subcommand" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" @@ -287,18 +286,6 @@ func (c *Command) Run(args []string) int { return 1 } - serverAddresses := c.flagServerAddresses - // Check if the provided addresses contain a cloud-auto join string. - // If yes, call godiscover to discover addresses of the Consul servers. - if len(c.flagServerAddresses) == 1 && strings.Contains(c.flagServerAddresses[0], "provider=") { - var err error - serverAddresses, err = godiscover.ConsulServerAddresses(c.flagServerAddresses[0], c.providers, c.log) - if err != nil { - c.UI.Error(fmt.Sprintf("Unable to discover any Consul addresses from %q: %s", c.flagServerAddresses[0], err)) - return 1 - } - } - // The ClientSet might already be set if we're in a test. if c.clientset == nil { if err := c.configureKubeClient(); err != nil { @@ -307,6 +294,11 @@ func (c *Command) Run(args []string) int { } } + serverAddresses, err := common.GetResolvedServerAddresses(c.flagServerAddresses, c.providers, c.log) + if err != nil { + c.UI.Error(fmt.Sprintf("Unable to discover any Consul addresses from %q: %s", c.flagServerAddresses[0], err)) + return 1 + } scheme := "http" if c.flagUseHTTPS { scheme = "https" From 51c429209120f5837b4c0c3e1601cbe108825573 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Thu, 9 Sep 2021 10:15:39 -0400 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Nitya Dhanushkodi --- charts/consul/values.yaml | 4 ++-- control-plane/subcommand/common/common.go | 1 + control-plane/subcommand/common/common_test.go | 9 ++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 699b6ebff4..47aab7e1e7 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -31,8 +31,8 @@ global: # [Enterprise Only] Enabling `adminPartitions` allows creation of Admin Partitions in Kubernetes clusters. # It additionally indicates that you are running Consul Enterprise v1.11+ with a valid Consul Enterprise - # license and would like to make use of configuration beyond registering everything into - # the `default` Consul partition. + # license. Admin partitions enables deploying services across partitions, while sharing + # a set of Consul servers. adminPartitions: # If true, the Helm chart will enable Admin Partitions for the cluster. The clients in the server cluster # must be installed in the default partition. diff --git a/control-plane/subcommand/common/common.go b/control-plane/subcommand/common/common.go index c0818b7982..4dc2783683 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -119,6 +119,7 @@ func WriteFileWithPerms(outputFile, payload string, mode os.FileMode) error { } // GetResolvedServerAddresses resolves the Consul server address if it has been provided a provider else it returns the server addresses that were input to it. +// It attempts to use go-discover iff there is a single server address, the value of which begins with "provider=", else it returns the server addresses as is. func GetResolvedServerAddresses(serverAddresses []string, providers map[string]discover.Provider, logger hclog.Logger) ([]string, error) { if len(serverAddresses) != 1 || !strings.Contains(serverAddresses[0], "provider=") { return serverAddresses, nil diff --git a/control-plane/subcommand/common/common_test.go b/control-plane/subcommand/common/common_test.go index e2bac00e15..d5ac9a11b1 100644 --- a/control-plane/subcommand/common/common_test.go +++ b/control-plane/subcommand/common/common_test.go @@ -178,7 +178,14 @@ func TestGetResolvedServerAddresses(t *testing.T) { providerMap func() map[string]discover.Provider expectedServerAddresses []string }{ - "without providers": { + "without providers and single address": { + inputServerAddresses: []string{"foo.bar"}, + providerMap: func() map[string]discover.Provider { + return nil + }, + expectedServerAddresses: []string{"foo.bar"}, + }, + "without providers and multiple addresses": { inputServerAddresses: []string{"foo.bar", "hello.car"}, providerMap: func() map[string]discover.Provider { return nil