Skip to content

Commit 1c1ce11

Browse files
committed
operator: implement Console controller
This commit adds a standalone `Console` CR and its controller. Unlike the console stanza in the redpanda chart, this CR deploys console V3. This commit does NOT include a migration from the subchart to the new CR. That will be implemented later.
1 parent 58668bc commit 1c1ce11

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+16879
-125
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
@cluster:basic
2+
Feature: Console CRDs
3+
Background: Cluster available
4+
Given cluster "basic" is available
5+
6+
Scenario: Using clusterRef
7+
When I apply Kubernetes manifest:
8+
```yaml
9+
---
10+
apiVersion: cluster.redpanda.com/v1alpha2
11+
kind: Console
12+
metadata:
13+
name: console
14+
spec:
15+
cluster:
16+
clusterRef:
17+
name: basic
18+
```
19+
Then Console "console" will be healthy
20+
# These steps demonstrate that console is correct connected to Redpanda.
21+
And I exec "curl localhost:8080/api/schema-registry/mode" in a Pod matching "app.kubernetes.io/instance=console", it will output:
22+
```
23+
{"mode":"READWRITE"}
24+
```
25+
And I exec "curl localhost:8080/api/topics" in a Pod matching "app.kubernetes.io/instance=console", it will output:
26+
```
27+
{"topics":[{"topicName":"_schemas","isInternal":false,"partitionCount":1,"replicationFactor":1,"cleanupPolicy":"compact","documentation":"NOT_CONFIGURED","logDirSummary":{"totalSizeBytes":117}}]}
28+
```
29+
And I exec "curl localhost:8080/api/console/endpoints" in a Pod matching "app.kubernetes.io/instance=console", it will output:
30+
```
31+
{"distribution":"redpanda","endpointCompatibility":{"kafkaVersion":"between v0.11.0 and v1.0","endpoints":[{"endpoint":"/api/consumer-groups","method":"GET","isSupported":true},{"endpoint":"/api/consumer-groups/{groupId}","method":"PATCH","isSupported":true},{"endpoint":"/api/consumer-groups/{groupId}","method":"DELETE","isSupported":true},{"endpoint":"/api/users","method":"DELETE","isSupported":true},{"endpoint":"redpanda.api.console.v1alpha1.PipelineService","method":"POST","isSupported":false},{"endpoint":"redpanda.api.console.v1alpha1.SecurityService","method":"POST","isSupported":true},{"endpoint":"/api/topics/{topicName}/records","method":"DELETE","isSupported":true},{"endpoint":"/api/consumer-groups/{groupId}/offsets","method":"DELETE","isSupported":true},{"endpoint":"/api/operations/reassign-partitions","method":"PATCH","isSupported":true},{"endpoint":"/api/secrets","method":"POST","isSupported":false},{"endpoint":"/api/users","method":"GET","isSupported":true},{"endpoint":"redpanda.api.console.v1alpha1.TransformService","method":"POST","isSupported":false},{"endpoint":"redpanda.api.dataplane.v1.ACLService","method":"POST","isSupported":true},{"endpoint":"/api/secrets","method":"GET","isSupported":false},{"endpoint":"/api/secrets/{secretId}","method":"PUT","isSupported":false},{"endpoint":"/api/cluster/config","method":"GET","isSupported":true},{"endpoint":"/api/operations/reassign-partitions","method":"GET","isSupported":true},{"endpoint":"/api/quotas","method":"GET","isSupported":true},{"endpoint":"/api/users","method":"POST","isSupported":true},{"endpoint":"redpanda.api.console.v1alpha1.DebugBundleService","method":"POST","isSupported":true},{"endpoint":"/api/secrets/{secretId}","method":"DELETE","isSupported":false}]}}
32+
```
33+
34+
# Scenario: Using staticConfig
35+
# Given I apply Kubernetes manifest:
36+
# ```yaml
37+
# ---
38+
# apiVersion: cluster.redpanda.com/v1alpha2
39+
# kind: Console
40+
# metadata:
41+
# name: console
42+
# spec:
43+
# cluster:
44+
# ```

