Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changes/unreleased/operator-Added-20251001-165206.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
project: operator
kind: Added
body: Added a new `Console` CRD for managing a [Redpanda Console](https://github.com/redpanda-data/console/) deployments. For examples, see [`acceptance/features/console.feature`](../acceptance/features/console.feature).
time: 2025-10-01T16:52:06.679323-04:00
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
project: operator
kind: Deprecated
body: The Redpanda console stanza (`.spec.clusterSpec.console`) is now deprecated in favor of the stand-alone Console CRD.
time: 2025-10-01T17:09:58.327552-04:00
80 changes: 80 additions & 0 deletions acceptance/features/console.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
@cluster:basic
Feature: Console CRDs
Background: Cluster available
Given cluster "basic" is available

Scenario: Using clusterRef
When I apply Kubernetes manifest:
```yaml
---
apiVersion: cluster.redpanda.com/v1alpha2
kind: Console
metadata:
name: console
spec:
cluster:
clusterRef:
name: basic
```
Then Console "console" will be healthy
# These steps demonstrate that console is correctly connected to Redpanda (Kafka, Schema Registry, and Admin API).
And I exec "curl localhost:8080/api/schema-registry/mode" in a Pod matching "app.kubernetes.io/instance=console", it will output:
```
{"mode":"READWRITE"}
```
And I exec "curl localhost:8080/api/topics" in a Pod matching "app.kubernetes.io/instance=console", it will output:
```
{"topics":[{"topicName":"_schemas","isInternal":false,"partitionCount":1,"replicationFactor":1,"cleanupPolicy":"compact","documentation":"NOT_CONFIGURED","logDirSummary":{"totalSizeBytes":117}}]}
```
And I exec "curl localhost:8080/api/console/endpoints | grep -o '{[^{}]*DebugBundleService[^{}]*}'" in a Pod matching "app.kubernetes.io/instance=console", it will output:
```
{"endpoint":"redpanda.api.console.v1alpha1.DebugBundleService","method":"POST","isSupported":true}
```

Scenario: Using staticConfig
When I apply Kubernetes manifest:
```yaml
---
apiVersion: cluster.redpanda.com/v1alpha2
kind: Console
metadata:
name: console
spec:
cluster:
staticConfiguration:
kafka:
brokers:
- basic-0.basic.${NAMESPACE}.svc.cluster.local.:9093
tls:
caCertSecretRef:
name: "basic-default-cert"
key: "ca.crt"
admin:
urls:
- https://basic-0.basic.${NAMESPACE}.svc.cluster.local.:9644
tls:
caCertSecretRef:
name: "basic-default-cert"
key: "ca.crt"
schemaRegistry:
urls:
- https://basic-0.basic.${NAMESPACE}.svc.cluster.local.:8081
tls:
caCertSecretRef:
name: "basic-default-cert"
key: "ca.crt"
```
Then Console "console" will be healthy
# These steps demonstrate that console is correctly connected to Redpanda (Kafka, Schema Registry, and Admin API).
And I exec "curl localhost:8080/api/schema-registry/mode" in a Pod matching "app.kubernetes.io/instance=console", it will output:
```
{"mode":"READWRITE"}
```
And I exec "curl localhost:8080/api/topics" in a Pod matching "app.kubernetes.io/instance=console", it will output:
```
{"topics":[{"topicName":"_schemas","isInternal":false,"partitionCount":1,"replicationFactor":1,"cleanupPolicy":"compact","documentation":"NOT_CONFIGURED","logDirSummary":{"totalSizeBytes":117}}]}
```
And I exec "curl localhost:8080/api/console/endpoints | grep -o '{[^{}]*DebugBundleService[^{}]*}'" in a Pod matching "app.kubernetes.io/instance=console", it will output:
```
{"endpoint":"redpanda.api.console.v1alpha1.DebugBundleService","method":"POST","isSupported":true}
```
8 changes: 8 additions & 0 deletions acceptance/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ var setupSuite = sync.OnceValues(func() (*framework.Suite, error) {
CreateNamespace: true,
Values: map[string]any{
"installCRDs": true,
"global": map[string]any{
// Make leader election more aggressive as cert-manager appears to
// not release it when uninstalled.
"leaderElection": map[string]any{
"renewDeadline": "10s",
"retryPeriod": "5s",
},
},
},
}).
OnFeature(func(ctx context.Context, t framework.TestingT, tags ...framework.ParsedTag) {
Expand Down
35 changes: 35 additions & 0 deletions acceptance/steps/console.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2025 Redpanda Data, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.md
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0

package steps

import (
"context"
"time"

"github.com/stretchr/testify/require"

framework "github.com/redpanda-data/redpanda-operator/harpoon"
redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2"
)

func consoleIsHealthy(ctx context.Context, t framework.TestingT, name string) {
key := t.ResourceKey(name)

t.Logf("Checking console %q is healthy", name)
require.Eventually(t, func() bool {
var console redpandav1alpha2.Console
require.NoError(t, t.Get(ctx, key, &console))

upToDate := console.Generation == console.Status.ObservedGeneration
hasHealthyReplicas := console.Status.ReadyReplicas == console.Status.Replicas

return upToDate && hasHealthyReplicas
}, time.Minute, 10*time.Second)
}
33 changes: 33 additions & 0 deletions acceptance/steps/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,25 @@
package steps

import (
"bytes"
"context"
"fmt"
"reflect"
"strings"
"time"

"github.com/cucumber/godog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/util/jsonpath"
"sigs.k8s.io/controller-runtime/pkg/client"

framework "github.com/redpanda-data/redpanda-operator/harpoon"
redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2"
"github.com/redpanda-data/redpanda-operator/pkg/kube"
)

// this is a nasty hack due to the fact that we can't disable the linter for typecheck
Expand Down Expand Up @@ -156,3 +162,30 @@ func execJSONPath(ctx context.Context, t framework.TestingT, jsonPath, groupVers
}
return nil
}

func iExecInPodMatching(
ctx context.Context,
t framework.TestingT,
cmd,
selectorStr string,
expected *godog.DocString,
) {
selector, err := labels.Parse(selectorStr)
require.NoError(t, err)

ctl, err := kube.FromRESTConfig(t.RestConfig())
require.NoError(t, err)

pods, err := kube.List[corev1.PodList](ctx, ctl, t.Namespace(), client.MatchingLabelsSelector{Selector: selector})
require.NoError(t, err)

require.True(t, len(pods.Items) > 0, "selector %q found no Pods", selector.String())

var stdout bytes.Buffer
require.NoError(t, ctl.Exec(ctx, &pods.Items[0], kube.ExecOptions{
Command: []string{"sh", "-c", cmd},
Stdout: &stdout,
}))

assert.Equal(t, strings.TrimSpace(expected.Content), strings.TrimSpace(stdout.String()))
}
6 changes: 5 additions & 1 deletion acceptance/steps/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ func iApplyKubernetesManifest(ctx context.Context, t framework.TestingT, manifes
file, err := os.CreateTemp("", "manifest-*.yaml")
require.NoError(t, err)

_, err = file.Write(normalizeContent(t, manifest.Content))
content := PatchManifest(t, manifest.Content)

_, err = file.Write(normalizeContent(t, content))
require.NoError(t, err)
require.NoError(t, file.Close())

Expand Down Expand Up @@ -97,6 +99,8 @@ func PatchManifest(t framework.TestingT, content string) string {
return DefaultRedpandaRepo
case "DEFAULT_REDPANDA_TAG":
return DefaultRedpandaTag
case "NAMESPACE":
return t.Namespace()
}

t.Fatalf("unhandled expansion: %s", key)
Expand Down
5 changes: 5 additions & 0 deletions acceptance/steps/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ func init() {
// General scenario steps
framework.RegisterStep(`^(vectorized )?cluster "([^"]*)" is available$`, checkClusterAvailability)
framework.RegisterStep(`^I apply Kubernetes manifest:$`, iApplyKubernetesManifest)
framework.RegisterStep(`^I exec "([^"]+)" in a Pod matching "([^"]+)", it will output:$`, iExecInPodMatching)

framework.RegisterStep(`^I store "([^"]*)" of Kubernetes object with type "([^"]*)" and name "([^"]*)" as "([^"]*)"$`, recordVariable)
framework.RegisterStep(`^the recorded value "([^"]*)" has the same value as "([^"]*)" of the Kubernetes object with type "([^"]*)" and name "([^"]*)"$`, assertVariableValue)
framework.RegisterStep(`^the recorded value "([^"]*)" is one less than "([^"]*)" of the Kubernetes object with type "([^"]*)" and name "([^"]*)"$`, assertVariableValueIncremented)
Expand Down Expand Up @@ -98,4 +100,7 @@ func init() {
framework.RegisterStep(`^I can upgrade to the latest operator with the values:$`, iCanUpgradeToTheLatestOperatorWithTheValues)
framework.RegisterStep(`^I install redpanda helm chart version "([^"]*)" with the values:$`, iInstallRedpandaHelmChartVersionWithTheValues)
framework.RegisterStep(`^I install local CRDs from "([^"]*)"`, iInstallLocalCRDs)

// Console scenario steps
framework.RegisterStep(`^Console "([^"]+)" will be healthy`, consoleIsHealthy)
}
4 changes: 4 additions & 0 deletions charts/console/chart/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ func DotToState(dot *helmette.Dot) *console.RenderState {
values := helmette.Unwrap[Values](dot.Values)
templater := &templater{Dot: dot, FauxDot: newFauxDot(dot)}

if values.RenderValues.Secret.Authentication.JWTSigningKey == "" {
values.RenderValues.Secret.Authentication.JWTSigningKey = helmette.RandAlphaNum(32)
}

return &console.RenderState{
ReleaseName: dot.Release.Name,
Namespace: dot.Release.Namespace,
Expand Down
3 changes: 3 additions & 0 deletions charts/console/chart/templates/_chart.chart.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
{{- $_is_returning := false -}}
{{- $values := $dot.Values.AsMap -}}
{{- $templater := (mustMergeOverwrite (dict "Dot" (coalesce nil) "FauxDot" (coalesce nil)) (dict "Dot" $dot "FauxDot" (get (fromJson (include "chart.newFauxDot" (dict "a" (list $dot)))) "r"))) -}}
{{- if (eq $values.secret.authentication.jwtSigningKey "") -}}
{{- $_ := (set $values.secret.authentication "jwtSigningKey" (randAlphaNum (32 | int))) -}}
{{- end -}}
{{- $_is_returning = true -}}
{{- (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 -}}
{{- break -}}
Expand Down
9 changes: 9 additions & 0 deletions charts/console/chart/templates/_console.render.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@
{{- end -}}
{{- end -}}

{{- define "console.Types" -}}
{{- range $_ := (list 1) -}}
{{- $_is_returning := false -}}
{{- $_is_returning = true -}}
{{- (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 -}}
{{- break -}}
{{- end -}}
{{- end -}}

{{- define "console.cleanForK8s" -}}
{{- $s := (index .a 0) -}}
{{- range $_ := (list 1) -}}
Expand Down
6 changes: 1 addition & 5 deletions charts/console/chart/templates/_console.secret.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@
{{- (dict "r" (coalesce nil)) | toJson -}}
{{- break -}}
{{- end -}}
{{- $jwtSigningKey := $state.Values.secret.authentication.jwtSigningKey -}}
{{- if (eq $jwtSigningKey "") -}}
{{- $jwtSigningKey = (randAlphaNum (32 | int)) -}}
{{- end -}}
{{- $_is_returning = true -}}
{{- (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 -}}
{{- (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 -}}
{{- break -}}
{{- end -}}
{{- end -}}
Expand Down
10 changes: 4 additions & 6 deletions charts/console/chart/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ import (
type Values struct {
console.RenderValues `json:",inline"`

Globals map[string]any `json:"global,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
CommonLabels map[string]string `json:"commonLabels"`
NameOverride string `json:"nameOverride"`
FullnameOverride string `json:"fullnameOverride"`
Tests Enableable `json:"tests"`
Globals map[string]any `json:"global,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
CommonLabels map[string]string `json:"commonLabels"`
Tests Enableable `json:"tests"`
}

type Enableable struct {
Expand Down
Loading
Loading