From 73756fd25daacdda54b0d2318f4cfb678151897f Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Tue, 15 Oct 2024 09:58:39 -0400 Subject: [PATCH] Add schema registry implementation --- acceptance/features/schema-crds.feature | 38 + acceptance/features/topic-crds.feature | 15 +- acceptance/features/user-crds.feature | 64 +- acceptance/go.mod | 1 + acceptance/go.sum | 1 + acceptance/main_test.go | 24 + acceptance/steps/helpers.go | 43 ++ acceptance/steps/register.go | 5 + acceptance/steps/schemas.go | 43 ++ acceptance/steps/users.go | 4 +- go.work.sum | 24 +- .../redpanda/v1alpha2/schema.go | 212 ++++++ .../redpanda/v1alpha2/schemareference.go | 50 ++ .../redpanda/v1alpha2/schemaregistrysasl.go | 63 ++ .../redpanda/v1alpha2/schemaregistryspec.go | 52 ++ .../redpanda/v1alpha2/schemaspec.go | 86 +++ .../redpanda/v1alpha2/schemastatus.go | 70 ++ .../v1alpha2/staticconfigurationsource.go | 13 +- operator/api/redpanda/v1alpha2/common.go | 39 +- .../v1alpha2/redpanda_clusterspec_types.go | 3 + .../api/redpanda/v1alpha2/redpanda_types.go | 2 +- .../redpanda/v1alpha2/redpanda_types_test.go | 3 + .../api/redpanda/v1alpha2/schema_types.go | 231 ++++++ operator/api/redpanda/v1alpha2/topic_types.go | 1 + operator/api/redpanda/v1alpha2/user_types.go | 1 + .../v1alpha2/zz_generated.deepcopy.go | 198 +++++ .../bases/cluster.redpanda.com_redpandas.yaml | 10 + .../bases/cluster.redpanda.com_schemas.yaml | 676 ++++++++++++++++++ .../bases/cluster.redpanda.com_topics.yaml | 234 +++++- .../crd/bases/cluster.redpanda.com_users.yaml | 117 ++- operator/config/rbac/bases/operator/role.yaml | 6 + operator/go.mod | 7 +- operator/go.sum | 14 +- .../redpanda/resource_controller_test.go | 25 + .../controller/redpanda/schema_controller.go | 105 +++ .../redpanda/schema_controller_test.go | 134 ++++ operator/pkg/client/cluster.go | 23 + operator/pkg/client/factory.go | 51 ++ operator/pkg/client/schemas/schema.go | 183 +++++ operator/pkg/client/schemas/syncer.go | 125 ++++ operator/pkg/client/schemas/syncer_test.go | 161 +++++ operator/pkg/client/spec.go | 54 ++ operator/pkg/client/spec_sasl.go | 27 + operator/pkg/functional/compare.go | 46 ++ 44 files changed, 3215 insertions(+), 69 deletions(-) create mode 100644 acceptance/features/schema-crds.feature create mode 100644 acceptance/steps/schemas.go create mode 100644 operator/api/applyconfiguration/redpanda/v1alpha2/schema.go create mode 100644 operator/api/applyconfiguration/redpanda/v1alpha2/schemareference.go create mode 100644 operator/api/applyconfiguration/redpanda/v1alpha2/schemaregistrysasl.go create mode 100644 operator/api/applyconfiguration/redpanda/v1alpha2/schemaregistryspec.go create mode 100644 operator/api/applyconfiguration/redpanda/v1alpha2/schemaspec.go create mode 100644 operator/api/applyconfiguration/redpanda/v1alpha2/schemastatus.go create mode 100644 operator/api/redpanda/v1alpha2/schema_types.go create mode 100644 operator/config/crd/bases/cluster.redpanda.com_schemas.yaml create mode 100644 operator/internal/controller/redpanda/schema_controller.go create mode 100644 operator/internal/controller/redpanda/schema_controller_test.go create mode 100644 operator/pkg/client/schemas/schema.go create mode 100644 operator/pkg/client/schemas/syncer.go create mode 100644 operator/pkg/client/schemas/syncer_test.go create mode 100644 operator/pkg/functional/compare.go diff --git a/acceptance/features/schema-crds.feature b/acceptance/features/schema-crds.feature new file mode 100644 index 000000000..679225242 --- /dev/null +++ b/acceptance/features/schema-crds.feature @@ -0,0 +1,38 @@ +@cluster:basic +Feature: Schema CRDs + Background: Cluster available + Given cluster "basic" is available + + @skip:gke @skip:aks @skip:eks + Scenario: Managing Schemas + Given there is no schema "schema1" in cluster "basic" + When I apply Kubernetes manifest: + """ + --- + apiVersion: cluster.redpanda.com/v1alpha2 + kind: Schema + metadata: + name: schema1 + spec: + cluster: + clusterRef: + name: basic + text: | + { + "type": "record", + "name": "test", + "fields": + [ + { + "type": "string", + "name": "field1" + }, + { + "type": "int", + "name": "field2" + } + ] + } + """ + And schema "schema1" is successfully synced + Then I should be able to check compatibility against "schema1" in cluster "basic" \ No newline at end of file diff --git a/acceptance/features/topic-crds.feature b/acceptance/features/topic-crds.feature index 04403a5e1..d183eb858 100644 --- a/acceptance/features/topic-crds.feature +++ b/acceptance/features/topic-crds.feature @@ -8,20 +8,17 @@ Feature: Topic CRDs Given there is no topic "topic1" in cluster "basic" When I apply Kubernetes manifest: """ -# tag::basic-topic-example[] -# In this example manifest, a topic called "topic1" is created in a cluster called "basic". It has a replication factor of 1 and is distributed across a single partition. --- apiVersion: cluster.redpanda.com/v1alpha2 kind: Topic metadata: - name: topic1 + name: topic1 spec: - cluster: - clusterRef: - name: basic - partitions: 1 - replicationFactor: 1 -# end::basic-topic-example[] + cluster: + clusterRef: + name: basic + partitions: 1 + replicationFactor: 1 """ And topic "topic1" is successfully synced Then I should be able to produce and consume from "topic1" in cluster "basic" \ No newline at end of file diff --git a/acceptance/features/user-crds.feature b/acceptance/features/user-crds.feature index 571e83359..67b14136d 100644 --- a/acceptance/features/user-crds.feature +++ b/acceptance/features/user-crds.feature @@ -4,7 +4,7 @@ Feature: User CRDs Given cluster "sasl" is available @skip:gke @skip:aks @skip:eks - Scenario: Manage users + Scenario: Managing Users Given there is no user "bob" in cluster "sasl" And there is no user "james" in cluster "sasl" And there is no user "alice" in cluster "sasl" @@ -18,69 +18,59 @@ Feature: User CRDs And "alice" should exist and be able to authenticate to the "sasl" cluster @skip:gke @skip:aks @skip:eks - Scenario: Manage authentication-only users + Scenario: Managing Authentication-only Users Given there is no user "jason" in cluster "sasl" And there are already the following ACLs in cluster "sasl": | user | acls | | jason | [{"type":"allow","resource":{"type":"cluster"},"operations":["Read"]}] | When I apply Kubernetes manifest: """ -# tag::manage-authn-only-manifest[] -# In this example manifest, a user called "jason" is created in a cluster called "sasl". -# The user's password is defined in a Secret called "jason-password". -# This example assumes that you will create ACLs for this user separately. --- apiVersion: cluster.redpanda.com/v1alpha2 kind: User metadata: - name: jason + name: jason spec: - cluster: - clusterRef: - name: sasl - authentication: - type: scram-sha-512 - password: - valueFrom: - secretKeyRef: - name: jason-password - key: password -# end::manage-authn-only-manifest[] + cluster: + clusterRef: + name: sasl + authentication: + type: scram-sha-512 + password: + valueFrom: + secretKeyRef: + name: jason-password + key: password """ And user "jason" is successfully synced And I delete the CRD user "jason" Then there should be ACLs in the cluster "sasl" for user "jason" @skip:gke @skip:aks @skip:eks - Scenario: Manage authorization-only users + Scenario: Managing Authorization-only Users Given there are the following pre-existing users in cluster "sasl" | name | password | mechanism | | travis | password | SCRAM-SHA-256 | When I apply Kubernetes manifest: """ -# tag::manage-authz-only-manifest[] -# In this example manifest, an ACL called "travis" is created in a cluster called "sasl". -# The ACL give an existing user called "travis" permissions to read from all topics whose names start with some-topic. -# This example assumes that you already have a user called "travis" in your cluster. --- apiVersion: cluster.redpanda.com/v1alpha2 kind: User metadata: - name: travis + name: travis spec: - cluster: - clusterRef: - name: sasl - authorization: - acls: - - type: allow - resource: - type: topic - name: some-topic - patternType: prefixed - operations: [Read] -# end::manage-authz-only-manifest[] + cluster: + clusterRef: + name: sasl + authorization: + acls: + - type: allow + resource: + type: topic + name: some-topic + patternType: prefixed + operations: [Read] """ And user "travis" is successfully synced And I delete the CRD user "travis" - Then "travis" should be able to authenticate to the "sasl" cluster with password "password" and mechanism "SCRAM-SHA-256" \ No newline at end of file + Then "travis" should be able to authenticate to the "sasl" cluster with password "password" and mechanism "SCRAM-SHA-256" diff --git a/acceptance/go.mod b/acceptance/go.mod index 1483402ea..daa18b5e1 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -159,6 +159,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/twmb/franz-go/pkg/kmsg v1.8.0 // indirect github.com/twmb/franz-go/pkg/sasl/kerberos v1.1.0 // indirect + github.com/twmb/franz-go/pkg/sr v1.0.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index c9a0a72cb..d60fbca04 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -729,6 +729,7 @@ github.com/twmb/franz-go/pkg/kmsg v1.8.0 h1:lAQB9Z3aMrIP9qF9288XcFf/ccaSxEitNA1C github.com/twmb/franz-go/pkg/kmsg v1.8.0/go.mod h1:HzYEb8G3uu5XevZbtU0dVbkphaKTHk0X68N5ka4q6mU= github.com/twmb/franz-go/pkg/sasl/kerberos v1.1.0 h1:alKdbddkPw3rDh+AwmUEwh6HNYgTvDSFIe/GWYRR9RM= github.com/twmb/franz-go/pkg/sasl/kerberos v1.1.0/go.mod h1:k8BoBjyUbFj34f0rRbn+Ky12sZFAPbmShrg0karAIMo= +github.com/twmb/franz-go/pkg/sr v1.0.1 h1:hf3eRFDUWSfmR7JQCS/3JiqZEQwqbiDSS/DooewMHCE= github.com/twmb/tlscfg v1.2.1 h1:IU2efmP9utQEIV2fufpZjPq7xgcZK4qu25viD51BB44= github.com/twmb/tlscfg v1.2.1/go.mod h1:GameEQddljI+8Es373JfQEBvtI4dCTLKWGJbqT2kErs= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= diff --git a/acceptance/main_test.go b/acceptance/main_test.go index 3ba0f044e..04b526ac3 100644 --- a/acceptance/main_test.go +++ b/acceptance/main_test.go @@ -23,6 +23,7 @@ import ( redpandav1alpha1 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha1" redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2" "github.com/stretchr/testify/require" + rbacv1 "k8s.io/api/rbac/v1" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -74,10 +75,33 @@ func TestMain(m *testing.M) { }, }) t.Log("Successfully installed Redpanda operator chart") + + // hack to patch the RBAC policies we'll need for schemas + var role rbacv1.Role + require.NoError(t, t.Get(ctx, t.ResourceKey("redpanda-operator"), &role)) + role.Rules = append(role.Rules, []rbacv1.PolicyRule{ + { + Verbs: []string{"get", "list", "patch", "update", "watch"}, + APIGroups: []string{"cluster.redpanda.com"}, + Resources: []string{"schemas"}, + }, + { + Verbs: []string{"update"}, + APIGroups: []string{"cluster.redpanda.com"}, + Resources: []string{"schemas/finalizers"}, + }, + { + Verbs: []string{"get", "patch", "update"}, + APIGroups: []string{"cluster.redpanda.com"}, + Resources: []string{"schemas/status"}, + }, + }...) + require.NoError(t, t.Update(ctx, &role)) }). RegisterTag("cluster", 1, ClusterTag). ExitOnCleanupFailures(). Build() + if err != nil { fmt.Printf("error running test suite: %v\n", err) os.Exit(1) diff --git a/acceptance/steps/helpers.go b/acceptance/steps/helpers.go index 7ffd60df0..ca987be85 100644 --- a/acceptance/steps/helpers.go +++ b/acceptance/steps/helpers.go @@ -29,6 +29,7 @@ import ( "github.com/stretchr/testify/require" "github.com/twmb/franz-go/pkg/kadm" "github.com/twmb/franz-go/pkg/kgo" + "github.com/twmb/franz-go/pkg/sr" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" @@ -65,6 +66,14 @@ func (c *clusterClients) Kafka(ctx context.Context) *kgo.Client { return client } +func (c *clusterClients) SchemaRegistry(ctx context.Context) *sr.Client { + t := framework.T(ctx) + + client, err := c.factory.SchemaRegistryClient(ctx, c.resourceTarget) + require.NoError(t, err) + return client +} + func (c *clusterClients) RedpandaAdmin(ctx context.Context) *rpadmin.AdminAPI { t := framework.T(ctx) @@ -114,6 +123,40 @@ func (c *clusterClients) ExpectNoUser(ctx context.Context, user string) { t.Logf("Found no user %q in cluster %q", user, c.cluster) } +func (c *clusterClients) ExpectSchema(ctx context.Context, schema string) { + t := framework.T(ctx) + + t.Logf("Checking that schema %q exists in cluster %q", schema, c.cluster) + c.checkSchema(ctx, schema, true, fmt.Sprintf("Schema %q does not exist in cluster %q", schema, c.cluster)) + t.Logf("Found schema %q in cluster %q", schema, c.cluster) +} + +func (c *clusterClients) ExpectNoSchema(ctx context.Context, schema string) { + t := framework.T(ctx) + + t.Logf("Checking that schema %q does not exist in cluster %q", schema, c.cluster) + c.checkSchema(ctx, schema, false, fmt.Sprintf("Schema %q still exists in cluster %q", schema, c.cluster)) + t.Logf("Found no schema %q in cluster %q", schema, c.cluster) +} + +func (c *clusterClients) checkSchema(ctx context.Context, schema string, exists bool, message string) { + t := framework.T(ctx) + + var subjects []string + var err error + + if !assert.Eventually(t, func() bool { + t.Logf("Pulling list of schema subjects from cluster") + schemaRegistry := c.SchemaRegistry(ctx) + subjects, err = schemaRegistry.Subjects(ctx) + require.NoError(t, err) + + return exists == slices.Contains(subjects, schema) + }, 10*time.Second, 1*time.Second, message) { + t.Errorf("Final list of schema subjects: %v", subjects) + } +} + func (c *clusterClients) ExpectTopic(ctx context.Context, topic string) { t := framework.T(ctx) diff --git a/acceptance/steps/register.go b/acceptance/steps/register.go index ba531a2f1..04df9b297 100644 --- a/acceptance/steps/register.go +++ b/acceptance/steps/register.go @@ -18,6 +18,11 @@ func init() { framework.RegisterStep(`^I apply Kubernetes manifest:$`, iApplyKubernetesManifest) + // Schema scenario steps + framework.RegisterStep(`^there is no schema "([^"]*)" in cluster "([^"]*)"$`, thereIsNoSchema) + framework.RegisterStep(`^schema "([^"]*)" is successfully synced$`, schemaIsSuccessfullySynced) + framework.RegisterStep(`^I should be able to check compatibility against "([^"]*)" in cluster "([^"]*)"$`, iShouldBeAbleToCheckCompatibilityAgainst) + // Topic scenario steps framework.RegisterStep(`^there is no topic "([^"]*)" in cluster "([^"]*)"$`, thereIsNoTopic) framework.RegisterStep(`^topic "([^"]*)" is successfully synced$`, topicIsSuccessfullySynced) diff --git a/acceptance/steps/schemas.go b/acceptance/steps/schemas.go new file mode 100644 index 000000000..6622df3c8 --- /dev/null +++ b/acceptance/steps/schemas.go @@ -0,0 +1,43 @@ +// Copyright 2024 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" + + framework "github.com/redpanda-data/redpanda-operator/harpoon" + redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func schemaIsSuccessfullySynced(ctx context.Context, t framework.TestingT, schema string) { + var schemaObject redpandav1alpha2.Schema + require.NoError(t, t.Get(ctx, t.ResourceKey(schema), &schemaObject)) + + // make sure the resource is stable + checkStableResource(ctx, t, &schemaObject) + + // make sure it's synchronized + t.RequireCondition(metav1.Condition{ + Type: redpandav1alpha2.ResourceConditionTypeSynced, + Status: metav1.ConditionTrue, + Reason: redpandav1alpha2.ResourceConditionReasonSynced, + }, schemaObject.Status.Conditions) +} + +func thereIsNoSchema(ctx context.Context, schema, cluster string) { + clientsForCluster(ctx, cluster).ExpectNoSchema(ctx, schema) +} + +func iShouldBeAbleToCheckCompatibilityAgainst(ctx context.Context, t framework.TestingT, schema, cluster string) { + clients := clientsForCluster(ctx, cluster) + clients.ExpectSchema(ctx, schema) +} diff --git a/acceptance/steps/users.go b/acceptance/steps/users.go index 4b613e16e..408dde979 100644 --- a/acceptance/steps/users.go +++ b/acceptance/steps/users.go @@ -49,9 +49,9 @@ func iCreateCRDbasedUsers(ctx context.Context, t framework.TestingT, cluster str // make sure it's synchronized t.RequireCondition(metav1.Condition{ - Type: redpandav1alpha2.UserConditionTypeSynced, + Type: redpandav1alpha2.ResourceConditionTypeSynced, Status: metav1.ConditionTrue, - Reason: redpandav1alpha2.UserConditionReasonSynced, + Reason: redpandav1alpha2.ResourceConditionReasonSynced, }, user.Status.Conditions) t.Cleanup(func(ctx context.Context) { diff --git a/go.work.sum b/go.work.sum index 00580c689..88a7d1249 100644 --- a/go.work.sum +++ b/go.work.sum @@ -484,6 +484,7 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83 github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= @@ -947,6 +948,7 @@ github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= @@ -999,6 +1001,7 @@ github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwo github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -1723,6 +1726,7 @@ github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= @@ -1801,11 +1805,13 @@ github.com/redpanda-data/common-go/rpadmin v0.1.4/go.mod h1:I7umqhnMhIOSEnIA3fvL github.com/redpanda-data/common-go/rpadmin v0.1.5-0.20240814205445-8e6eb5806561/go.mod h1:I7umqhnMhIOSEnIA3fvLtdQU7QO/SbWGCwFfFDs3De4= github.com/redpanda-data/helm-charts v0.0.0-20240911060052-2bf9dd6f0996/go.mod h1:uEMmuH+gTppAsZZNYlUbh6tuxN3fqffWY0Bi8AcE2Zk= github.com/redpanda-data/helm-charts v0.0.0-20240916201426-9ca3b128bb8e/go.mod h1:uEMmuH+gTppAsZZNYlUbh6tuxN3fqffWY0Bi8AcE2Zk= +github.com/redpanda-data/redpanda/src/go/rpk v0.0.0-20240105044330-c094966ca0cf/go.mod h1:SaSp5/JwdLHu8ZU82wFbXD8/oE4UWB+8ZkjWWreAt7Y= github.com/rhnvrm/simples3 v0.6.1 h1:H0DJwybR6ryQE+Odi9eqkHuzjYAeJgtGcGtuBwOhsH8= github.com/rickb777/period v1.0.6 h1:f4TcHBtL/4qa4D44eqgxs7785/kfLKUjRI7XYI2HCvk= github.com/rickb777/period v1.0.6/go.mod h1:TKkPHI/WSyjjVdeVCyqwBoQg0Cdb/jRvnc8FFdq2cgw= github.com/rickb777/plural v1.4.2 h1:Kl/syFGLFZ5EbuV8c9SVud8s5HI2HpCCtOMw2U1kS+A= github.com/rickb777/plural v1.4.2/go.mod h1:kdmXUpmKBJTS0FtG/TFumd//VBWsNTD7zOw7x4umxNw= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -1915,12 +1921,15 @@ github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZy github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM= github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E= github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= github.com/tilinna/z85 v1.0.0 h1:uqFnJBlD01dosSeo5sK1G1YGbPuwqVHqR+12OJDRjUw= github.com/tilinna/z85 v1.0.0/go.mod h1:EfpFU/DUY4ddEy6CRvk2l+UQNEzHbh+bqBQS+04Nkxs= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= +github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= @@ -1931,14 +1940,20 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twmb/franz-go v1.15.4/go.mod h1:rC18hqNmfo8TMc1kz7CQmHL74PLNF8KVvhflxiiJZCU= github.com/twmb/franz-go v1.17.1 h1:0LwPsbbJeJ9R91DPUHSEd4su82WJWcTY1Zzbgbg4CeQ= +github.com/twmb/franz-go v1.18.0 h1:25FjMZfdozBywVX+5xrWC2W+W76i0xykKjTdEeD2ejw= +github.com/twmb/franz-go v1.18.0/go.mod h1:zXCGy74M0p5FbXsLeASdyvfLFsBvTubVqctIaa5wQ+I= github.com/twmb/franz-go/pkg/kadm v1.13.0 h1:bJq4C2ZikUE2jh/wl9MtMTQ/kpmnBgVFh8XMQBEC+60= github.com/twmb/franz-go/pkg/kfake v0.0.0-20230703040638-f324841a32b4 h1:y5NllVZKHBJRSPLWn01cKE94pj2FU9cv6W8H5YqnJw8= github.com/twmb/franz-go/pkg/kfake v0.0.0-20230703040638-f324841a32b4/go.mod h1:Cj9p83ubk+71qEHi33iIT/m/53DzerdNDkMs3Lf2h+4= github.com/twmb/franz-go/pkg/kfake v0.0.0-20240412162337-6a58760afaa7 h1:ehifEfv6+joNOFrOZ7vRDcgeAJsOIrav2MrZbGhK2MA= github.com/twmb/franz-go/pkg/kfake v0.0.0-20240412162337-6a58760afaa7/go.mod h1:DCMFat7WCZfk946rqd9aVAcAmB6/rIcdMTslJSjJZgk= +github.com/twmb/franz-go/pkg/kmsg v1.7.0/go.mod h1:se9Mjdt0Nwzc9lnjJ0HyDtLyBnaBDAd7pCje47OhSyw= +github.com/twmb/franz-go/pkg/kmsg v1.9.0 h1:JojYUph2TKAau6SBtErXpXGC7E3gg4vGZMv9xFU/B6M= +github.com/twmb/franz-go/pkg/kmsg v1.9.0/go.mod h1:CMbfazviCyY6HM0SXuG5t9vOwYDHRCSrJJyBAe5paqg= github.com/twmb/franz-go/pkg/sr v0.0.0-20231231072040-c69fa0b5dc26/go.mod h1:egX+kicq83hpztv3PRCXKLNO132Ol9JTAJOCRZcqUxI= -github.com/twmb/franz-go/pkg/sr v1.0.1 h1:hf3eRFDUWSfmR7JQCS/3JiqZEQwqbiDSS/DooewMHCE= github.com/twmb/franz-go/pkg/sr v1.0.1/go.mod h1:aUFRRLI5WYKpKzmWDztzZFecx5eOkCNuuamd91jUV5c= +github.com/twmb/franz-go/pkg/sr v1.2.0 h1:zYr0Ly7KLFfeCGaSr8teN6LvAVeYVrZoUsyyPHTYB+M= +github.com/twmb/franz-go/pkg/sr v1.2.0/go.mod h1:gpd2Xl5/prkj3gyugcL+rVzagjaxFqMgvKMYcUlrpDw= github.com/twmb/franz-go/plugin/kzap v1.1.2 h1:0arX5xJ0soUPX1LlDay6ZZoxuWkWk1lggQ5M/IgRXAE= github.com/twmb/franz-go/plugin/kzap v1.1.2/go.mod h1:53Cl9Uz1pbdOPDvUISIxLrZIWSa2jCuY1bTMauRMBmo= github.com/twmb/go-cache v1.2.0 h1:VEA/7Arb/yCAzqBiNrN2Q1L6IJrm8+tNonaZ3OsLcgk= @@ -2738,11 +2753,13 @@ honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= +k8s.io/api v0.29.5/go.mod h1:7b18TtPcJzdjk7w5zWyIHgoAtpGeRvGGASxlS7UZXdQ= k8s.io/api v0.29.7/go.mod h1:mPimdbyuIjwoLtBEVIGVUYb4BKOE+44XHt/n4IqKsLA= k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= k8s.io/apiextensions-apiserver v0.29.2/go.mod h1:aLfYjpA5p3OwtqNXQFkhJ56TB+spV8Gc4wfMhUA3/b8= +k8s.io/apiextensions-apiserver v0.29.5/go.mod h1:pfIvij+MH9a8NQKtW7MD4EFnzvUjJ1ZQsDL8wuP8fnc= k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= @@ -2750,6 +2767,7 @@ k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRp k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= +k8s.io/apimachinery v0.29.5/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= k8s.io/apimachinery v0.29.7/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= k8s.io/apimachinery v0.29.8/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= @@ -2758,6 +2776,7 @@ k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsM k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8= k8s.io/apiserver v0.29.2/go.mod h1:B0LieKVoyU7ykQvPFm7XSdIHaCHSzCzQWPFa5bqbeMQ= +k8s.io/apiserver v0.29.5/go.mod h1:zN9xdatz5g7XwL1Xoz9hD4QQON1GN0c+1kV5e/NHejM= k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= k8s.io/apiserver v0.30.1/go.mod h1:i87ZnQ+/PGAmSbD/iEKM68bm1D5reX8fO4Ito4B01mo= k8s.io/apiserver v0.31.0/go.mod h1:KI9ox5Yu902iBnnyMmy7ajonhKnkeZYJhTZ/YI+WEMk= @@ -2766,6 +2785,7 @@ k8s.io/cli-runtime v0.31.0/go.mod h1:vg3H94wsubuvWfSmStDbekvbla5vFGC+zLWqcf+bGDw k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= +k8s.io/client-go v0.29.5/go.mod h1:aY5CnqUUvXYccJhm47XHoPcRyX6vouHdIBHaKZGTbK4= k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= @@ -2780,6 +2800,7 @@ k8s.io/code-generator v0.31.1/go.mod h1:oL2ky46L48osNqqZAeOcWWy0S5BXj50vVdwOtTef k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= k8s.io/component-base v0.29.2/go.mod h1:BfB3SLrefbZXiBfbM+2H1dlat21Uewg/5qtKOl8degM= +k8s.io/component-base v0.29.5/go.mod h1:9nBUoPxW/yimISIgAG7sJDrUGJlu7t8HnDafIrOdU8Q= k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ= k8s.io/component-base v0.30.1/go.mod h1:e/X9kDiOebwlI41AvBHuWdqFriSRrX50CdwA9TFaHLI= k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo= @@ -2859,6 +2880,7 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RCh sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.17.2/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= sigs.k8s.io/controller-runtime v0.18.0/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= sigs.k8s.io/controller-tools v0.13.0 h1:NfrvuZ4bxyolhDBt/rCZhDnx3M2hzlhgo5n3Iv2RykI= sigs.k8s.io/controller-tools v0.13.0/go.mod h1:5vw3En2NazbejQGCeWKRrE7q4P+CW8/klfVqP8QZkgA= diff --git a/operator/api/applyconfiguration/redpanda/v1alpha2/schema.go b/operator/api/applyconfiguration/redpanda/v1alpha2/schema.go new file mode 100644 index 000000000..a858e833e --- /dev/null +++ b/operator/api/applyconfiguration/redpanda/v1alpha2/schema.go @@ -0,0 +1,212 @@ +// Copyright 2024 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 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// SchemaApplyConfiguration represents an declarative configuration of the Schema type for use +// with apply. +type SchemaApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *SchemaSpecApplyConfiguration `json:"spec,omitempty"` + Status *SchemaStatusApplyConfiguration `json:"status,omitempty"` +} + +// Schema constructs an declarative configuration of the Schema type for use with +// apply. +func Schema(name, namespace string) *SchemaApplyConfiguration { + b := &SchemaApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Schema") + b.WithAPIVersion("cluster.redpanda.com/v1alpha2") + return b +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithKind(value string) *SchemaApplyConfiguration { + b.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithAPIVersion(value string) *SchemaApplyConfiguration { + b.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithName(value string) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithGenerateName(value string) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithNamespace(value string) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithUID(value types.UID) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithResourceVersion(value string) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithGeneration(value int64) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithCreationTimestamp(value metav1.Time) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *SchemaApplyConfiguration) WithLabels(entries map[string]string) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *SchemaApplyConfiguration) WithAnnotations(entries map[string]string) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *SchemaApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.OwnerReferences = append(b.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *SchemaApplyConfiguration) WithFinalizers(values ...string) *SchemaApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.Finalizers = append(b.Finalizers, values[i]) + } + return b +} + +func (b *SchemaApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithSpec(value *SchemaSpecApplyConfiguration) *SchemaApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *SchemaApplyConfiguration) WithStatus(value *SchemaStatusApplyConfiguration) *SchemaApplyConfiguration { + b.Status = value + return b +} diff --git a/operator/api/applyconfiguration/redpanda/v1alpha2/schemareference.go b/operator/api/applyconfiguration/redpanda/v1alpha2/schemareference.go new file mode 100644 index 000000000..d3a5dda7a --- /dev/null +++ b/operator/api/applyconfiguration/redpanda/v1alpha2/schemareference.go @@ -0,0 +1,50 @@ +// Copyright 2024 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 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +// SchemaReferenceApplyConfiguration represents an declarative configuration of the SchemaReference type for use +// with apply. +type SchemaReferenceApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Subject *string `json:"subject,omitempty"` + Version *int `json:"version,omitempty"` +} + +// SchemaReferenceApplyConfiguration constructs an declarative configuration of the SchemaReference type for use with +// apply. +func SchemaReference() *SchemaReferenceApplyConfiguration { + return &SchemaReferenceApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SchemaReferenceApplyConfiguration) WithName(value string) *SchemaReferenceApplyConfiguration { + b.Name = &value + return b +} + +// WithSubject sets the Subject field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Subject field is set to the value of the last call. +func (b *SchemaReferenceApplyConfiguration) WithSubject(value string) *SchemaReferenceApplyConfiguration { + b.Subject = &value + return b +} + +// WithVersion sets the Version field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Version field is set to the value of the last call. +func (b *SchemaReferenceApplyConfiguration) WithVersion(value int) *SchemaReferenceApplyConfiguration { + b.Version = &value + return b +} diff --git a/operator/api/applyconfiguration/redpanda/v1alpha2/schemaregistrysasl.go b/operator/api/applyconfiguration/redpanda/v1alpha2/schemaregistrysasl.go new file mode 100644 index 000000000..646c74450 --- /dev/null +++ b/operator/api/applyconfiguration/redpanda/v1alpha2/schemaregistrysasl.go @@ -0,0 +1,63 @@ +// Copyright 2024 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 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2" +) + +// SchemaRegistrySASLApplyConfiguration represents an declarative configuration of the SchemaRegistrySASL type for use +// with apply. +type SchemaRegistrySASLApplyConfiguration struct { + Username *string `json:"username,omitempty"` + Password *SecretKeyRefApplyConfiguration `json:"passwordSecretRef,omitempty"` + Mechanism *redpandav1alpha2.SASLMechanism `json:"mechanism,omitempty"` + AuthToken *SecretKeyRefApplyConfiguration `json:"token,omitempty"` +} + +// SchemaRegistrySASLApplyConfiguration constructs an declarative configuration of the SchemaRegistrySASL type for use with +// apply. +func SchemaRegistrySASL() *SchemaRegistrySASLApplyConfiguration { + return &SchemaRegistrySASLApplyConfiguration{} +} + +// WithUsername sets the Username field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Username field is set to the value of the last call. +func (b *SchemaRegistrySASLApplyConfiguration) WithUsername(value string) *SchemaRegistrySASLApplyConfiguration { + b.Username = &value + return b +} + +// WithPassword sets the Password field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Password field is set to the value of the last call. +func (b *SchemaRegistrySASLApplyConfiguration) WithPassword(value *SecretKeyRefApplyConfiguration) *SchemaRegistrySASLApplyConfiguration { + b.Password = value + return b +} + +// WithMechanism sets the Mechanism field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Mechanism field is set to the value of the last call. +func (b *SchemaRegistrySASLApplyConfiguration) WithMechanism(value redpandav1alpha2.SASLMechanism) *SchemaRegistrySASLApplyConfiguration { + b.Mechanism = &value + return b +} + +// WithAuthToken sets the AuthToken field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AuthToken field is set to the value of the last call. +func (b *SchemaRegistrySASLApplyConfiguration) WithAuthToken(value *SecretKeyRefApplyConfiguration) *SchemaRegistrySASLApplyConfiguration { + b.AuthToken = value + return b +} diff --git a/operator/api/applyconfiguration/redpanda/v1alpha2/schemaregistryspec.go b/operator/api/applyconfiguration/redpanda/v1alpha2/schemaregistryspec.go new file mode 100644 index 000000000..65fa6804a --- /dev/null +++ b/operator/api/applyconfiguration/redpanda/v1alpha2/schemaregistryspec.go @@ -0,0 +1,52 @@ +// Copyright 2024 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 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +// SchemaRegistrySpecApplyConfiguration represents an declarative configuration of the SchemaRegistrySpec type for use +// with apply. +type SchemaRegistrySpecApplyConfiguration struct { + URLs []string `json:"urls,omitempty"` + TLS *CommonTLSApplyConfiguration `json:"tls,omitempty"` + SASL *SchemaRegistrySASLApplyConfiguration `json:"sasl,omitempty"` +} + +// SchemaRegistrySpecApplyConfiguration constructs an declarative configuration of the SchemaRegistrySpec type for use with +// apply. +func SchemaRegistrySpec() *SchemaRegistrySpecApplyConfiguration { + return &SchemaRegistrySpecApplyConfiguration{} +} + +// WithURLs adds the given value to the URLs field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the URLs field. +func (b *SchemaRegistrySpecApplyConfiguration) WithURLs(values ...string) *SchemaRegistrySpecApplyConfiguration { + for i := range values { + b.URLs = append(b.URLs, values[i]) + } + return b +} + +// WithTLS sets the TLS field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TLS field is set to the value of the last call. +func (b *SchemaRegistrySpecApplyConfiguration) WithTLS(value *CommonTLSApplyConfiguration) *SchemaRegistrySpecApplyConfiguration { + b.TLS = value + return b +} + +// WithSASL sets the SASL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SASL field is set to the value of the last call. +func (b *SchemaRegistrySpecApplyConfiguration) WithSASL(value *SchemaRegistrySASLApplyConfiguration) *SchemaRegistrySpecApplyConfiguration { + b.SASL = value + return b +} diff --git a/operator/api/applyconfiguration/redpanda/v1alpha2/schemaspec.go b/operator/api/applyconfiguration/redpanda/v1alpha2/schemaspec.go new file mode 100644 index 000000000..2edc868b5 --- /dev/null +++ b/operator/api/applyconfiguration/redpanda/v1alpha2/schemaspec.go @@ -0,0 +1,86 @@ +// Copyright 2024 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 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2" +) + +// SchemaSpecApplyConfiguration represents an declarative configuration of the SchemaSpec type for use +// with apply. +type SchemaSpecApplyConfiguration struct { + ClusterSource *ClusterSourceApplyConfiguration `json:"cluster,omitempty"` + Text *string `json:"text,omitempty"` + Type *redpandav1alpha2.SchemaType `json:"schemaType,omitempty"` + References []SchemaReferenceApplyConfiguration `json:"references,omitempty"` + Normalize *bool `json:"normalize,omitempty"` + CompatibilityLevel *redpandav1alpha2.CompatibilityLevel `json:"compatibilityLevel,omitempty"` +} + +// SchemaSpecApplyConfiguration constructs an declarative configuration of the SchemaSpec type for use with +// apply. +func SchemaSpec() *SchemaSpecApplyConfiguration { + return &SchemaSpecApplyConfiguration{} +} + +// WithClusterSource sets the ClusterSource field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClusterSource field is set to the value of the last call. +func (b *SchemaSpecApplyConfiguration) WithClusterSource(value *ClusterSourceApplyConfiguration) *SchemaSpecApplyConfiguration { + b.ClusterSource = value + return b +} + +// WithText sets the Text field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Text field is set to the value of the last call. +func (b *SchemaSpecApplyConfiguration) WithText(value string) *SchemaSpecApplyConfiguration { + b.Text = &value + return b +} + +// WithType sets the Type field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Type field is set to the value of the last call. +func (b *SchemaSpecApplyConfiguration) WithType(value redpandav1alpha2.SchemaType) *SchemaSpecApplyConfiguration { + b.Type = &value + return b +} + +// WithReferences adds the given value to the References field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the References field. +func (b *SchemaSpecApplyConfiguration) WithReferences(values ...*SchemaReferenceApplyConfiguration) *SchemaSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithReferences") + } + b.References = append(b.References, *values[i]) + } + return b +} + +// WithNormalize sets the Normalize field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Normalize field is set to the value of the last call. +func (b *SchemaSpecApplyConfiguration) WithNormalize(value bool) *SchemaSpecApplyConfiguration { + b.Normalize = &value + return b +} + +// WithCompatibilityLevel sets the CompatibilityLevel field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CompatibilityLevel field is set to the value of the last call. +func (b *SchemaSpecApplyConfiguration) WithCompatibilityLevel(value redpandav1alpha2.CompatibilityLevel) *SchemaSpecApplyConfiguration { + b.CompatibilityLevel = &value + return b +} diff --git a/operator/api/applyconfiguration/redpanda/v1alpha2/schemastatus.go b/operator/api/applyconfiguration/redpanda/v1alpha2/schemastatus.go new file mode 100644 index 000000000..241393701 --- /dev/null +++ b/operator/api/applyconfiguration/redpanda/v1alpha2/schemastatus.go @@ -0,0 +1,70 @@ +// Copyright 2024 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 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// SchemaStatusApplyConfiguration represents an declarative configuration of the SchemaStatus type for use +// with apply. +type SchemaStatusApplyConfiguration struct { + ObservedGeneration *int64 `json:"observedGeneration,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + Versions []int `json:"versions,omitempty"` + SchemaHash *string `json:"schemaHash,omitempty"` +} + +// SchemaStatusApplyConfiguration constructs an declarative configuration of the SchemaStatus type for use with +// apply. +func SchemaStatus() *SchemaStatusApplyConfiguration { + return &SchemaStatusApplyConfiguration{} +} + +// WithObservedGeneration sets the ObservedGeneration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ObservedGeneration field is set to the value of the last call. +func (b *SchemaStatusApplyConfiguration) WithObservedGeneration(value int64) *SchemaStatusApplyConfiguration { + b.ObservedGeneration = &value + return b +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *SchemaStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *SchemaStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithVersions adds the given value to the Versions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Versions field. +func (b *SchemaStatusApplyConfiguration) WithVersions(values ...int) *SchemaStatusApplyConfiguration { + for i := range values { + b.Versions = append(b.Versions, values[i]) + } + return b +} + +// WithSchemaHash sets the SchemaHash field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SchemaHash field is set to the value of the last call. +func (b *SchemaStatusApplyConfiguration) WithSchemaHash(value string) *SchemaStatusApplyConfiguration { + b.SchemaHash = &value + return b +} diff --git a/operator/api/applyconfiguration/redpanda/v1alpha2/staticconfigurationsource.go b/operator/api/applyconfiguration/redpanda/v1alpha2/staticconfigurationsource.go index 0c1e2206c..d2ea4822a 100644 --- a/operator/api/applyconfiguration/redpanda/v1alpha2/staticconfigurationsource.go +++ b/operator/api/applyconfiguration/redpanda/v1alpha2/staticconfigurationsource.go @@ -14,8 +14,9 @@ package v1alpha2 // StaticConfigurationSourceApplyConfiguration represents an declarative configuration of the StaticConfigurationSource type for use // with apply. type StaticConfigurationSourceApplyConfiguration struct { - Kafka *KafkaAPISpecApplyConfiguration `json:"kafka,omitempty"` - Admin *AdminAPISpecApplyConfiguration `json:"admin,omitempty"` + Kafka *KafkaAPISpecApplyConfiguration `json:"kafka,omitempty"` + Admin *AdminAPISpecApplyConfiguration `json:"admin,omitempty"` + SchemaRegistry *SchemaRegistrySpecApplyConfiguration `json:"schemaRegistry,omitempty"` } // StaticConfigurationSourceApplyConfiguration constructs an declarative configuration of the StaticConfigurationSource type for use with @@ -39,3 +40,11 @@ func (b *StaticConfigurationSourceApplyConfiguration) WithAdmin(value *AdminAPIS b.Admin = value return b } + +// WithSchemaRegistry sets the SchemaRegistry field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SchemaRegistry field is set to the value of the last call. +func (b *StaticConfigurationSourceApplyConfiguration) WithSchemaRegistry(value *SchemaRegistrySpecApplyConfiguration) *StaticConfigurationSourceApplyConfiguration { + b.SchemaRegistry = value + return b +} diff --git a/operator/api/redpanda/v1alpha2/common.go b/operator/api/redpanda/v1alpha2/common.go index 0ba8e534b..551f244f6 100644 --- a/operator/api/redpanda/v1alpha2/common.go +++ b/operator/api/redpanda/v1alpha2/common.go @@ -210,6 +210,32 @@ type AdminSASL struct { AuthToken SecretKeyRef `json:"token,omitempty"` } +// SchemaRegistrySpec defines client configuration for connecting to Redpanda's admin API. +type SchemaRegistrySpec struct { + // Specifies a list of broker addresses in the format : + URLs []string `json:"urls"` + // Defines TLS configuration settings for Redpanda clusters that have TLS enabled. + // +optional + TLS *CommonTLS `json:"tls,omitempty"` + // Defines authentication configuration settings for Redpanda clusters that have authentication enabled. + // +optional + SASL *SchemaRegistrySASL `json:"sasl,omitempty"` +} + +// SchemaRegistrySASL configures credentials to connect to Redpanda cluster that has authentication enabled. +type SchemaRegistrySASL struct { + // Specifies the username. + // +optional + Username string `json:"username,omitempty"` + // Specifies the password. + // +optional + Password SecretKeyRef `json:"passwordSecretRef,omitempty"` + // Specifies the SASL/SCRAM authentication mechanism. + Mechanism SASLMechanism `json:"mechanism"` + // +optional + AuthToken SecretKeyRef `json:"token,omitempty"` +} + // ClusterRef represents a reference to a cluster that is being targeted. type ClusterRef struct { // Name specifies the name of the cluster being referenced. @@ -236,11 +262,13 @@ type MetadataTemplate struct { type StaticConfigurationSource struct { // Kafka is the configuration information for communicating with the Kafka // API of a Redpanda cluster where the object should be created. - // +required - Kafka *KafkaAPISpec `json:"kafka"` + Kafka *KafkaAPISpec `json:"kafka,omitempty"` // AdminAPISpec is the configuration information for communicating with the Admin // API of a Redpanda cluster where the object should be created. Admin *AdminAPISpec `json:"admin,omitempty"` + // SchemaRegistry is the configuration information for communicating with the Schema Registry + // API of a Redpanda cluster where the object should be created. + SchemaRegistry *SchemaRegistrySpec `json:"schemaRegistry,omitempty"` } // ClusterSource defines how to connect to a particular Redpanda cluster. @@ -269,6 +297,13 @@ func (c *ClusterSource) GetAdminAPISpec() *AdminAPISpec { return nil } +func (c *ClusterSource) GetSchemaRegistrySpec() *SchemaRegistrySpec { + if c.StaticConfiguration != nil { + return c.StaticConfiguration.SchemaRegistry + } + return nil +} + func (c *ClusterSource) GetClusterRef() *ClusterRef { return c.ClusterRef } diff --git a/operator/api/redpanda/v1alpha2/redpanda_clusterspec_types.go b/operator/api/redpanda/v1alpha2/redpanda_clusterspec_types.go index be8f06f73..4410a7018 100644 --- a/operator/api/redpanda/v1alpha2/redpanda_clusterspec_types.go +++ b/operator/api/redpanda/v1alpha2/redpanda_clusterspec_types.go @@ -357,6 +357,9 @@ type SASL struct { // BootstrapUser configures the user used to bootstrap Redpanda when SASL is enabled. type BootstrapUser struct { + // Name specifies the name of the bootstrap user created for the cluster, if unspecified + // defaults to "kubernetes-controller". + Name *string `json:"name,omitempty"` // Specifies the location where the generated password will be written or a pre-existing // password will be read from. SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"` diff --git a/operator/api/redpanda/v1alpha2/redpanda_types.go b/operator/api/redpanda/v1alpha2/redpanda_types.go index 4415eb1bd..448edfcfe 100644 --- a/operator/api/redpanda/v1alpha2/redpanda_types.go +++ b/operator/api/redpanda/v1alpha2/redpanda_types.go @@ -282,5 +282,5 @@ func (in *Redpanda) GetDot(restConfig *rest.Config) (*helmette.Dot, error) { IsUpgrade: true, } - return redpandachart.Dot(release, partial, kube.RestToConfig(restConfig)) + return redpandachart.Chart.Dot(kube.RestToConfig(restConfig), release, partial) } diff --git a/operator/api/redpanda/v1alpha2/redpanda_types_test.go b/operator/api/redpanda/v1alpha2/redpanda_types_test.go index ca5def209..b06650f7a 100644 --- a/operator/api/redpanda/v1alpha2/redpanda_types_test.go +++ b/operator/api/redpanda/v1alpha2/redpanda_types_test.go @@ -157,6 +157,9 @@ func TestHelmValuesCompat(t *testing.T) { reflect.TypeFor[redpanda.PartialBootstrapUser](): { "Password": rapid.Just[any](nil), // This field is intentionally not documented or added to the CRD }, + reflect.TypeFor[redpanda.PartialServiceAccountCfg](): { + "AutomountServiceAccountToken": rapid.Just[any](nil), + }, }, } diff --git a/operator/api/redpanda/v1alpha2/schema_types.go b/operator/api/redpanda/v1alpha2/schema_types.go new file mode 100644 index 000000000..b35a2b91d --- /dev/null +++ b/operator/api/redpanda/v1alpha2/schema_types.go @@ -0,0 +1,231 @@ +// Copyright 2024 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 v1alpha2 + +import ( + "fmt" + "hash/fnv" + + "github.com/redpanda-data/redpanda-operator/operator/pkg/functional" + "github.com/twmb/franz-go/pkg/sr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +func init() { + SchemeBuilder.Register(&Schema{}, &SchemaList{}) +} + +// Schema defines the CRD for a Redpanda schema. +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:path=schemas +// +kubebuilder:resource:shortName=sc +// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=`.status.conditions[?(@.type=="Synced")].status` +// +kubebuilder:printcolumn:name="Latest Version",type="number",JSONPath=`.status.versions[-1]` +type Schema struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Defines the desired state of the Redpanda schema. + Spec SchemaSpec `json:"spec"` + // Represents the current status of the Redpanda schema. + // +kubebuilder:default={conditions: {{type: "Synced", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}}} + Status SchemaStatus `json:"status,omitempty"` +} + +var _ ClusterReferencingObject = (*Schema)(nil) + +func (s *Schema) GetClusterSource() *ClusterSource { + return s.Spec.ClusterSource +} + +// SchemaType specifies the type of the given schema. +// +kubebuilder:validation:Enum=avro;protobuf +type SchemaType string + +const ( + SchemaTypeAvro SchemaType = "avro" + SchemaTypeProtobuf SchemaType = "protobuf" +) + +var ( + schemaTypesFromKafka = map[sr.SchemaType]SchemaType{ + sr.TypeAvro: SchemaTypeAvro, + sr.TypeProtobuf: SchemaTypeProtobuf, + } + schemaTypesToKafka = map[SchemaType]sr.SchemaType{ + SchemaTypeAvro: sr.TypeAvro, + SchemaTypeProtobuf: sr.TypeProtobuf, + } +) + +func (s SchemaType) ToKafka() sr.SchemaType { + return schemaTypesToKafka[s] +} + +func SchemaTypeFromKafka(s sr.SchemaType) SchemaType { + return schemaTypesFromKafka[s] +} + +// +kubebuilder:validation:Enum=None;Backward;BackwardTransitive;Forward;ForwardTransitive;Full;FullTransitive +type CompatibilityLevel string + +const ( + CompatabilityLevelNone CompatibilityLevel = "None" + CompatabilityLevelBackward CompatibilityLevel = "Backward" + CompatabilityLevelBackwardTransitive CompatibilityLevel = "BackwardTransitive" + CompatabilityLevelForward CompatibilityLevel = "Forward" + CompatabilityLevelForwardTransitive CompatibilityLevel = "ForwardTransitive" + CompatabilityLevelFull CompatibilityLevel = "Full" + CompatabilityLevelFullTransitive CompatibilityLevel = "FullTransitive" +) + +var ( + compatibilityLevelsFromKafka = map[sr.CompatibilityLevel]CompatibilityLevel{ + sr.CompatNone: CompatabilityLevelNone, + sr.CompatBackward: CompatabilityLevelBackward, + sr.CompatBackwardTransitive: CompatabilityLevelBackwardTransitive, + sr.CompatForward: CompatabilityLevelForward, + sr.CompatForwardTransitive: CompatabilityLevelForwardTransitive, + sr.CompatFull: CompatabilityLevelFull, + sr.CompatFullTransitive: CompatabilityLevelFullTransitive, + } + compatibilityLevelsToKafka = map[CompatibilityLevel]sr.CompatibilityLevel{ + CompatabilityLevelNone: sr.CompatNone, + CompatabilityLevelBackward: sr.CompatBackward, + CompatabilityLevelBackwardTransitive: sr.CompatBackwardTransitive, + CompatabilityLevelForward: sr.CompatForward, + CompatabilityLevelForwardTransitive: sr.CompatForwardTransitive, + CompatabilityLevelFull: sr.CompatFull, + CompatabilityLevelFullTransitive: sr.CompatFullTransitive, + } +) + +func (c CompatibilityLevel) ToKafka() sr.CompatibilityLevel { + return compatibilityLevelsToKafka[c] +} + +func CompatibilityLevelFromKafka(c sr.CompatibilityLevel) CompatibilityLevel { + return compatibilityLevelsFromKafka[c] +} + +// SchemaSpec defines the configuration of a Redpanda schema. +type SchemaSpec struct { + // ClusterSource is a reference to the cluster hosting the schema registry. + // It is used in constructing the client created to configure a cluster. + // +required + // +kubebuilder:validation:XValidation:message="spec.cluster.staticConfiguration.schemaRegistry: required value",rule=`!has(self.staticConfiguration) || has(self.staticConfiguration.schemaRegistry)` + ClusterSource *ClusterSource `json:"cluster"` + // Text is the actual unescaped text of a schema. + // +required + Text string `json:"text"` + // Type is the type of a schema. The default type is avro. + // + // +kubebuilder:default=avro + Type *SchemaType `json:"schemaType,omitempty"` + + // References declares other schemas this schema references. See the + // docs on SchemaReference for more details. + References []SchemaReference `json:"references,omitempty"` + + // Normalize sets whether or not the schema should be normalized. + // +kubebuilder:default=true + Normalize *bool `json:"normalize,omitempty"` + + // CompatibilityLevel sets the compatibility level for the given schema + // +kubebuilder:default=Backward + CompatibilityLevel *CompatibilityLevel `json:"compatibilityLevel,omitempty"` +} + +func (s *SchemaSpec) SchemaHash() (string, error) { + hasher := fnv.New32() + if _, err := hasher.Write([]byte(s.Text)); err != nil { + return "", err + } + return fmt.Sprintf("%x", hasher.Sum(nil)), nil +} + +func (s *SchemaSpec) GetNormalize() bool { + if s.Normalize == nil { + return true + } + return *s.Normalize +} + +func (s *SchemaSpec) GetCompatibilityLevel() CompatibilityLevel { + if s.CompatibilityLevel == nil { + return CompatabilityLevelBackward + } + return *s.CompatibilityLevel +} + +func (s *SchemaSpec) GetType() SchemaType { + if s.Type == nil { + return SchemaTypeAvro + } + return *s.Type +} + +// SchemaReference is a way for a one schema to reference another. The +// details for how referencing is done are type specific; for example, +// JSON objects that use the key "$ref" can refer to another schema via +// URL. +type SchemaReference struct { + Name string `json:"name"` + Subject string `json:"subject"` + Version int `json:"version"` +} + +func (s *SchemaReference) ToKafka() sr.SchemaReference { + return sr.SchemaReference{ + Name: s.Name, + Subject: s.Subject, + Version: s.Version, + } +} + +func SchemaReferenceToKafka(s SchemaReference) sr.SchemaReference { + return s.ToKafka() +} + +func SchemaReferenceFromKafka(s sr.SchemaReference) SchemaReference { + return SchemaReference{ + Name: s.Name, + Subject: s.Subject, + Version: s.Version, + } +} + +// SchemaStatus defines the observed state of a Redpanda schema. +type SchemaStatus struct { + // Specifies the last observed generation. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // Conditions holds the conditions for the Redpanda schema. + Conditions []metav1.Condition `json:"conditions,omitempty"` + // Versions shows the versions of a given schema + Versions []int `json:"versions,omitempty"` + // SchemaHash is the hashed value of the schema synced to the cluster + SchemaHash string `json:"schemaHash,omitempty"` +} + +// SchemaList contains a list of Redpanda schema objects. +// +kubebuilder:object:root=true +type SchemaList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + // Specifies a list of Redpanda schema resources. + Items []Schema `json:"items"` +} + +func (s *SchemaList) GetItems() []*Schema { + return functional.MapFn(ptr.To, s.Items) +} diff --git a/operator/api/redpanda/v1alpha2/topic_types.go b/operator/api/redpanda/v1alpha2/topic_types.go index 25dc8a683..bb0069ee5 100644 --- a/operator/api/redpanda/v1alpha2/topic_types.go +++ b/operator/api/redpanda/v1alpha2/topic_types.go @@ -44,6 +44,7 @@ type TopicSpec struct { // ClusterSource is a reference to the cluster where the user should be created. // It is used in constructing the client created to configure a cluster. + // +kubebuilder:validation:XValidation:message="spec.cluster.staticConfiguration.kafka: required value",rule=`!has(self.staticConfiguration) || has(self.staticConfiguration.kafka)` ClusterSource *ClusterSource `json:"cluster,omitempty"` // Defines client configuration for connecting to Redpanda brokers. diff --git a/operator/api/redpanda/v1alpha2/user_types.go b/operator/api/redpanda/v1alpha2/user_types.go index 52bbdf4b4..ac8f7f614 100644 --- a/operator/api/redpanda/v1alpha2/user_types.go +++ b/operator/api/redpanda/v1alpha2/user_types.go @@ -88,6 +88,7 @@ type UserSpec struct { // ClusterSource is a reference to the cluster where the user should be created. // It is used in constructing the client created to configure a cluster. // +kubebuilder:validation:XValidation:message="spec.cluster.staticConfiguration.admin: required value",rule=`!has(self.staticConfiguration) || has(self.staticConfiguration.admin)` + // +kubebuilder:validation:XValidation:message="spec.cluster.staticConfiguration.kafka: required value",rule=`!has(self.staticConfiguration) || has(self.staticConfiguration.kafka)` // +required ClusterSource *ClusterSource `json:"cluster"` // Authentication defines the authentication information for a user. If no diff --git a/operator/api/redpanda/v1alpha2/zz_generated.deepcopy.go b/operator/api/redpanda/v1alpha2/zz_generated.deepcopy.go index 98242bc95..7fa531e30 100644 --- a/operator/api/redpanda/v1alpha2/zz_generated.deepcopy.go +++ b/operator/api/redpanda/v1alpha2/zz_generated.deepcopy.go @@ -249,6 +249,11 @@ func (in *Auth) DeepCopy() *Auth { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BootstrapUser) DeepCopyInto(out *BootstrapUser) { *out = *in + if in.Name != nil { + in, out := &in.Name, &out.Name + *out = new(string) + **out = **in + } if in.SecretKeyRef != nil { in, out := &in.SecretKeyRef, &out.SecretKeyRef *out = new(v1.SecretKeySelector) @@ -2998,6 +3003,80 @@ func (in *SASL) DeepCopy() *SASL { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Schema) DeepCopyInto(out *Schema) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Schema. +func (in *Schema) DeepCopy() *Schema { + if in == nil { + return nil + } + out := new(Schema) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Schema) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchemaList) DeepCopyInto(out *SchemaList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Schema, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaList. +func (in *SchemaList) DeepCopy() *SchemaList { + if in == nil { + return nil + } + out := new(SchemaList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SchemaList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchemaReference) DeepCopyInto(out *SchemaReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaReference. +func (in *SchemaReference) DeepCopy() *SchemaReference { + if in == nil { + return nil + } + out := new(SchemaReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SchemaRegistry) DeepCopyInto(out *SchemaRegistry) { *out = *in @@ -3054,6 +3133,120 @@ func (in *SchemaRegistry) DeepCopy() *SchemaRegistry { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchemaRegistrySASL) DeepCopyInto(out *SchemaRegistrySASL) { + *out = *in + out.Password = in.Password + out.AuthToken = in.AuthToken +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaRegistrySASL. +func (in *SchemaRegistrySASL) DeepCopy() *SchemaRegistrySASL { + if in == nil { + return nil + } + out := new(SchemaRegistrySASL) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchemaRegistrySpec) DeepCopyInto(out *SchemaRegistrySpec) { + *out = *in + if in.URLs != nil { + in, out := &in.URLs, &out.URLs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(CommonTLS) + (*in).DeepCopyInto(*out) + } + if in.SASL != nil { + in, out := &in.SASL, &out.SASL + *out = new(SchemaRegistrySASL) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaRegistrySpec. +func (in *SchemaRegistrySpec) DeepCopy() *SchemaRegistrySpec { + if in == nil { + return nil + } + out := new(SchemaRegistrySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchemaSpec) DeepCopyInto(out *SchemaSpec) { + *out = *in + if in.ClusterSource != nil { + in, out := &in.ClusterSource, &out.ClusterSource + *out = new(ClusterSource) + (*in).DeepCopyInto(*out) + } + if in.Type != nil { + in, out := &in.Type, &out.Type + *out = new(SchemaType) + **out = **in + } + if in.References != nil { + in, out := &in.References, &out.References + *out = make([]SchemaReference, len(*in)) + copy(*out, *in) + } + if in.Normalize != nil { + in, out := &in.Normalize, &out.Normalize + *out = new(bool) + **out = **in + } + if in.CompatibilityLevel != nil { + in, out := &in.CompatibilityLevel, &out.CompatibilityLevel + *out = new(CompatibilityLevel) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaSpec. +func (in *SchemaSpec) DeepCopy() *SchemaSpec { + if in == nil { + return nil + } + out := new(SchemaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchemaStatus) DeepCopyInto(out *SchemaStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Versions != nil { + in, out := &in.Versions, &out.Versions + *out = make([]int, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaStatus. +func (in *SchemaStatus) DeepCopy() *SchemaStatus { + if in == nil { + return nil + } + out := new(SchemaStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecretKeyRef) DeepCopyInto(out *SecretKeyRef) { *out = *in @@ -3510,6 +3703,11 @@ func (in *StaticConfigurationSource) DeepCopyInto(out *StaticConfigurationSource *out = new(AdminAPISpec) (*in).DeepCopyInto(*out) } + if in.SchemaRegistry != nil { + in, out := &in.SchemaRegistry, &out.SchemaRegistry + *out = new(SchemaRegistrySpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StaticConfigurationSource. diff --git a/operator/config/crd/bases/cluster.redpanda.com_redpandas.yaml b/operator/config/crd/bases/cluster.redpanda.com_redpandas.yaml index 94f0be055..2e5e22890 100644 --- a/operator/config/crd/bases/cluster.redpanda.com_redpandas.yaml +++ b/operator/config/crd/bases/cluster.redpanda.com_redpandas.yaml @@ -1129,6 +1129,11 @@ spec: to use for the bootstrap user. Options are `SCRAM-SHA-256` and `SCRAM-SHA-512`. type: string + name: + description: |- + Name specifies the name of the bootstrap user created for the cluster, if unspecified + defaults to "kubernetes-controller". + type: string secretKeyRef: description: |- Specifies the location where the generated password will be written or a pre-existing @@ -10907,6 +10912,11 @@ spec: to use for the bootstrap user. Options are `SCRAM-SHA-256` and `SCRAM-SHA-512`. type: string + name: + description: |- + Name specifies the name of the bootstrap user created for the cluster, if unspecified + defaults to "kubernetes-controller". + type: string secretKeyRef: description: |- Specifies the location where the generated password will be written or a pre-existing diff --git a/operator/config/crd/bases/cluster.redpanda.com_schemas.yaml b/operator/config/crd/bases/cluster.redpanda.com_schemas.yaml new file mode 100644 index 000000000..cf35c85fc --- /dev/null +++ b/operator/config/crd/bases/cluster.redpanda.com_schemas.yaml @@ -0,0 +1,676 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.3 + name: schemas.cluster.redpanda.com +spec: + group: cluster.redpanda.com + names: + kind: Schema + listKind: SchemaList + plural: schemas + shortNames: + - sc + singular: schema + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Synced")].status + name: Synced + type: string + - jsonPath: .status.versions[-1] + name: Latest Version + type: number + name: v1alpha2 + schema: + openAPIV3Schema: + description: Schema defines the CRD for a Redpanda schema. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Defines the desired state of the Redpanda schema. + properties: + cluster: + description: |- + ClusterSource is a reference to the cluster hosting the schema registry. + It is used in constructing the client created to configure a cluster. + properties: + clusterRef: + description: |- + ClusterRef is a reference to the cluster where the object should be created. + It is used in constructing the client created to configure a cluster. + This takes precedence over StaticConfigurationSource. + properties: + name: + description: Name specifies the name of the cluster being + referenced. + type: string + required: + - name + type: object + staticConfiguration: + description: StaticConfiguration holds connection parameters to + Kafka and Admin APIs. + properties: + admin: + description: |- + AdminAPISpec is the configuration information for communicating with the Admin + API of a Redpanda cluster where the object should be created. + properties: + sasl: + description: Defines authentication configuration settings + for Redpanda clusters that have authentication enabled. + properties: + mechanism: + description: Specifies the SASL/SCRAM authentication + mechanism. + type: string + passwordSecretRef: + description: Specifies the password. + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + token: + description: |- + SecretKeyRef contains enough information to inspect or modify the referred Secret data + See https://pkg.go.dev/k8s.io/api/core/v1#ObjectReference. + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + username: + description: Specifies the username. + type: string + required: + - mechanism + type: object + tls: + description: Defines TLS configuration settings for Redpanda + clusters that have TLS enabled. + properties: + caCertSecretRef: + description: CaCert is the reference for certificate + authority used to establish TLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + certSecretRef: + description: Cert is the reference for client public + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + insecureSkipTlsVerify: + description: InsecureSkipTLSVerify can skip verifying + Redpanda self-signed certificate when establish + TLS connection to Redpanda + type: boolean + keySecretRef: + description: Key is the reference for client private + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + urls: + description: Specifies a list of broker addresses in the + format : + items: + type: string + type: array + required: + - urls + type: object + kafka: + description: |- + Kafka is the configuration information for communicating with the Kafka + API of a Redpanda cluster where the object should be created. + properties: + brokers: + description: Specifies a list of broker addresses in the + format : + items: + type: string + type: array + sasl: + description: Defines authentication configuration settings + for Redpanda clusters that have authentication enabled. + properties: + awsMskIam: + description: |- + KafkaSASLAWSMskIam is the config for AWS IAM SASL mechanism, + see: https://docs.aws.amazon.com/msk/latest/developerguide/iam-access-control.html + properties: + accessKey: + type: string + secretKeySecretRef: + description: |- + SecretKeyRef contains enough information to inspect or modify the referred Secret data + See https://pkg.go.dev/k8s.io/api/core/v1#ObjectReference. + properties: + key: + description: Key in Secret data to get value + from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + sessionTokenSecretRef: + description: |- + SessionToken, if non-empty, is a session / security token to use for authentication. + See: https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html + properties: + key: + description: Key in Secret data to get value + from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + userAgent: + description: |- + UserAgent is the user agent to for the client to use when connecting + to Kafka, overriding the default "franz-go//". + + Setting a UserAgent allows authorizing based on the aws:UserAgent + condition key; see the following link for more details: + https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-useragent + type: string + required: + - accessKey + - secretKeySecretRef + - sessionTokenSecretRef + - userAgent + type: object + gssapi: + description: KafkaSASLGSSAPI represents the Kafka + Kerberos config. + properties: + authType: + type: string + enableFast: + description: |- + EnableFAST enables FAST, which is a pre-authentication framework for Kerberos. + It includes a mechanism for tunneling pre-authentication exchanges using armored KDC messages. + FAST provides increased resistance to passive password guessing attacks. + type: boolean + kerberosConfigPath: + type: string + keyTabPath: + type: string + passwordSecretRef: + description: |- + SecretKeyRef contains enough information to inspect or modify the referred Secret data + See https://pkg.go.dev/k8s.io/api/core/v1#ObjectReference. + properties: + key: + description: Key in Secret data to get value + from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + realm: + type: string + serviceName: + type: string + username: + type: string + required: + - authType + - enableFast + - kerberosConfigPath + - keyTabPath + - passwordSecretRef + - realm + - serviceName + - username + type: object + mechanism: + description: Specifies the SASL/SCRAM authentication + mechanism. + type: string + oauth: + description: KafkaSASLOAuthBearer is the config struct + for the SASL OAuthBearer mechanism + properties: + tokenSecretRef: + description: |- + SecretKeyRef contains enough information to inspect or modify the referred Secret data + See https://pkg.go.dev/k8s.io/api/core/v1#ObjectReference. + properties: + key: + description: Key in Secret data to get value + from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - tokenSecretRef + type: object + passwordSecretRef: + description: Specifies the password. + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + username: + description: Specifies the username. + type: string + required: + - mechanism + type: object + tls: + description: Defines TLS configuration settings for Redpanda + clusters that have TLS enabled. + properties: + caCertSecretRef: + description: CaCert is the reference for certificate + authority used to establish TLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + certSecretRef: + description: Cert is the reference for client public + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + insecureSkipTlsVerify: + description: InsecureSkipTLSVerify can skip verifying + Redpanda self-signed certificate when establish + TLS connection to Redpanda + type: boolean + keySecretRef: + description: Key is the reference for client private + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + required: + - brokers + type: object + schemaRegistry: + description: |- + SchemaRegistry is the configuration information for communicating with the Schema Registry + API of a Redpanda cluster where the object should be created. + properties: + sasl: + description: Defines authentication configuration settings + for Redpanda clusters that have authentication enabled. + properties: + mechanism: + description: Specifies the SASL/SCRAM authentication + mechanism. + type: string + passwordSecretRef: + description: Specifies the password. + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + token: + description: |- + SecretKeyRef contains enough information to inspect or modify the referred Secret data + See https://pkg.go.dev/k8s.io/api/core/v1#ObjectReference. + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + username: + description: Specifies the username. + type: string + required: + - mechanism + type: object + tls: + description: Defines TLS configuration settings for Redpanda + clusters that have TLS enabled. + properties: + caCertSecretRef: + description: CaCert is the reference for certificate + authority used to establish TLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + certSecretRef: + description: Cert is the reference for client public + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + insecureSkipTlsVerify: + description: InsecureSkipTLSVerify can skip verifying + Redpanda self-signed certificate when establish + TLS connection to Redpanda + type: boolean + keySecretRef: + description: Key is the reference for client private + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + urls: + description: Specifies a list of broker addresses in the + format : + items: + type: string + type: array + required: + - urls + type: object + type: object + type: object + x-kubernetes-validations: + - message: 'spec.cluster.staticConfiguration.schemaRegistry: required + value' + rule: '!has(self.staticConfiguration) || has(self.staticConfiguration.schemaRegistry)' + - message: either clusterRef or staticConfiguration must be set + rule: has(self.clusterRef) || has(self.staticConfiguration) + - message: ClusterSource is immutable + rule: self == oldSelf + compatibilityLevel: + default: Backward + description: CompatibilityLevel sets the compatibility level for the + given schema + enum: + - None + - Backward + - BackwardTransitive + - Forward + - ForwardTransitive + - Full + - FullTransitive + type: string + normalize: + default: true + description: Normalize sets whether or not the schema should be normalized. + type: boolean + references: + description: |- + References declares other schemas this schema references. See the + docs on SchemaReference for more details. + items: + description: |- + SchemaReference is a way for a one schema to reference another. The + details for how referencing is done are type specific; for example, + JSON objects that use the key "$ref" can refer to another schema via + URL. + properties: + name: + type: string + subject: + type: string + version: + type: integer + required: + - name + - subject + - version + type: object + type: array + schemaType: + default: avro + description: Type is the type of a schema. The default type is avro. + enum: + - avro + - protobuf + type: string + text: + description: Text is the actual unescaped text of a schema. + type: string + required: + - cluster + - text + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Synced + description: Represents the current status of the Redpanda schema. + properties: + conditions: + description: Conditions holds the conditions for the Redpanda schema. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGeneration: + description: Specifies the last observed generation. + format: int64 + type: integer + schemaHash: + description: SchemaHash is the hashed value of the schema synced to + the cluster + type: string + versions: + description: Versions shows the versions of a given schema + items: + type: integer + type: array + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/operator/config/crd/bases/cluster.redpanda.com_topics.yaml b/operator/config/crd/bases/cluster.redpanda.com_topics.yaml index 6b47eb774..14925bc96 100644 --- a/operator/config/crd/bases/cluster.redpanda.com_topics.yaml +++ b/operator/config/crd/bases/cluster.redpanda.com_topics.yaml @@ -412,11 +412,124 @@ spec: required: - brokers type: object - required: - - kafka + schemaRegistry: + description: |- + SchemaRegistry is the configuration information for communicating with the Schema Registry + API of a Redpanda cluster where the object should be created. + properties: + sasl: + description: Defines authentication configuration settings + for Redpanda clusters that have authentication enabled. + properties: + mechanism: + description: Specifies the SASL/SCRAM authentication + mechanism. + type: string + passwordSecretRef: + description: Specifies the password. + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + token: + description: |- + SecretKeyRef contains enough information to inspect or modify the referred Secret data + See https://pkg.go.dev/k8s.io/api/core/v1#ObjectReference. + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + username: + description: Specifies the username. + type: string + required: + - mechanism + type: object + tls: + description: Defines TLS configuration settings for Redpanda + clusters that have TLS enabled. + properties: + caCertSecretRef: + description: CaCert is the reference for certificate + authority used to establish TLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + certSecretRef: + description: Cert is the reference for client public + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + insecureSkipTlsVerify: + description: InsecureSkipTLSVerify can skip verifying + Redpanda self-signed certificate when establish + TLS connection to Redpanda + type: boolean + keySecretRef: + description: Key is the reference for client private + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + urls: + description: Specifies a list of broker addresses in the + format : + items: + type: string + type: array + required: + - urls + type: object type: object type: object x-kubernetes-validations: + - message: 'spec.cluster.staticConfiguration.kafka: required value' + rule: '!has(self.staticConfiguration) || has(self.staticConfiguration.kafka)' - message: either clusterRef or staticConfiguration must be set rule: has(self.clusterRef) || has(self.staticConfiguration) - message: ClusterSource is immutable @@ -1236,11 +1349,124 @@ spec: required: - brokers type: object - required: - - kafka + schemaRegistry: + description: |- + SchemaRegistry is the configuration information for communicating with the Schema Registry + API of a Redpanda cluster where the object should be created. + properties: + sasl: + description: Defines authentication configuration settings + for Redpanda clusters that have authentication enabled. + properties: + mechanism: + description: Specifies the SASL/SCRAM authentication + mechanism. + type: string + passwordSecretRef: + description: Specifies the password. + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + token: + description: |- + SecretKeyRef contains enough information to inspect or modify the referred Secret data + See https://pkg.go.dev/k8s.io/api/core/v1#ObjectReference. + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + username: + description: Specifies the username. + type: string + required: + - mechanism + type: object + tls: + description: Defines TLS configuration settings for Redpanda + clusters that have TLS enabled. + properties: + caCertSecretRef: + description: CaCert is the reference for certificate + authority used to establish TLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + certSecretRef: + description: Cert is the reference for client public + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + insecureSkipTlsVerify: + description: InsecureSkipTLSVerify can skip verifying + Redpanda self-signed certificate when establish + TLS connection to Redpanda + type: boolean + keySecretRef: + description: Key is the reference for client private + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + urls: + description: Specifies a list of broker addresses in the + format : + items: + type: string + type: array + required: + - urls + type: object type: object type: object x-kubernetes-validations: + - message: 'spec.cluster.staticConfiguration.kafka: required value' + rule: '!has(self.staticConfiguration) || has(self.staticConfiguration.kafka)' - message: either clusterRef or staticConfiguration must be set rule: has(self.clusterRef) || has(self.staticConfiguration) - message: ClusterSource is immutable diff --git a/operator/config/crd/bases/cluster.redpanda.com_users.yaml b/operator/config/crd/bases/cluster.redpanda.com_users.yaml index 778b2ebc1..b8dec0dec 100644 --- a/operator/config/crd/bases/cluster.redpanda.com_users.yaml +++ b/operator/config/crd/bases/cluster.redpanda.com_users.yaml @@ -592,13 +592,126 @@ spec: required: - brokers type: object - required: - - kafka + schemaRegistry: + description: |- + SchemaRegistry is the configuration information for communicating with the Schema Registry + API of a Redpanda cluster where the object should be created. + properties: + sasl: + description: Defines authentication configuration settings + for Redpanda clusters that have authentication enabled. + properties: + mechanism: + description: Specifies the SASL/SCRAM authentication + mechanism. + type: string + passwordSecretRef: + description: Specifies the password. + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + token: + description: |- + SecretKeyRef contains enough information to inspect or modify the referred Secret data + See https://pkg.go.dev/k8s.io/api/core/v1#ObjectReference. + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + username: + description: Specifies the username. + type: string + required: + - mechanism + type: object + tls: + description: Defines TLS configuration settings for Redpanda + clusters that have TLS enabled. + properties: + caCertSecretRef: + description: CaCert is the reference for certificate + authority used to establish TLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + certSecretRef: + description: Cert is the reference for client public + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + insecureSkipTlsVerify: + description: InsecureSkipTLSVerify can skip verifying + Redpanda self-signed certificate when establish + TLS connection to Redpanda + type: boolean + keySecretRef: + description: Key is the reference for client private + certificate to establish mTLS connection to Redpanda + properties: + key: + description: Key in Secret data to get value from + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + urls: + description: Specifies a list of broker addresses in the + format : + items: + type: string + type: array + required: + - urls + type: object type: object type: object x-kubernetes-validations: - message: 'spec.cluster.staticConfiguration.admin: required value' rule: '!has(self.staticConfiguration) || has(self.staticConfiguration.admin)' + - message: 'spec.cluster.staticConfiguration.kafka: required value' + rule: '!has(self.staticConfiguration) || has(self.staticConfiguration.kafka)' - message: either clusterRef or staticConfiguration must be set rule: has(self.clusterRef) || has(self.staticConfiguration) - message: ClusterSource is immutable diff --git a/operator/config/rbac/bases/operator/role.yaml b/operator/config/rbac/bases/operator/role.yaml index fa7ae3e51..5b300e212 100644 --- a/operator/config/rbac/bases/operator/role.yaml +++ b/operator/config/rbac/bases/operator/role.yaml @@ -97,6 +97,7 @@ rules: - apiGroups: - cluster.redpanda.com resources: + - schemas - topics - users verbs: @@ -108,6 +109,7 @@ rules: - apiGroups: - cluster.redpanda.com resources: + - schemas/finalizers - topics/finalizers - users/finalizers verbs: @@ -115,6 +117,7 @@ rules: - apiGroups: - cluster.redpanda.com resources: + - schemas/status - topics/status - users/status verbs: @@ -308,6 +311,7 @@ rules: - cluster.redpanda.com resources: - redpandas/finalizers + - schemas/finalizers - topics/finalizers - users/finalizers verbs: @@ -316,6 +320,7 @@ rules: - cluster.redpanda.com resources: - redpandas/status + - schemas/status - topics/status - users/status verbs: @@ -325,6 +330,7 @@ rules: - apiGroups: - cluster.redpanda.com resources: + - schemas - topics - users verbs: diff --git a/operator/go.mod b/operator/go.mod index b41639511..c64541bcc 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -27,7 +27,7 @@ require ( github.com/prometheus/common v0.55.0 github.com/redpanda-data/common-go/rpadmin v0.1.7-0.20240916201938-8d748d9ac10b github.com/redpanda-data/console/backend v0.0.0-20240303221210-05d5d9e85f20 - github.com/redpanda-data/helm-charts v0.0.0-20240920171404-e6c075329771 + github.com/redpanda-data/helm-charts v0.0.0-20241015140509-56e8cc7a5e8a github.com/redpanda-data/redpanda/src/go/rpk v0.0.0-20240827155712-244863ea0ae8 github.com/scalalang2/golang-fifo v1.0.2 github.com/spf13/afero v1.11.0 @@ -35,10 +35,11 @@ require ( github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.33.0 github.com/testcontainers/testcontainers-go/modules/redpanda v0.32.0 - github.com/twmb/franz-go v1.17.0 + github.com/twmb/franz-go v1.18.0 github.com/twmb/franz-go/pkg/kadm v1.12.0 - github.com/twmb/franz-go/pkg/kmsg v1.8.0 + github.com/twmb/franz-go/pkg/kmsg v1.9.0 github.com/twmb/franz-go/pkg/sasl/kerberos v1.1.0 + github.com/twmb/franz-go/pkg/sr v1.2.0 go.uber.org/zap v1.27.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/operator/go.sum b/operator/go.sum index f5879d3a2..1101b87b1 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -1093,8 +1093,8 @@ github.com/redpanda-data/flux-controller-shim/helm/shim v0.0.0-20231227162419-a4 github.com/redpanda-data/flux-controller-shim/helm/shim v0.0.0-20231227162419-a45126310240/go.mod h1:5KLXArOMFOrwb3BihpFaRNiPCyo9AXsXhvMdUmrCdUg= github.com/redpanda-data/flux-controller-shim/source/shim v0.0.0-20240113100428-5e301ef97b19 h1:sJjDhnIbTMOuP4Rnhm1N3GNfgv6BJlocCnGliNvhgbw= github.com/redpanda-data/flux-controller-shim/source/shim v0.0.0-20240113100428-5e301ef97b19/go.mod h1:T39OECA7eOlhpHZPBSGg+bpuwtt/G4m03fjBkJ821CM= -github.com/redpanda-data/helm-charts v0.0.0-20240920171404-e6c075329771 h1:yqbIHCsRpUz4lrX9LuKb8Xc4vP5bVHVrOzUgHRGuiaE= -github.com/redpanda-data/helm-charts v0.0.0-20240920171404-e6c075329771/go.mod h1:uEMmuH+gTppAsZZNYlUbh6tuxN3fqffWY0Bi8AcE2Zk= +github.com/redpanda-data/helm-charts v0.0.0-20241015140509-56e8cc7a5e8a h1:kNx8tH6z02nJctZS+oYt7LOWWoZgfMW4Ktees89GTh0= +github.com/redpanda-data/helm-charts v0.0.0-20241015140509-56e8cc7a5e8a/go.mod h1:TqCaTv9K8+VbAeZHlR/OqHVksTlj0HYAzDkUEtZNyZc= github.com/redpanda-data/helm-controller v0.37.3-0.20240119022335-c90fadbd044e h1:8HB05vSCY+0MwjT2DIVq6gJV5iw7nQNIDfMqcc1NEC8= github.com/redpanda-data/helm-controller v0.37.3-0.20240119022335-c90fadbd044e/go.mod h1:jF5kbQy3qT/zufL27DE3lecfYTRWeAzSiVmrbDDQwUw= github.com/redpanda-data/redpanda/src/go/rpk v0.0.0-20240827155712-244863ea0ae8 h1:uTQKqF8UPNxYxKBJ11VlG6Vt2l9ctkkeXsmmjHUSUG4= @@ -1259,15 +1259,17 @@ github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8t github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/twmb/franz-go v1.7.0/go.mod h1:PMze0jNfNghhih2XHbkmTFykbMF5sJqmNJB31DOOzro= -github.com/twmb/franz-go v1.17.0 h1:hawgCx5ejDHkLe6IwAtFWwxi3OU4OztSTl7ZV5rwkYk= -github.com/twmb/franz-go v1.17.0/go.mod h1:NreRdJ2F7dziDY/m6VyspWd6sNxHKXdMZI42UfQ3GXM= +github.com/twmb/franz-go v1.18.0 h1:25FjMZfdozBywVX+5xrWC2W+W76i0xykKjTdEeD2ejw= +github.com/twmb/franz-go v1.18.0/go.mod h1:zXCGy74M0p5FbXsLeASdyvfLFsBvTubVqctIaa5wQ+I= github.com/twmb/franz-go/pkg/kadm v1.12.0 h1:I8P/gpXFzhl73QcAYmJu+1fOXvrynyH/MAotr2udEg4= github.com/twmb/franz-go/pkg/kadm v1.12.0/go.mod h1:VMvpfjz/szpH9WB+vGM+rteTzVv0djyHFimci9qm2C0= github.com/twmb/franz-go/pkg/kmsg v1.2.0/go.mod h1:SxG/xJKhgPu25SamAq0rrucfp7lbzCpEXOC+vH/ELrY= -github.com/twmb/franz-go/pkg/kmsg v1.8.0 h1:lAQB9Z3aMrIP9qF9288XcFf/ccaSxEitNA1CDTEIeTA= -github.com/twmb/franz-go/pkg/kmsg v1.8.0/go.mod h1:HzYEb8G3uu5XevZbtU0dVbkphaKTHk0X68N5ka4q6mU= +github.com/twmb/franz-go/pkg/kmsg v1.9.0 h1:JojYUph2TKAau6SBtErXpXGC7E3gg4vGZMv9xFU/B6M= +github.com/twmb/franz-go/pkg/kmsg v1.9.0/go.mod h1:CMbfazviCyY6HM0SXuG5t9vOwYDHRCSrJJyBAe5paqg= github.com/twmb/franz-go/pkg/sasl/kerberos v1.1.0 h1:alKdbddkPw3rDh+AwmUEwh6HNYgTvDSFIe/GWYRR9RM= github.com/twmb/franz-go/pkg/sasl/kerberos v1.1.0/go.mod h1:k8BoBjyUbFj34f0rRbn+Ky12sZFAPbmShrg0karAIMo= +github.com/twmb/franz-go/pkg/sr v1.2.0 h1:zYr0Ly7KLFfeCGaSr8teN6LvAVeYVrZoUsyyPHTYB+M= +github.com/twmb/franz-go/pkg/sr v1.2.0/go.mod h1:gpd2Xl5/prkj3gyugcL+rVzagjaxFqMgvKMYcUlrpDw= github.com/twmb/tlscfg v1.2.1 h1:IU2efmP9utQEIV2fufpZjPq7xgcZK4qu25viD51BB44= github.com/twmb/tlscfg v1.2.1/go.mod h1:GameEQddljI+8Es373JfQEBvtI4dCTLKWGJbqT2kErs= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= diff --git a/operator/internal/controller/redpanda/resource_controller_test.go b/operator/internal/controller/redpanda/resource_controller_test.go index 594f4f89a..626648c96 100644 --- a/operator/internal/controller/redpanda/resource_controller_test.go +++ b/operator/internal/controller/redpanda/resource_controller_test.go @@ -145,6 +145,17 @@ func InitializeResourceReconcilerTest[T any, U Resource[T]](t *testing.T, ctx co Mechanism: redpandav1alpha2.SASLMechanismScramSHA256, }, }, + SchemaRegistry: &redpandav1alpha2.SchemaRegistrySpec{ + URLs: []string{schemaRegistry}, + SASL: &redpandav1alpha2.SchemaRegistrySASL{ + Username: "superuser", + Password: redpandav1alpha2.SecretKeyRef{ + Name: "superuser", + Key: "password", + }, + Mechanism: redpandav1alpha2.SASLMechanismScramSHA256, + }, + }, }, } @@ -172,6 +183,17 @@ func InitializeResourceReconcilerTest[T any, U Resource[T]](t *testing.T, ctx co Mechanism: redpandav1alpha2.SASLMechanismScramSHA256, }, }, + SchemaRegistry: &redpandav1alpha2.SchemaRegistrySpec{ + URLs: []string{schemaRegistry}, + SASL: &redpandav1alpha2.SchemaRegistrySASL{ + Username: "superuser", + Password: redpandav1alpha2.SecretKeyRef{ + Name: "invalidsuperuser", + Key: "password", + }, + Mechanism: redpandav1alpha2.SASLMechanismScramSHA256, + }, + }, }, } @@ -183,6 +205,9 @@ func InitializeResourceReconcilerTest[T any, U Resource[T]](t *testing.T, ctx co Admin: &redpandav1alpha2.AdminAPISpec{ URLs: []string{adminAPI}, }, + SchemaRegistry: &redpandav1alpha2.SchemaRegistrySpec{ + URLs: []string{schemaRegistry}, + }, }, } diff --git a/operator/internal/controller/redpanda/schema_controller.go b/operator/internal/controller/redpanda/schema_controller.go new file mode 100644 index 000000000..f93b408b1 --- /dev/null +++ b/operator/internal/controller/redpanda/schema_controller.go @@ -0,0 +1,105 @@ +// Copyright 2024 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 redpanda reconciles resources that comes from Redpanda dictionary like Topic, ACL and more. +package redpanda + +import ( + "context" + "time" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + redpandav1alpha2ac "github.com/redpanda-data/redpanda-operator/operator/api/applyconfiguration/redpanda/v1alpha2" + redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2" + internalclient "github.com/redpanda-data/redpanda-operator/operator/pkg/client" + "github.com/redpanda-data/redpanda-operator/operator/pkg/client/kubernetes" + "github.com/redpanda-data/redpanda-operator/operator/pkg/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +//+kubebuilder:rbac:groups=cluster.redpanda.com,namespace=default,resources=schemas,verbs=get;list;watch;update;patch +//+kubebuilder:rbac:groups=cluster.redpanda.com,namespace=default,resources=schemas/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=cluster.redpanda.com,namespace=default,resources=schemas/finalizers,verbs=update + +// For cluster scoped operator + +//+kubebuilder:rbac:groups=cluster.redpanda.com,resources=schemas,verbs=get;list;watch;update;patch +//+kubebuilder:rbac:groups=cluster.redpanda.com,resources=schemas/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=cluster.redpanda.com,resources=schemas/finalizers,verbs=update + +// SchemaReconciler reconciles a schema object +type SchemaReconciler struct{} + +func (r *SchemaReconciler) FinalizerPatch(request ResourceRequest[*redpandav1alpha2.Schema]) client.Patch { + schema := request.object + config := redpandav1alpha2ac.Schema(schema.Name, schema.Namespace) + return kubernetes.ApplyPatch(config.WithFinalizers(FinalizerKey)) +} + +func (r *SchemaReconciler) SyncResource(ctx context.Context, request ResourceRequest[*redpandav1alpha2.Schema]) (client.Patch, error) { + schema := request.object + createPatch := func(err error, hash string, versions []int) (client.Patch, error) { + var syncCondition metav1.Condition + config := redpandav1alpha2ac.Schema(schema.Name, schema.Namespace) + + if err != nil { + syncCondition, err = handleResourceSyncErrors(err) + } else { + syncCondition = redpandav1alpha2.ResourceSyncedCondition(schema.Name) + } + + return kubernetes.ApplyPatch(config.WithStatus(redpandav1alpha2ac.SchemaStatus(). + WithObservedGeneration(schema.Generation). + WithVersions(versions...). + WithSchemaHash(hash). + WithConditions(utils.StatusConditionConfigs(schema.Status.Conditions, schema.Generation, []metav1.Condition{ + syncCondition, + })...))), err + } + + hash := schema.Status.SchemaHash + versions := schema.Status.Versions + syncer, err := request.factory.Schemas(ctx, schema) + if err != nil { + return createPatch(err, hash, versions) + } + + hash, versions, err = syncer.Sync(ctx, schema) + return createPatch(err, hash, versions) +} + +func (r *SchemaReconciler) DeleteResource(ctx context.Context, request ResourceRequest[*redpandav1alpha2.Schema]) error { + syncer, err := request.factory.Schemas(ctx, request.object) + if err != nil { + return err + } + return syncer.Delete(ctx, request.object) +} + +func SetupSchemaController(ctx context.Context, mgr ctrl.Manager) error { + c := mgr.GetClient() + config := mgr.GetConfig() + factory := internalclient.NewFactory(config, c) + controller := NewResourceController(c, factory, &SchemaReconciler{}, "SchemaReconciler") + + enqueueSchema, err := registerClusterSourceIndex(ctx, mgr, "schema", &redpandav1alpha2.Schema{}, &redpandav1alpha2.SchemaList{}) + if err != nil { + return err + } + + return ctrl.NewControllerManagedBy(mgr). + For(&redpandav1alpha2.Schema{}). + Watches(&redpandav1alpha2.Redpanda{}, enqueueSchema). + // Every 5 minutes try and check to make sure no manual modifications + // happened on the resource synced to the cluster and attempt to correct + // any drift. + Complete(controller.PeriodicallyReconcile(5 * time.Minute)) +} diff --git a/operator/internal/controller/redpanda/schema_controller_test.go b/operator/internal/controller/redpanda/schema_controller_test.go new file mode 100644 index 000000000..4794f2c59 --- /dev/null +++ b/operator/internal/controller/redpanda/schema_controller_test.go @@ -0,0 +1,134 @@ +// Copyright 2024 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 redpanda + +import ( + "context" + "fmt" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2" + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +func TestSchemaReconcile(t *testing.T) { // nolint:funlen // These tests have clear subtests. + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*2) + defer cancel() + + environment := InitializeResourceReconcilerTest(t, ctx, &SchemaReconciler{}) + + baseSchema := &redpandav1alpha2.Schema{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceDefault, + }, + Spec: redpandav1alpha2.SchemaSpec{ + ClusterSource: environment.ClusterSourceValid, + Text: `{ + "type": "record", + "name": "test", + "fields": + [ + { + "type": "string", + "name": "field1" + }, + { + "type": "int", + "name": "field2" + } + ] + }`, + }, + } + + for name, tt := range map[string]struct { + mutate func(schema *redpandav1alpha2.Schema) + expectedCondition metav1.Condition + }{ + "success": { + expectedCondition: environment.SyncedCondition, + }, + "error - invalid cluster ref": { + mutate: func(schema *redpandav1alpha2.Schema) { + schema.Spec.ClusterSource = environment.ClusterSourceInvalidRef + }, + expectedCondition: environment.InvalidClusterRefCondition, + }, + "error - client error no SASL": { + mutate: func(schema *redpandav1alpha2.Schema) { + schema.Spec.ClusterSource = environment.ClusterSourceNoSASL + }, + expectedCondition: environment.ClientErrorCondition, + }, + "error - client error invalid credentials": { + mutate: func(schema *redpandav1alpha2.Schema) { + schema.Spec.ClusterSource = environment.ClusterSourceBadPassword + }, + expectedCondition: environment.ClientErrorCondition, + }, + } { + t.Run(name, func(t *testing.T) { + schema := baseSchema.DeepCopy() + schema.Name = "schema" + strconv.Itoa(int(time.Now().UnixNano())) + + if tt.mutate != nil { + tt.mutate(schema) + } + + key := client.ObjectKeyFromObject(schema) + req := ctrl.Request{NamespacedName: key} + + require.NoError(t, environment.Factory.Create(ctx, schema)) + _, err := environment.Reconciler.Reconcile(ctx, req) + require.NoError(t, err) + + require.NoError(t, environment.Factory.Get(ctx, key, schema)) + require.Equal(t, []string{FinalizerKey}, schema.Finalizers) + require.Len(t, schema.Status.Conditions, 1) + require.Equal(t, tt.expectedCondition.Type, schema.Status.Conditions[0].Type) + require.Equal(t, tt.expectedCondition.Reason, schema.Status.Conditions[0].Reason) + require.Equal(t, tt.expectedCondition.Status, schema.Status.Conditions[0].Status) + + if tt.expectedCondition.Status == metav1.ConditionTrue { //nolint:nestif // ignore + schemaClient, err := environment.Factory.SchemaRegistryClient(ctx, schema) + require.NoError(t, err) + require.NotNil(t, schemaClient) + + _, err = schemaClient.SchemaByVersion(ctx, schema.Name, -1) + require.NoError(t, err) + + // clean up and make sure we properly delete everything + require.NoError(t, environment.Factory.Delete(ctx, schema)) + _, err = environment.Reconciler.Reconcile(ctx, req) + require.NoError(t, err) + require.True(t, apierrors.IsNotFound(environment.Factory.Get(ctx, key, schema))) + + // make sure we no longer have a schema + _, err = schemaClient.SchemaByVersion(ctx, schema.Name, -1) + require.EqualError(t, err, fmt.Sprintf("Subject '%s' not found.", schema.Name)) + + return + } + + // clean up and make sure we properly delete everything + require.NoError(t, environment.Factory.Delete(ctx, schema)) + _, err = environment.Reconciler.Reconcile(ctx, req) + require.NoError(t, err) + require.True(t, apierrors.IsNotFound(environment.Factory.Get(ctx, key, schema))) + }) + } +} diff --git a/operator/pkg/client/cluster.go b/operator/pkg/client/cluster.go index 007d4e868..a734d8fec 100644 --- a/operator/pkg/client/cluster.go +++ b/operator/pkg/client/cluster.go @@ -17,6 +17,7 @@ import ( "github.com/twmb/franz-go/pkg/kgo" "github.com/twmb/franz-go/pkg/sasl" "github.com/twmb/franz-go/pkg/sasl/scram" + "github.com/twmb/franz-go/pkg/sr" ) // RedpandaAdminForCluster returns a simple kgo.Client able to communicate with the given cluster specified via a Redpanda cluster. @@ -41,6 +42,28 @@ func (c *Factory) redpandaAdminForCluster(cluster *redpandav1alpha2.Redpanda) (* return client, nil } +// schemaRegistryForCluster returns a simple kgo.Client able to communicate with the given cluster specified via a Redpanda cluster. +func (c *Factory) schemaRegistryForCluster(cluster *redpandav1alpha2.Redpanda) (*sr.Client, error) { + dot, err := cluster.GetDot(c.config) + if err != nil { + return nil, err + } + + client, err := redpanda.SchemaRegistryClient(dot, c.dialer) + if err != nil { + return nil, err + } + + if c.userAuth != nil { + client, err = sr.NewClient(append(client.Opts(), sr.BasicAuth(c.userAuth.Username, c.userAuth.Password))...) + if err != nil { + return nil, err + } + } + + return client, nil +} + // KafkaForCluster returns a simple kgo.Client able to communicate with the given cluster specified via a Redpanda cluster. func (c *Factory) kafkaForCluster(cluster *redpandav1alpha2.Redpanda, opts ...kgo.Opt) (*kgo.Client, error) { dot, err := cluster.GetDot(c.config) diff --git a/operator/pkg/client/factory.go b/operator/pkg/client/factory.go index ffc0299cc..113df2011 100644 --- a/operator/pkg/client/factory.go +++ b/operator/pkg/client/factory.go @@ -17,9 +17,11 @@ import ( "github.com/redpanda-data/helm-charts/pkg/redpanda" redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2" "github.com/redpanda-data/redpanda-operator/operator/pkg/client/acls" + "github.com/redpanda-data/redpanda-operator/operator/pkg/client/schemas" "github.com/redpanda-data/redpanda-operator/operator/pkg/client/users" "github.com/twmb/franz-go/pkg/kadm" "github.com/twmb/franz-go/pkg/kgo" + "github.com/twmb/franz-go/pkg/sr" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/rest" @@ -63,11 +65,19 @@ type ClientFactory interface { // interface to properly initialize. RedpandaAdminClient(ctx context.Context, object client.Object) (*rpadmin.AdminAPI, error) + // SchemaRegistryClient initializes an sr.Client based on the spec of the passed in struct. + // The struct *must* implement either the v1alpha2.SchemaRegistryConnectedObject interface of the v1alpha2.ClusterReferencingObject + // interface to properly initialize. + SchemaRegistryClient(ctx context.Context, object client.Object) (*sr.Client, error) + // ACLs returns a high-level client for synchronizing ACLs. ACLs(ctx context.Context, object redpandav1alpha2.ClusterReferencingObject, opts ...kgo.Opt) (*acls.Syncer, error) // Users returns a high-level client for managing users. Users(ctx context.Context, object redpandav1alpha2.ClusterReferencingObject, opts ...kgo.Opt) (*users.Client, error) + + // Schemas returns a high-level client for synchronizing Schemas. + Schemas(ctx context.Context, object redpandav1alpha2.ClusterReferencingObject) (*schemas.Syncer, error) } type Factory struct { @@ -149,6 +159,37 @@ func (c *Factory) RedpandaAdminClient(ctx context.Context, obj client.Object) (* return nil, ErrInvalidRedpandaClientObject } +func (c *Factory) SchemaRegistryClient(ctx context.Context, obj client.Object) (*sr.Client, error) { + // if we pass in a Redpanda cluster, just use it + if cluster, ok := obj.(*redpandav1alpha2.Redpanda); ok { + return c.schemaRegistryForCluster(cluster) + } + + cluster, err := c.getCluster(ctx, obj) + if err != nil { + return nil, err + } + + if cluster != nil { + return c.schemaRegistryForCluster(cluster) + } + + if spec := c.getSchemaRegistrySpec(obj); spec != nil { + return c.schemaRegistryForSpec(ctx, obj.GetNamespace(), spec) + } + + return nil, ErrInvalidRedpandaClientObject +} + +func (c *Factory) Schemas(ctx context.Context, obj redpandav1alpha2.ClusterReferencingObject) (*schemas.Syncer, error) { + schemaRegistryClient, err := c.SchemaRegistryClient(ctx, obj) + if err != nil { + return nil, err + } + + return schemas.NewSyncer(schemaRegistryClient), nil +} + func (c *Factory) ACLs(ctx context.Context, obj redpandav1alpha2.ClusterReferencingObject, opts ...kgo.Opt) (*acls.Syncer, error) { kafkaClient, err := c.KafkaClient(ctx, obj, opts...) if err != nil { @@ -227,3 +268,13 @@ func (c *Factory) getAdminSpec(obj client.Object) *redpandav1alpha2.AdminAPISpec return nil } + +func (c *Factory) getSchemaRegistrySpec(obj client.Object) *redpandav1alpha2.SchemaRegistrySpec { + if o, ok := obj.(redpandav1alpha2.ClusterReferencingObject); ok { + if source := o.GetClusterSource(); source != nil { + return source.GetSchemaRegistrySpec() + } + } + + return nil +} diff --git a/operator/pkg/client/schemas/schema.go b/operator/pkg/client/schemas/schema.go new file mode 100644 index 000000000..1e8d67f36 --- /dev/null +++ b/operator/pkg/client/schemas/schema.go @@ -0,0 +1,183 @@ +// Copyright 2024 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 schemas + +import ( + "slices" + + redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2" + "github.com/redpanda-data/redpanda-operator/operator/pkg/functional" + "github.com/twmb/franz-go/pkg/sr" +) + +type schema struct { + Subject string + Normalize bool + CompatibilityLevel sr.CompatibilityLevel + Schema string + Type sr.SchemaType + References []sr.SchemaReference + SchemaMetadata *sr.SchemaMetadata + SchemaRuleSet *sr.SchemaRuleSet + Hash string +} + +func (s *schema) toKafka() sr.Schema { + return sr.Schema{ + Schema: s.Schema, + Type: s.Type, + References: s.References, + SchemaMetadata: s.SchemaMetadata, + SchemaRuleSet: s.SchemaRuleSet, + } +} + +func schemaFromV1Alpha2Schema(s *redpandav1alpha2.Schema) (*schema, error) { + hash, err := s.Spec.SchemaHash() + if err != nil { + return nil, err + } + return &schema{ + Subject: s.Name, + Normalize: s.Spec.GetNormalize(), + CompatibilityLevel: s.Spec.GetCompatibilityLevel().ToKafka(), + Schema: s.Spec.Text, + Type: s.Spec.GetType().ToKafka(), + References: functional.MapFn(redpandav1alpha2.SchemaReferenceToKafka, s.Spec.References), + Hash: hash, + }, nil +} + +func schemaFromRedpandaSubjectSchema(s *sr.SubjectSchema, hash string, compatibility sr.CompatibilityLevel, normalize bool) *schema { + return &schema{ + Subject: s.Subject, + Normalize: normalize, + CompatibilityLevel: compatibility, + Schema: s.Schema.Schema, + Type: s.Type, + References: s.References, + Hash: hash, + } +} + +func (s *schema) CompatibilityEquals(other *schema) bool { + return s.CompatibilityLevel == other.CompatibilityLevel && s.Normalize == other.Normalize +} + +func (s *schema) SchemaEquals(other *schema) bool { + // subject + if s.Subject != other.Subject { + return false + } + + // type + if s.Type != other.Type { + return false + } + + // schema + // we cheat here, rather than trying to match the normalized schema in the cluster + // we instead just check to see if we've changed at all in the CRD + if s.Hash != other.Hash { + return false + } + + // references + if !functional.CompareConvertibleSlices(s.References, other.References, schemaReferencesEqual) { + return false + } + + // metadata + if s.SchemaMetadata == nil && other.SchemaMetadata != nil { + return false + } + if s.SchemaMetadata != nil && other.SchemaMetadata == nil { + return false + } + if s.SchemaMetadata != nil && other.SchemaMetadata != nil { + if !functional.CompareMaps(s.SchemaMetadata.Properties, other.SchemaMetadata.Properties) { + return false + } + if !functional.CompareMapsFn(s.SchemaMetadata.Tags, other.SchemaMetadata.Tags, slices.Equal) { + return false + } + if !slices.Equal(s.SchemaMetadata.Sensitive, other.SchemaMetadata.Sensitive) { + return false + } + } + + // rule set + if s.SchemaRuleSet == nil && other.SchemaRuleSet != nil { + return false + } + if s.SchemaRuleSet != nil && other.SchemaRuleSet == nil { + return false + } + if s.SchemaRuleSet != nil && other.SchemaRuleSet != nil { + if !functional.CompareConvertibleSlices(s.SchemaRuleSet.DomainRules, other.SchemaRuleSet.DomainRules, schemaRulesEqual) { + return false + } + if !functional.CompareConvertibleSlices(s.SchemaRuleSet.MigrationRules, other.SchemaRuleSet.MigrationRules, schemaRulesEqual) { + return false + } + } + + return true +} + +func schemaReferencesEqual(a, b sr.SchemaReference) bool { + if a.Name != b.Name { + return false + } + if a.Subject != b.Subject { + return false + } + if a.Version != b.Version { + return false + } + return true +} + +func schemaRulesEqual(a, b sr.SchemaRule) bool { + if a.Name != b.Name { + return false + } + if a.Doc != b.Doc { + return false + } + if a.Kind != b.Kind { + return false + } + if a.Mode != b.Mode { + return false + } + if a.Type != b.Type { + return false + } + if !slices.Equal(a.Tags, b.Tags) { + return false + } + if !functional.CompareMaps(a.Params, b.Params) { + return false + } + if a.Expr != b.Expr { + return false + } + if a.OnSuccess != b.OnSuccess { + return false + } + if a.OnFailure != b.OnFailure { + return false + } + if a.Disabled != b.Disabled { + return false + } + return true +} diff --git a/operator/pkg/client/schemas/syncer.go b/operator/pkg/client/schemas/syncer.go new file mode 100644 index 000000000..c813ce832 --- /dev/null +++ b/operator/pkg/client/schemas/syncer.go @@ -0,0 +1,125 @@ +// Copyright 2024 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 schemas + +import ( + "context" + "errors" + + redpandav1alpha2 "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2" + "github.com/twmb/franz-go/pkg/sr" +) + +// Syncer synchronizes Schemas for the given object to Redpanda. +type Syncer struct { + client *sr.Client +} + +// NewSyncer initializes a Syncer. +func NewSyncer(client *sr.Client) *Syncer { + return &Syncer{ + client: client, + } +} + +// Sync synchronizes the schema in Redpanda. +func (s *Syncer) Sync(ctx context.Context, o *redpandav1alpha2.Schema) (string, []int, error) { + versions := o.Status.Versions + hash := o.Status.SchemaHash + + want, err := schemaFromV1Alpha2Schema(o) + if err != nil { + return hash, versions, err + } + + // default to creating the schema + createSchema := true + // default to setting compatibilty for the schema subject + setCompatibility := true + + if !s.isInitial(o) { + have, err := s.getLatest(ctx, o) + if err != nil { + return hash, versions, err + } + + setCompatibility = !have.CompatibilityEquals(want) + createSchema = !have.SchemaEquals(want) + } + + if setCompatibility { + if err := s.setCompatibility(ctx, want); err != nil { + return hash, versions, err + } + } + + if createSchema { + subjectSchema, err := s.client.CreateSchema(ctx, o.Name, want.toKafka()) + if err != nil { + return hash, versions, err + } + hash = want.Hash + versions = append(versions, subjectSchema.Version) + } + + return hash, versions, nil +} + +func (s *Syncer) isInitial(o *redpandav1alpha2.Schema) bool { + return len(o.Status.Versions) == 0 +} + +func (s *Syncer) setCompatibility(ctx context.Context, sc *schema) error { + results := s.client.SetCompatibility(ctx, sr.SetCompatibility{ + Level: sc.CompatibilityLevel, + Normalize: sc.Normalize, + }, sc.Subject) + if len(results) == 0 { + return errors.New("empty results returned from syncing compatability levels") + } + if err := results[0].Err; err != nil { + return err + } + + return nil +} + +func (s *Syncer) getLatest(ctx context.Context, o *redpandav1alpha2.Schema) (*schema, error) { + subjectSchema, err := s.client.SchemaByVersion(ctx, o.Name, -1) + if err != nil { + return nil, err + } + + var compatibility sr.CompatibilityLevel + var normalize bool + + results := s.client.Compatibility(ctx, o.Name) + if len(results) > 0 { + result := results[0] + if err := result.Err; err != nil { + return nil, err + } + compatibility = result.Level + normalize = result.Normalize + } + + return schemaFromRedpandaSubjectSchema(&subjectSchema, o.Status.SchemaHash, compatibility, normalize), nil +} + +// Delete removes the schema in Redpanda. +func (s *Syncer) Delete(ctx context.Context, o *redpandav1alpha2.Schema) error { + if _, err := s.client.DeleteSubject(ctx, o.Name, sr.SoftDelete); err != nil { + return err + } + if _, err := s.client.DeleteSubject(ctx, o.Name, sr.HardDelete); err != nil { + return err + } + return nil +} diff --git a/operator/pkg/client/schemas/syncer_test.go b/operator/pkg/client/schemas/syncer_test.go new file mode 100644 index 000000000..f9ce31f15 --- /dev/null +++ b/operator/pkg/client/schemas/syncer_test.go @@ -0,0 +1,161 @@ +// Copyright 2024 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 schemas + +import ( + "context" + "testing" + "time" + + "github.com/redpanda-data/redpanda-operator/operator/api/redpanda/v1alpha2" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go/modules/redpanda" + "github.com/twmb/franz-go/pkg/sr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +const validSchema = ` +{ + "type": "record", + "name": "test", + "fields": + [ + { + "type": "string", + "name": "field1" + }, + { + "type": "int", + "name": "field2" + } + ] +} +` + +func normalizeSchema(t *testing.T, ctx context.Context, syncer *Syncer, schema *v1alpha2.Schema) { + actualSchema, err := syncer.getLatest(ctx, schema) + require.NoError(t, err) + schema.Spec.Text = actualSchema.Schema + hash, err := schema.Spec.SchemaHash() + require.NoError(t, err) + schema.Status.SchemaHash = hash +} + +func expectSchemasMatch(t *testing.T, ctx context.Context, syncer *Syncer, schema *v1alpha2.Schema) { + normalizeSchema(t, ctx, syncer, schema) + + expectedSchema, err := schemaFromV1Alpha2Schema(schema) + require.NoError(t, err) + + actualSchema, err := syncer.getLatest(ctx, schema) + require.NoError(t, err) + + require.True(t, expectedSchema.CompatibilityEquals(actualSchema), "Compatability levels not equal %+v != %+v", actualSchema.CompatibilityLevel, expectedSchema.CompatibilityLevel) + require.Equal(t, expectedSchema.Schema, actualSchema.Schema) + require.True(t, expectedSchema.SchemaEquals(actualSchema), "Schemas not equal %+v != %+v", actualSchema, expectedSchema) +} + +func expectSchemaUpdate(t *testing.T, ctx context.Context, syncer *Syncer, schema *v1alpha2.Schema, update bool) { + t.Helper() + + _, versions, err := syncer.Sync(ctx, schema) + require.NoError(t, err) + + if !update { + require.EqualValues(t, schema.Status.Versions, versions) + } else { + require.Len(t, versions, len(schema.Status.Versions)+1, "update expected, but didn't create another schema version") + schema.Status.Versions = versions + } + + expectSchemasMatch(t, ctx, syncer, schema) + + if update { + // check to make sure we don't update again + expectSchemaUpdate(t, ctx, syncer, schema, false) + } +} + +func TestSyncer(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*2) + defer cancel() + + container, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.2.8", + redpanda.WithEnableSchemaRegistryHTTPBasicAuth(), + redpanda.WithEnableKafkaAuthorization(), + redpanda.WithEnableSASL(), + redpanda.WithSuperusers("user"), + redpanda.WithNewServiceAccount("user", "password"), + ) + + require.NoError(t, err) + + schemaRegistry, err := container.SchemaRegistryAddress(ctx) + require.NoError(t, err) + + schemaRegistryClient, err := sr.NewClient(sr.BasicAuth("user", "password"), sr.URLs(schemaRegistry)) + require.NoError(t, err) + + syncer := NewSyncer(schemaRegistryClient) + + schema := &v1alpha2.Schema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "schema", + }, + Spec: v1alpha2.SchemaSpec{ + Text: validSchema, + }, + } + + reference := &v1alpha2.Schema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "reference", + }, + Spec: v1alpha2.SchemaSpec{ + Text: validSchema, + }, + } + + // create initial schema and reference + expectSchemaUpdate(t, ctx, syncer, schema, true) + expectSchemaUpdate(t, ctx, syncer, reference, true) + + // update references + schema.Spec.References = []v1alpha2.SchemaReference{ + { + Subject: reference.Name, + Name: "test", + Version: 1, + }, + } + expectSchemaUpdate(t, ctx, syncer, schema, true) + + // update metadata + // TODO: metadata is not supported + + // update type + // TODO: protobuf, JSON is not supported + + // update schema rules + // TODO: rules not supported + + // update compatibility level + schema.Spec.CompatibilityLevel = ptr.To(v1alpha2.CompatabilityLevelFull) + expectSchemaUpdate(t, ctx, syncer, schema, false) + + // delete + err = syncer.Delete(ctx, schema) + require.NoError(t, err) + + subjects, err := schemaRegistryClient.Subjects(ctx) + require.NoError(t, err) + require.NotContains(t, subjects, schema.Name) +} diff --git a/operator/pkg/client/spec.go b/operator/pkg/client/spec.go index 52e1f18fb..b67555495 100644 --- a/operator/pkg/client/spec.go +++ b/operator/pkg/client/spec.go @@ -13,6 +13,7 @@ import ( "context" "crypto/tls" "net" + "net/http" "time" "github.com/redpanda-data/common-go/rpadmin" @@ -21,6 +22,7 @@ import ( "github.com/twmb/franz-go/pkg/kgo" "github.com/twmb/franz-go/pkg/sasl" "github.com/twmb/franz-go/pkg/sasl/scram" + "github.com/twmb/franz-go/pkg/sr" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -144,3 +146,55 @@ func (c *Factory) redpandaAdminForSpec(ctx context.Context, namespace string, sp return client, nil } + +func (c *Factory) schemaRegistryForSpec(ctx context.Context, namespace string, spec *redpandav1alpha2.SchemaRegistrySpec) (*sr.Client, error) { + if len(spec.URLs) == 0 { + return nil, ErrEmptyURLList + } + + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + MaxIdleConnsPerHost: 100, + DialContext: c.dialer, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + var err error + var tlsConfig *tls.Config + if spec.TLS != nil { + tlsConfig, err = c.configureSpecTLS(ctx, namespace, spec.TLS) + if err != nil { + return nil, err + } + transport.TLSClientConfig = tlsConfig + } + + opts := []sr.ClientOpt{ + sr.HTTPClient(&http.Client{ + Timeout: 5 * time.Second, + Transport: transport, + }), + } + + var username, password, token string + username, password, token, err = c.configureSchemaRegistrySpecSASL(ctx, namespace, spec) + if err != nil { + return nil, err + } + + if c.userAuth != nil { + opts = append(opts, sr.BasicAuth(c.userAuth.Username, c.userAuth.Password)) + } else if username != "" { + opts = append(opts, sr.BasicAuth(username, password)) + } else if token != "" { + opts = append(opts, sr.BearerToken(token)) + } + + opts = append(opts, sr.URLs(spec.URLs...)) + + return sr.NewClient(opts...) +} diff --git a/operator/pkg/client/spec_sasl.go b/operator/pkg/client/spec_sasl.go index 69a662f1f..4908bb205 100644 --- a/operator/pkg/client/spec_sasl.go +++ b/operator/pkg/client/spec_sasl.go @@ -55,6 +55,33 @@ func (c *Factory) configureAdminSpecSASL(ctx context.Context, namespace string, return "", "", "", fmt.Errorf("unsupported SASL mechanism: %s", spec.SASL.Mechanism) } +func (c *Factory) configureSchemaRegistrySpecSASL(ctx context.Context, namespace string, spec *redpandav1alpha2.SchemaRegistrySpec) (username, password, token string, err error) { + if spec.SASL == nil { + return "", "", "", nil + } + + //nolint:exhaustive // we don't need this to be exhaustive, as we only support 3 auth mechanisms in this API. + switch spec.SASL.Mechanism { + // SCRAM + case config.SASLMechanismScramSHA256, config.SASLMechanismScramSHA512: + p, err := spec.SASL.Password.GetValue(ctx, c.Client, namespace, "password") + if err != nil { + return "", "", "", fmt.Errorf("unable to fetch sasl password: %w", err) + } + + return spec.SASL.Username, string(p), "", nil + // OAUTH + case config.SASLMechanismOAuthBearer: + token, err := spec.SASL.AuthToken.GetValue(ctx, c.Client, namespace, "password") + if err != nil { + return "", "", "", fmt.Errorf("unable to fetch sasl token: %w", err) + } + return "", "", string(token), nil + } + + return "", "", "", fmt.Errorf("unsupported SASL mechanism: %s", spec.SASL.Mechanism) +} + func (c *Factory) configureKafkaSpecSASL(ctx context.Context, namespace string, spec *redpandav1alpha2.KafkaAPISpec) (kgo.Opt, error) { logger := log.FromContext(ctx) diff --git a/operator/pkg/functional/compare.go b/operator/pkg/functional/compare.go new file mode 100644 index 000000000..eb713ea3f --- /dev/null +++ b/operator/pkg/functional/compare.go @@ -0,0 +1,46 @@ +// Copyright 2024 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 functional + +func CompareMaps[T, U comparable](a, b map[T]U) bool { + if len(a) != len(b) { + return false + } + for key := range a { + if a[key] != b[key] { + return false + } + } + return true +} + +func CompareMapsFn[T comparable, U any](a, b map[T]U, fn func(U, U) bool) bool { + if len(a) != len(b) { + return false + } + for key := range a { + if !fn(a[key], b[key]) { + return false + } + } + return true +} + +func CompareConvertibleSlices[T, U any](a []T, b []U, fn func(T, U) bool) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if !fn(a[i], b[i]) { + return false + } + } + return true +}