acceptance/main_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ var setupSuite = sync.OnceValues(func() (*framework.Suite, error) {
6767
CreateNamespace: true,
6868
Values: map[string]any{
6969
"installCRDs": true,
70+
"global": map[string]any{
71+
// Make leader election more aggressive as cert-manager appears to
72+
// not release it when uninstalled.
73+
"leaderElection": map[string]any{
74+
"renewDeadline": "10s",
75+
"retryPeriod": "5s",
76+
},
77+
},
7078
},
7179
}).
7280
OnFeature(func(ctx context.Context, t framework.TestingT, tags ...framework.ParsedTag) {

acceptance/steps/console.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package steps
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/stretchr/testify/require"
8+
9+
framework "github.com/redpanda-data/redpanda-operator/harpoon"
10+
redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2"
11+
)
12+
13+
func consoleIsHealthy(ctx context.Context, t framework.TestingT, name string) {
14+
key := t.ResourceKey(name)
15+
16+
t.Logf("Checking console %q is healthy", name)
17+
require.Eventually(t, func() bool {
18+
var console redpandav1alpha2.Console
19+
require.NoError(t, t.Get(ctx, key, &console))
20+
21+
upToDate := console.Generation == console.Status.ObservedGeneration
22+
hasHealthyReplicas := console.Status.ReadyReplicas == console.Status.Replicas
23+
24+
return upToDate && hasHealthyReplicas
25+
}, time.Minute, 10*time.Second)
26+
}

acceptance/steps/k8s.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
package steps
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"reflect"
78
"strings"
89
"time"
910

11+
"github.com/cucumber/godog"
12+
"github.com/stretchr/testify/assert"
1013
"github.com/stretchr/testify/require"
14+
corev1 "k8s.io/api/core/v1"
15+
"k8s.io/apimachinery/pkg/labels"
1116
"k8s.io/apimachinery/pkg/runtime/schema"
1217
"k8s.io/client-go/util/jsonpath"
1318
"sigs.k8s.io/controller-runtime/pkg/client"
1419

1520
framework "github.com/redpanda-data/redpanda-operator/harpoon"
1621
redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2"
22+
"github.com/redpanda-data/redpanda-operator/pkg/kube"
1723
)
1824

1925
// this is a nasty hack due to the fact that we can't disable the linter for typecheck
@@ -147,3 +153,30 @@ func execJSONPath(ctx context.Context, t framework.TestingT, jsonPath, groupVers
147153
}
148154
return nil
149155
}
156+
157+
func iExecInPodMatching(
158+
ctx context.Context,
159+
t framework.TestingT,
160+
cmd,
161+
selectorStr string,
162+
expected *godog.DocString,
163+
) {
164+
selector, err := labels.Parse(selectorStr)
165+
require.NoError(t, err)
166+
167+
ctl, err := kube.FromRESTConfig(t.RestConfig())
168+
require.NoError(t, err)
169+
170+
pods, err := kube.List[corev1.PodList](ctx, ctl, client.MatchingLabelsSelector{Selector: selector}, client.InNamespace(t.Namespace()))
171+
require.NoError(t, err)
172+
173+
require.True(t, len(pods.Items) > 0, "selector %q found no Pods", selector.String())
174+
175+
var stdout bytes.Buffer
176+
require.NoError(t, ctl.Exec(ctx, &pods.Items[0], kube.ExecOptions{
177+
Command: []string{"sh", "-c", cmd},
178+
Stdout: &stdout,
179+
}))
180+
181+
assert.Equal(t, expected.Content, stdout.String())
182+
}

acceptance/steps/register.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ func init() {
1515
// General scenario steps
1616
framework.RegisterStep(`^cluster "([^"]*)" is available$`, checkClusterAvailability)
1717
framework.RegisterStep(`^I apply Kubernetes manifest:$`, iApplyKubernetesManifest)
18+
framework.RegisterStep(`^I exec "([^"]+)" in a Pod matching "([^"]+)", it will output:$`, iExecInPodMatching)
1819

1920
framework.RegisterStep(`^I store "([^"]*)" of Kubernetes object with type "([^"]*)" and name "([^"]*)" as "([^"]*)"$`, recordVariable)
2021
framework.RegisterStep(`^the recorded value "([^"]*)" has the same value as "([^"]*)" of the Kubernetes object with type "([^"]*)" and name "([^"]*)"$`, assertVariableValue)
@@ -92,4 +93,7 @@ func init() {
9293
framework.RegisterStep(`^I can upgrade to the latest operator with the values:$`, iCanUpgradeToTheLatestOperatorWithTheValues)
9394
framework.RegisterStep(`^I install redpanda helm chart version "([^"]*)" with the values:$`, iInstallRedpandaHelmChartVersionWithTheValues)
9495
framework.RegisterStep(`^I install local CRDs from "([^"]*)"`, iInstallLocalCRDs)
96+
97+
// Console scenario steps
98+
framework.RegisterStep(`^Console "([^"]+)" will be healthy`, consoleIsHealthy)
9599
}

charts/console/chart/chart.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ func DotToState(dot *helmette.Dot) *console.RenderState {
5656
values := helmette.Unwrap[Values](dot.Values)
5757
templater := &templater{Dot: dot, FauxDot: newFauxDot(dot)}
5858

59+
if values.RenderValues.Secret.Authentication.JWTSigningKey == "" {
60+
values.RenderValues.Secret.Authentication.JWTSigningKey = helmette.RandAlphaNum(32)
61+
}
62+
5963
return &console.RenderState{
6064
ReleaseName: dot.Release.Name,
6165
Namespace: dot.Release.Namespace,

charts/console/chart/templates/_chart.chart.tpl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
{{- $_is_returning := false -}}
1919
{{- $values := $dot.Values.AsMap -}}
2020
{{- $templater := (mustMergeOverwrite (dict "Dot" (coalesce nil) "FauxDot" (coalesce nil)) (dict "Dot" $dot "FauxDot" (get (fromJson (include "chart.newFauxDot" (dict "a" (list $dot)))) "r"))) -}}
21+
{{- if (eq $values.secret.authentication.jwtSigningKey "") -}}
22+
{{- $_ := (set $values.secret.authentication "jwtSigningKey" (randAlphaNum (32 | int))) -}}
23+
{{- end -}}
2124
{{- $_is_returning = true -}}
2225
{{- (dict "r" (mustMergeOverwrite (dict "ReleaseName" "" "Namespace" "" "Template" (coalesce nil) "CommonLabels" (coalesce nil) "Values" (dict "replicaCount" 0 "nameOverride" "" "commonLabels" (coalesce nil) "fullnameOverride" "" "image" (dict "registry" "" "repository" "" "pullPolicy" "" "tag" "") "imagePullSecrets" (coalesce nil) "automountServiceAccountToken" false "serviceAccount" (dict "create" false "automountServiceAccountToken" false "annotations" (coalesce nil) "name" "") "annotations" (coalesce nil) "podAnnotations" (coalesce nil) "podLabels" (coalesce nil) "podSecurityContext" (dict) "securityContext" (dict) "service" (dict "type" "" "port" 0 "annotations" (coalesce nil)) "ingress" (dict "enabled" false "annotations" (coalesce nil) "hosts" (coalesce nil) "tls" (coalesce nil)) "resources" (dict) "autoscaling" (dict "enabled" false "minReplicas" 0 "maxReplicas" 0 "targetCPUUtilizationPercentage" (coalesce nil)) "nodeSelector" (coalesce nil) "tolerations" (coalesce nil) "affinity" (dict) "topologySpreadConstraints" (coalesce nil) "priorityClassName" "" "config" (coalesce nil) "extraEnv" (coalesce nil) "extraEnvFrom" (coalesce nil) "extraVolumes" (coalesce nil) "extraVolumeMounts" (coalesce nil) "extraContainers" (coalesce nil) "initContainers" (dict "extraInitContainers" (coalesce nil)) "secretMounts" (coalesce nil) "secret" (dict "create" false "kafka" (dict) "authentication" (dict "jwtSigningKey" "" "oidc" (dict)) "license" "" "redpanda" (dict "adminApi" (dict)) "serde" (dict) "schemaRegistry" (dict)) "livenessProbe" (dict) "readinessProbe" (dict) "configmap" (dict "create" false) "deployment" (dict "create" false) "strategy" (dict))) (dict "ReleaseName" $dot.Release.Name "Namespace" $dot.Release.Namespace "Values" $values "Template" (list "chart.templater.Template" $templater) "CommonLabels" (dict "helm.sh/chart" (get (fromJson (include "chart.ChartLabel" (dict "a" (list $dot)))) "r") "app.kubernetes.io/managed-by" $dot.Release.Service "app.kubernetes.io/version" $dot.Chart.AppVersion)))) | toJson -}}
2326
{{- break -}}

charts/console/chart/templates/_console.render.tpl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@
8787
{{- end -}}
8888
{{- end -}}
8989

90+
{{- define "console.Types" -}}
91+
{{- range $_ := (list 1) -}}
92+
{{- $_is_returning := false -}}
93+
{{- $_is_returning = true -}}
94+
{{- (dict "r" (list (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil))) (dict)) (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil))) (dict)) (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil))) (dict)) (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict) "status" (dict "loadBalancer" (dict))) (dict)) (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict) "status" (dict "loadBalancer" (dict))) (dict)) (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict "selector" (coalesce nil) "template" (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict "containers" (coalesce nil))) "strategy" (dict)) "status" (dict)) (dict)) (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict "scaleTargetRef" (dict "kind" "" "name" "") "maxReplicas" 0) "status" (dict "desiredReplicas" 0 "currentMetrics" (coalesce nil))) (dict)))) | toJson -}}
95+
{{- break -}}
96+
{{- end -}}
97+
{{- end -}}
98+
9099
{{- define "console.cleanForK8s" -}}
91100
{{- $s := (index .a 0) -}}
92101
{{- range $_ := (list 1) -}}

charts/console/chart/templates/_console.secret.tpl

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,8 @@
1010
{{- (dict "r" (coalesce nil)) | toJson -}}
1111
{{- break -}}
1212
{{- end -}}
13-
{{- $jwtSigningKey := $state.Values.secret.authentication.jwtSigningKey -}}
14-
{{- if (eq $jwtSigningKey "") -}}
15-
{{- $jwtSigningKey = (randAlphaNum (32 | int)) -}}
16-
{{- end -}}
1713
{{- $_is_returning = true -}}
18-
{{- (dict "r" (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil))) (mustMergeOverwrite (dict) (dict "apiVersion" "v1" "kind" "Secret")) (dict "metadata" (mustMergeOverwrite (dict "creationTimestamp" (coalesce nil)) (dict "name" (get (fromJson (include "console.RenderState.FullName" (dict "a" (list $state)))) "r") "labels" (get (fromJson (include "console.RenderState.Labels" (dict "a" (list $state (coalesce nil))))) "r") "namespace" $state.Namespace)) "type" "Opaque" "stringData" (dict "kafka-sasl-password" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.kafka.saslPassword "")))) "r") "kafka-sasl-aws-msk-iam-secret-key" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.kafka.awsMskIamSecretKey "")))) "r") "kafka-tls-ca" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.kafka.tlsCa "")))) "r") "kafka-tls-cert" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.kafka.tlsCert "")))) "r") "kafka-tls-key" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.kafka.tlsKey "")))) "r") "schema-registry-bearertoken" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.schemaRegistry.bearerToken "")))) "r") "schema-registry-password" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.schemaRegistry.password "")))) "r") "schemaregistry-tls-ca" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.schemaRegistry.tlsCa "")))) "r") "schemaregistry-tls-cert" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.schemaRegistry.tlsCert "")))) "r") "schemaregistry-tls-key" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.schemaRegistry.tlsKey "")))) "r") "authentication-jwt-signingkey" $jwtSigningKey "authentication-oidc-client-secret" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.authentication.oidc.clientSecret "")))) "r") "license" $state.Values.secret.license "redpanda-admin-api-password" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.redpanda.adminApi.password "")))) "r") "redpanda-admin-api-tls-ca" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.redpanda.adminApi.tlsCa "")))) "r") "redpanda-admin-api-tls-cert" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.redpanda.adminApi.tlsCert "")))) "r") "redpanda-admin-api-tls-key" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.redpanda.adminApi.tlsKey "")))) "r") "serde-protobuf-git-basicauth-password" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.serde.protobufGitBasicAuthPassword "")))) "r"))))) | toJson -}}
14+
{{- (dict "r" (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil))) (mustMergeOverwrite (dict) (dict "apiVersion" "v1" "kind" "Secret")) (dict "metadata" (mustMergeOverwrite (dict "creationTimestamp" (coalesce nil)) (dict "name" (get (fromJson (include "console.RenderState.FullName" (dict "a" (list $state)))) "r") "labels" (get (fromJson (include "console.RenderState.Labels" (dict "a" (list $state (coalesce nil))))) "r") "namespace" $state.Namespace)) "type" "Opaque" "stringData" (dict "kafka-sasl-password" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.kafka.saslPassword "")))) "r") "kafka-sasl-aws-msk-iam-secret-key" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.kafka.awsMskIamSecretKey "")))) "r") "kafka-tls-ca" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.kafka.tlsCa "")))) "r") "kafka-tls-cert" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.kafka.tlsCert "")))) "r") "kafka-tls-key" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.kafka.tlsKey "")))) "r") "schema-registry-bearertoken" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.schemaRegistry.bearerToken "")))) "r") "schema-registry-password" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.schemaRegistry.password "")))) "r") "schemaregistry-tls-ca" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.schemaRegistry.tlsCa "")))) "r") "schemaregistry-tls-cert" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.schemaRegistry.tlsCert "")))) "r") "schemaregistry-tls-key" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.schemaRegistry.tlsKey "")))) "r") "authentication-jwt-signingkey" $state.Values.secret.authentication.jwtSigningKey "authentication-oidc-client-secret" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.authentication.oidc.clientSecret "")))) "r") "license" $state.Values.secret.license "redpanda-admin-api-password" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.redpanda.adminApi.password "")))) "r") "redpanda-admin-api-tls-ca" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.redpanda.adminApi.tlsCa "")))) "r") "redpanda-admin-api-tls-cert" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.redpanda.adminApi.tlsCert "")))) "r") "redpanda-admin-api-tls-key" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.redpanda.adminApi.tlsKey "")))) "r") "serde-protobuf-git-basicauth-password" (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $state.Values.secret.serde.protobufGitBasicAuthPassword "")))) "r"))))) | toJson -}}
1915
{{- break -}}
2016
{{- end -}}
2117
{{- end -}}

charts/console/chart/values.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,10 @@ import (
1717
type Values struct {
1818
console.RenderValues `json:",inline"`
1919

20-
Globals map[string]any `json:"global,omitempty"`
21-
Enabled *bool `json:"enabled,omitempty"`
22-
CommonLabels map[string]string `json:"commonLabels"`
23-
NameOverride string `json:"nameOverride"`
24-
FullnameOverride string `json:"fullnameOverride"`
25-
Tests Enableable `json:"tests"`
20+
Globals map[string]any `json:"global,omitempty"`
21+
Enabled *bool `json:"enabled,omitempty"`
22+
CommonLabels map[string]string `json:"commonLabels"`
23+
Tests Enableable `json:"tests"`
2624
}
2725

2826
type Enableable struct {

0 commit comments

Comments
 (0)