From ee7c9246e1a9d9048332ea825bce623547c4b453 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 9 May 2025 10:17:21 +0200 Subject: [PATCH 01/11] feat: Add CRD previewer --- Cargo.lock | 9 + crates/stackable-crd-previewer/Cargo.toml | 20 ++ crates/stackable-crd-previewer/build.rs | 75 +++++ crates/stackable-crd-previewer/src/lib.rs | 1 + .../AuthenticationClass.yaml | 294 ++++++++++++++++++ generated-crd-previews/Listener.yaml | 123 ++++++++ generated-crd-previews/ListenerClass.yaml | 70 +++++ generated-crd-previews/PodListeners.yaml | 86 +++++ generated-crd-previews/S3Bucket.yaml | 160 ++++++++++ generated-crd-previews/S3Connection.yaml | 139 +++++++++ 10 files changed, 977 insertions(+) create mode 100644 crates/stackable-crd-previewer/Cargo.toml create mode 100644 crates/stackable-crd-previewer/build.rs create mode 100644 crates/stackable-crd-previewer/src/lib.rs create mode 100644 generated-crd-previews/AuthenticationClass.yaml create mode 100644 generated-crd-previews/Listener.yaml create mode 100644 generated-crd-previews/ListenerClass.yaml create mode 100644 generated-crd-previews/PodListeners.yaml create mode 100644 generated-crd-previews/S3Bucket.yaml create mode 100644 generated-crd-previews/S3Connection.yaml diff --git a/Cargo.lock b/Cargo.lock index c8fd63dba..8c88c993b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3119,6 +3119,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "stackable-crd-previewer" +version = "0.1.0" +dependencies = [ + "snafu 0.8.5", + "stackable-operator", + "stackable-shared", +] + [[package]] name = "stackable-operator" version = "0.92.0" diff --git a/crates/stackable-crd-previewer/Cargo.toml b/crates/stackable-crd-previewer/Cargo.toml new file mode 100644 index 000000000..667d3df5b --- /dev/null +++ b/crates/stackable-crd-previewer/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "stackable-crd-previewer" +description = "Previews parts of our CRD, so that the effect of changes to operators can be determined more easily" +version = "0.1.0" +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true + +[dependencies] +stackable-shared = { path = "../stackable-shared" } +stackable-operator = { path = "../stackable-operator" } + +snafu.workspace = true + +[build-dependencies] +stackable-shared = { path = "../stackable-shared" } +stackable-operator = { path = "../stackable-operator" } + +snafu.workspace = true diff --git a/crates/stackable-crd-previewer/build.rs b/crates/stackable-crd-previewer/build.rs new file mode 100644 index 000000000..0ad294935 --- /dev/null +++ b/crates/stackable-crd-previewer/build.rs @@ -0,0 +1,75 @@ +use std::fs::create_dir_all; + +use snafu::{Report, ResultExt, Snafu}; +use stackable_operator::{ + YamlSchema, + crd::{ + authentication::core::AuthenticationClass, + listener::{Listener, ListenerClass, PodListeners}, + s3::{S3Bucket, S3Connection}, + }, + kube::core::crd::MergeError, + shared::yaml::SerializeOptions, +}; + +const OPERATOR_VERSION: &str = "0.0.0-dev"; +const OUTPUT_DIR: &str = "../../generated-crd-previews"; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("Failed to merge CRD for CRD {crd}"))] + MergeCRD { source: MergeError, crd: String }, + + #[snafu(display("Failed to create output directory {dir}"))] + CreateOutputDir { source: std::io::Error, dir: String }, + + #[snafu(display("Failed to write CRD to output file"))] + WriteCRD { + source: stackable_shared::yaml::Error, + }, +} + +macro_rules! write_crd { + ($crd_name:ident, $stored_crd_version:ident) => { + $crd_name::merged_crd($crd_name::$stored_crd_version) + .with_context(|_| MergeCRDSnafu { + crd: stringify!($crd_name), + })? + .write_yaml_schema( + format!("{OUTPUT_DIR}/{}.yaml", stringify!($crd_name)), + OPERATOR_VERSION, + SerializeOptions::default(), + ) + .context(WriteCRDSnafu)?; + }; +} + +pub fn main() -> Report { + Report::capture(|| write_crds()) +} + +pub fn write_crds() -> Result<(), Error> { + create_dir_all(OUTPUT_DIR).with_context(|_| CreateOutputDirSnafu { + dir: OUTPUT_DIR.to_string(), + })?; + + // AuthenticationClass::merged_crd(AuthenticationClass::V1Alpha1) + // .with_context(|_| MergeCRDSnafu { + // crd: "AuthenticationClass".to_string(), + // })? + // .write_yaml_schema( + // format!("{OUTPUT_DIR}/{}.yaml", "AuthenticationClass"), + // OPERATOR_VERSION, + // SerializeOptions::default(), + // ) + // .context(WriteCRDSnafu)?; + + write_crd!(AuthenticationClass, V1Alpha1); + write_crd!(Listener, V1Alpha1); + write_crd!(ListenerClass, V1Alpha1); + write_crd!(PodListeners, V1Alpha1); + write_crd!(S3Bucket, V1Alpha1); + write_crd!(S3Connection, V1Alpha1); + + Ok(()) +} diff --git a/crates/stackable-crd-previewer/src/lib.rs b/crates/stackable-crd-previewer/src/lib.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/crates/stackable-crd-previewer/src/lib.rs @@ -0,0 +1 @@ + diff --git a/generated-crd-previews/AuthenticationClass.yaml b/generated-crd-previews/AuthenticationClass.yaml new file mode 100644 index 000000000..9ff169ae3 --- /dev/null +++ b/generated-crd-previews/AuthenticationClass.yaml @@ -0,0 +1,294 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: authenticationclasses.authentication.stackable.tech +spec: + group: authentication.stackable.tech + names: + categories: [] + kind: AuthenticationClass + plural: authenticationclasses + shortNames: [] + singular: authenticationclass + scope: Cluster + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for AuthenticationClassSpec via `CustomResource` + properties: + spec: + description: |- + The Stackable Platform uses the AuthenticationClass as a central mechanism to handle user authentication across supported products. + + The authentication mechanism needs to be configured only in the AuthenticationClass which is then referenced in the product. Multiple different authentication providers are supported. Learn more in the [authentication concept documentation][1] and the [Authentication with OpenLDAP tutorial][2]. + + [1]: https://docs.stackable.tech/home/nightly/concepts/authentication [2]: https://docs.stackable.tech/home/nightly/tutorials/authentication_with_openldap + properties: + provider: + description: Provider used for authentication like LDAP or Kerberos. + oneOf: + - required: + - static + - required: + - ldap + - required: + - oidc + - required: + - tls + - required: + - kerberos + properties: + kerberos: + description: The [Kerberos provider](https://docs.stackable.tech/home/nightly/concepts/authentication#_kerberos). The Kerberos AuthenticationClass is used when users should authenticate themselves via Kerberos. + properties: + kerberosSecretClass: + description: Mandatory SecretClass used to obtain keytabs. + type: string + required: + - kerberosSecretClass + type: object + ldap: + description: The [LDAP provider](https://docs.stackable.tech/home/nightly/concepts/authentication#_ldap). There is also the ["Authentication with LDAP" tutorial](https://docs.stackable.tech/home/nightly/tutorials/authentication_with_openldap) where you can learn to configure Superset and Trino with OpenLDAP. + properties: + bindCredentials: + description: In case you need a special account for searching the LDAP server you can specify it here. + nullable: true + properties: + scope: + description: '[Scope](https://docs.stackable.tech/home/nightly/secret-operator/scope) of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass).' + nullable: true + properties: + listenerVolumes: + default: [] + description: The listener volume scope allows Node and Service scopes to be inferred from the applicable listeners. This must correspond to Volume names in the Pod that mount Listeners. + items: + type: string + type: array + node: + default: false + description: The node scope is resolved to the name of the Kubernetes Node object that the Pod is running on. This will typically be the DNS name of the node. + type: boolean + pod: + default: false + description: The pod scope is resolved to the name of the Kubernetes Pod. This allows the secret to differentiate between StatefulSet replicas. + type: boolean + services: + default: [] + description: The service scope allows Pod objects to specify custom scopes. This should typically correspond to Service objects that the Pod participates in. + items: + type: string + type: array + type: object + secretClass: + description: '[SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) containing the LDAP bind credentials.' + type: string + required: + - secretClass + type: object + hostname: + description: 'Host of the LDAP server, for example: `my.ldap.server` or `127.0.0.1`.' + type: string + ldapFieldNames: + default: + email: mail + givenName: givenName + group: memberof + surname: sn + uid: uid + description: The name of the LDAP object fields. + properties: + email: + default: mail + description: The name of the email field + type: string + givenName: + default: givenName + description: The name of the firstname field + type: string + group: + default: memberof + description: The name of the group field + type: string + surname: + default: sn + description: The name of the lastname field + type: string + uid: + default: uid + description: The name of the username field + type: string + type: object + port: + description: Port of the LDAP server. If TLS is used defaults to 636 otherwise to 389. + format: uint16 + minimum: 0.0 + nullable: true + type: integer + searchBase: + default: '' + description: 'LDAP search base, for example: `ou=users,dc=example,dc=org`.' + type: string + searchFilter: + default: '' + description: 'LDAP query to filter users, for example: `(memberOf=cn=myTeam,ou=teams,dc=example,dc=org)`.' + type: string + tls: + description: Use a TLS connection. If not specified no TLS will be used. + nullable: true + properties: + verification: + description: The verification method used to verify the certificates of the server and/or the client. + oneOf: + - required: + - none + - required: + - server + properties: + none: + description: Use TLS but don't verify certificates. + type: object + server: + description: Use TLS and a CA certificate to verify the server. + properties: + caCert: + description: CA cert to verify the server. + oneOf: + - required: + - webPki + - required: + - secretClass + properties: + secretClass: + description: Name of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) which will provide the CA certificate. Note that a SecretClass does not need to have a key but can also work with just a CA certificate, so if you got provided with a CA cert but don't have access to the key you can still use this method. + type: string + webPki: + description: Use TLS and the CA certificates trusted by the common web browsers to verify the server. This can be useful when you e.g. use public AWS S3 or other public available services. + type: object + type: object + required: + - caCert + type: object + type: object + required: + - verification + type: object + required: + - hostname + type: object + oidc: + description: The OIDC provider can be used to configure OpenID Connect. + properties: + hostname: + description: Host of the identity provider, e.g. `my.keycloak.corp` or `127.0.0.1`. + type: string + port: + description: Port of the identity provider. If TLS is used defaults to 443, otherwise to 80. + format: uint16 + minimum: 0.0 + nullable: true + type: integer + principalClaim: + description: |- + If a product extracts some sort of "effective user" that is represented by a string internally, this config determines with claim is used to extract that string. It is desirable to use `sub` in here (or some other stable identifier), but in many cases you might need to use `preferred_username` (e.g. in case of Keycloak) or a different claim instead. + + Please note that some products hard-coded the claim in their implementation, so some product operators might error out if the product hardcodes a different claim than configured here. + + We don't provide any default value, as there is no correct way of doing it that works in all setups. Most demos will probably use `preferred_username`, although `sub` being more desirable, but technically impossible with the current behavior of the products. + type: string + providerHint: + description: This is a hint about which identity provider is used by the AuthenticationClass. Operators *can* opt to use this value to enable known quirks around OIDC / OAuth authentication. Not providing a hint means there is no hint and OIDC should be used as it is intended to be used (via the `.well-known` discovery). + enum: + - Keycloak + nullable: true + type: string + rootPath: + default: / + description: Root HTTP path of the identity provider. Defaults to `/`. + type: string + scopes: + description: Scopes to request from your identity provider. It is recommended to request the `openid`, `email`, and `profile` scopes. + items: + type: string + type: array + tls: + description: Use a TLS connection. If not specified no TLS will be used. + nullable: true + properties: + verification: + description: The verification method used to verify the certificates of the server and/or the client. + oneOf: + - required: + - none + - required: + - server + properties: + none: + description: Use TLS but don't verify certificates. + type: object + server: + description: Use TLS and a CA certificate to verify the server. + properties: + caCert: + description: CA cert to verify the server. + oneOf: + - required: + - webPki + - required: + - secretClass + properties: + secretClass: + description: Name of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) which will provide the CA certificate. Note that a SecretClass does not need to have a key but can also work with just a CA certificate, so if you got provided with a CA cert but don't have access to the key you can still use this method. + type: string + webPki: + description: Use TLS and the CA certificates trusted by the common web browsers to verify the server. This can be useful when you e.g. use public AWS S3 or other public available services. + type: object + type: object + required: + - caCert + type: object + type: object + required: + - verification + type: object + required: + - hostname + - principalClaim + - scopes + type: object + static: + description: The [static provider](https://https://docs.stackable.tech/home/nightly/concepts/authentication#_static) is used to configure a static set of users, identified by username and password. + properties: + userCredentialsSecret: + description: Secret providing the usernames and passwords. The Secret must contain an entry for every user, with the key being the username and the value the password in plain text. It must be located in the same namespace as the product using it. + properties: + name: + description: Name of the Secret. + type: string + required: + - name + type: object + required: + - userCredentialsSecret + type: object + tls: + description: The [TLS provider](https://docs.stackable.tech/home/nightly/concepts/authentication#_tls). The TLS AuthenticationClass is used when users should authenticate themselves with a TLS certificate. + properties: + clientCertSecretClass: + description: 'See [ADR017: TLS authentication](https://docs.stackable.tech/home/nightly/contributor/adr/adr017-tls_authentication). If `client_cert_secret_class` is not set, the TLS settings may also be used for client authentication. If `client_cert_secret_class` is set, the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) will be used to provision client certificates.' + nullable: true + type: string + type: object + type: object + required: + - provider + type: object + required: + - spec + title: AuthenticationClass + type: object + served: true + storage: true + subresources: {} diff --git a/generated-crd-previews/Listener.yaml b/generated-crd-previews/Listener.yaml new file mode 100644 index 000000000..8702af26b --- /dev/null +++ b/generated-crd-previews/Listener.yaml @@ -0,0 +1,123 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: listeners.listeners.stackable.tech +spec: + group: listeners.stackable.tech + names: + categories: [] + kind: Listener + plural: listeners + shortNames: [] + singular: listener + scope: Namespaced + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for ListenerSpec via `CustomResource` + properties: + spec: + description: |- + Exposes a set of pods to the outside world. + + Essentially a Stackable extension of a Kubernetes Service. Compared to a Service, a Listener changes three things: 1. It uses a cluster-level policy object (ListenerClass) to define how exactly the exposure works 2. It has a consistent API for reading back the exposed address(es) of the service 3. The Pod must mount a Volume referring to the Listener, which also allows ["sticky" scheduling](https://docs.stackable.tech/home/nightly/listener-operator/listener#_sticky_scheduling). + + Learn more in the [Listener documentation](https://docs.stackable.tech/home/nightly/listener-operator/listener). + properties: + className: + description: The name of the [ListenerClass](https://docs.stackable.tech/home/nightly/listener-operator/listenerclass). + nullable: true + type: string + extraPodSelectorLabels: + additionalProperties: + type: string + default: {} + description: Extra labels that the Pods must match in order to be exposed. They must _also_ still have a Volume referring to the Listener. + type: object + ports: + description: Ports that should be exposed. + items: + properties: + name: + description: |- + The name of the port. + + The name of each port *must* be unique within a single Listener. + type: string + port: + description: The port number. + format: int32 + type: integer + protocol: + description: The layer-4 protocol (`TCP` or `UDP`). + nullable: true + type: string + required: + - name + - port + type: object + nullable: true + type: array + publishNotReadyAddresses: + default: true + description: Whether incoming traffic should also be directed to Pods that are not `Ready`. + nullable: true + type: boolean + type: object + status: + description: Informs users about how to reach the Listener. + nullable: true + properties: + ingressAddresses: + description: All addresses that the Listener is currently reachable from. + items: + description: One address that a Listener is accessible from. + properties: + address: + description: The hostname or IP address to the Listener. + type: string + addressType: + description: The type of address (`Hostname` or `IP`). + enum: + - Hostname + - IP + type: string + ports: + additionalProperties: + format: int32 + type: integer + description: Port mapping table. + type: object + required: + - address + - addressType + - ports + type: object + nullable: true + type: array + nodePorts: + additionalProperties: + format: int32 + type: integer + description: |- + Port mappings for accessing the Listener on each Node that the Pods are currently running on. + + This is only intended for internal use by listener-operator itself. This will be left unset if using a ListenerClass that does not require Node-local access. + nullable: true + type: object + serviceName: + description: The backing Kubernetes Service. + nullable: true + type: string + type: object + required: + - spec + title: Listener + type: object + served: true + storage: true + subresources: + status: {} diff --git a/generated-crd-previews/ListenerClass.yaml b/generated-crd-previews/ListenerClass.yaml new file mode 100644 index 000000000..7082ba562 --- /dev/null +++ b/generated-crd-previews/ListenerClass.yaml @@ -0,0 +1,70 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: listenerclasses.listeners.stackable.tech +spec: + group: listeners.stackable.tech + names: + categories: [] + kind: ListenerClass + plural: listenerclasses + shortNames: [] + singular: listenerclass + scope: Cluster + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for ListenerClassSpec via `CustomResource` + properties: + spec: + description: Defines a policy for how [Listeners](https://docs.stackable.tech/home/nightly/listener-operator/listener) should be exposed. Read the [ListenerClass documentation](https://docs.stackable.tech/home/nightly/listener-operator/listenerclass) for more information. + properties: + preferredAddressType: + default: HostnameConservative + description: |- + Whether addresses should prefer using the IP address (`IP`) or the hostname (`Hostname`). Can also be set to `HostnameConservative`, which will use `IP` for `NodePort` service types, but `Hostname` for everything else. + + The other type will be used if the preferred type is not available. + + Defaults to `HostnameConservative`. + enum: + - Hostname + - IP + - HostnameConservative + type: string + serviceAnnotations: + additionalProperties: + type: string + default: {} + description: Annotations that should be added to the Service object. + type: object + serviceExternalTrafficPolicy: + default: Local + description: |- + `externalTrafficPolicy` that should be set on the created [`Service`] objects. + + The default is `Local` (in contrast to `Cluster`), as we aim to direct traffic to a node running the workload and we should keep testing that as the primary configuration. Cluster is a fallback option for providers that break Local mode (IONOS so far). + enum: + - Cluster + - Local + type: string + serviceType: + description: The method used to access the services. + enum: + - NodePort + - LoadBalancer + - ClusterIP + type: string + required: + - serviceType + type: object + required: + - spec + title: ListenerClass + type: object + served: true + storage: true + subresources: {} diff --git a/generated-crd-previews/PodListeners.yaml b/generated-crd-previews/PodListeners.yaml new file mode 100644 index 000000000..9907303a8 --- /dev/null +++ b/generated-crd-previews/PodListeners.yaml @@ -0,0 +1,86 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: podlisteners.listeners.stackable.tech +spec: + group: listeners.stackable.tech + names: + categories: [] + kind: PodListeners + plural: podlisteners + shortNames: [] + singular: podlisteners + scope: Namespaced + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for PodListenersSpec via `CustomResource` + properties: + spec: + description: |- + Informs users about Listeners that are bound by a given Pod. + + This is not expected to be created or modified by users. It will be created by the Stackable Listener Operator when mounting the listener volume, and is always named `pod-{pod.metadata.uid}`. + properties: + listeners: + additionalProperties: + properties: + ingressAddresses: + description: |- + Addresses allowing access to this Pod. + + Compared to `ingress_addresses` on the Listener status, this list is restricted to addresses that can access this Pod. + + This field is intended to be equivalent to the files mounted into the Listener volume. + items: + description: One address that a Listener is accessible from. + properties: + address: + description: The hostname or IP address to the Listener. + type: string + addressType: + description: The type of address (`Hostname` or `IP`). + enum: + - Hostname + - IP + type: string + ports: + additionalProperties: + format: int32 + type: integer + description: Port mapping table. + type: object + required: + - address + - addressType + - ports + type: object + nullable: true + type: array + scope: + description: '`Node` if this address only allows access to Pods hosted on a specific Kubernetes Node, otherwise `Cluster`.' + enum: + - Node + - Cluster + type: string + required: + - scope + type: object + description: |- + All Listeners currently bound by the Pod. + + Indexed by Volume name (not PersistentVolume or PersistentVolumeClaim). + type: object + required: + - listeners + type: object + required: + - spec + title: PodListeners + type: object + served: true + storage: true + subresources: {} diff --git a/generated-crd-previews/S3Bucket.yaml b/generated-crd-previews/S3Bucket.yaml new file mode 100644 index 000000000..d5a4091bd --- /dev/null +++ b/generated-crd-previews/S3Bucket.yaml @@ -0,0 +1,160 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: s3buckets.s3.stackable.tech +spec: + group: s3.stackable.tech + names: + categories: [] + kind: S3Bucket + plural: s3buckets + shortNames: [] + singular: s3bucket + scope: Namespaced + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for BucketSpec via `CustomResource` + properties: + spec: + description: S3 bucket specification containing the bucket name and an inlined or referenced connection specification. Learn more on the [S3 concept documentation](https://docs.stackable.tech/home/nightly/concepts/s3). + properties: + bucketName: + description: The name of the S3 bucket. + type: string + connection: + description: The definition of an S3 connection, either inline or as a reference. + oneOf: + - required: + - inline + - required: + - reference + properties: + inline: + description: S3 connection definition as a resource. Learn more on the [S3 concept documentation](https://docs.stackable.tech/home/nightly/concepts/s3). + properties: + accessStyle: + default: VirtualHosted + description: Which access style to use. Defaults to virtual hosted-style as most of the data products out there. Have a look at the [AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html). + enum: + - Path + - VirtualHosted + type: string + credentials: + description: If the S3 uses authentication you have to specify you S3 credentials. In the most cases a [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) providing `accessKey` and `secretKey` is sufficient. + nullable: true + properties: + scope: + description: '[Scope](https://docs.stackable.tech/home/nightly/secret-operator/scope) of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass).' + nullable: true + properties: + listenerVolumes: + default: [] + description: The listener volume scope allows Node and Service scopes to be inferred from the applicable listeners. This must correspond to Volume names in the Pod that mount Listeners. + items: + type: string + type: array + node: + default: false + description: The node scope is resolved to the name of the Kubernetes Node object that the Pod is running on. This will typically be the DNS name of the node. + type: boolean + pod: + default: false + description: The pod scope is resolved to the name of the Kubernetes Pod. This allows the secret to differentiate between StatefulSet replicas. + type: boolean + services: + default: [] + description: The service scope allows Pod objects to specify custom scopes. This should typically correspond to Service objects that the Pod participates in. + items: + type: string + type: array + type: object + secretClass: + description: '[SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) containing the LDAP bind credentials.' + type: string + required: + - secretClass + type: object + host: + description: 'Host of the S3 server without any protocol or port. For example: `west1.my-cloud.com`.' + type: string + port: + description: Port the S3 server listens on. If not specified the product will determine the port to use. + format: uint16 + minimum: 0.0 + nullable: true + type: integer + region: + default: + name: us-east-1 + description: |- + Bucket region used for signing headers (sigv4). + + This defaults to `us-east-1` which is compatible with other implementations such as Minio. + + WARNING: Some products use the Hadoop S3 implementation which falls back to us-east-2. + properties: + name: + default: us-east-1 + type: string + type: object + tls: + description: Use a TLS connection. If not specified no TLS will be used. + nullable: true + properties: + verification: + description: The verification method used to verify the certificates of the server and/or the client. + oneOf: + - required: + - none + - required: + - server + properties: + none: + description: Use TLS but don't verify certificates. + type: object + server: + description: Use TLS and a CA certificate to verify the server. + properties: + caCert: + description: CA cert to verify the server. + oneOf: + - required: + - webPki + - required: + - secretClass + properties: + secretClass: + description: Name of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) which will provide the CA certificate. Note that a SecretClass does not need to have a key but can also work with just a CA certificate, so if you got provided with a CA cert but don't have access to the key you can still use this method. + type: string + webPki: + description: Use TLS and the CA certificates trusted by the common web browsers to verify the server. This can be useful when you e.g. use public AWS S3 or other public available services. + type: object + type: object + required: + - caCert + type: object + type: object + required: + - verification + type: object + required: + - host + type: object + reference: + type: string + type: object + required: + - bucketName + - connection + type: object + required: + - spec + title: S3Bucket + type: object + served: true + storage: true + subresources: {} diff --git a/generated-crd-previews/S3Connection.yaml b/generated-crd-previews/S3Connection.yaml new file mode 100644 index 000000000..cb3d50de8 --- /dev/null +++ b/generated-crd-previews/S3Connection.yaml @@ -0,0 +1,139 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: s3connections.s3.stackable.tech +spec: + group: s3.stackable.tech + names: + categories: [] + kind: S3Connection + plural: s3connections + shortNames: [] + singular: s3connection + scope: Namespaced + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for ConnectionSpec via `CustomResource` + properties: + spec: + description: S3 connection definition as a resource. Learn more on the [S3 concept documentation](https://docs.stackable.tech/home/nightly/concepts/s3). + properties: + accessStyle: + default: VirtualHosted + description: Which access style to use. Defaults to virtual hosted-style as most of the data products out there. Have a look at the [AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html). + enum: + - Path + - VirtualHosted + type: string + credentials: + description: If the S3 uses authentication you have to specify you S3 credentials. In the most cases a [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) providing `accessKey` and `secretKey` is sufficient. + nullable: true + properties: + scope: + description: '[Scope](https://docs.stackable.tech/home/nightly/secret-operator/scope) of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass).' + nullable: true + properties: + listenerVolumes: + default: [] + description: The listener volume scope allows Node and Service scopes to be inferred from the applicable listeners. This must correspond to Volume names in the Pod that mount Listeners. + items: + type: string + type: array + node: + default: false + description: The node scope is resolved to the name of the Kubernetes Node object that the Pod is running on. This will typically be the DNS name of the node. + type: boolean + pod: + default: false + description: The pod scope is resolved to the name of the Kubernetes Pod. This allows the secret to differentiate between StatefulSet replicas. + type: boolean + services: + default: [] + description: The service scope allows Pod objects to specify custom scopes. This should typically correspond to Service objects that the Pod participates in. + items: + type: string + type: array + type: object + secretClass: + description: '[SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) containing the LDAP bind credentials.' + type: string + required: + - secretClass + type: object + host: + description: 'Host of the S3 server without any protocol or port. For example: `west1.my-cloud.com`.' + type: string + port: + description: Port the S3 server listens on. If not specified the product will determine the port to use. + format: uint16 + minimum: 0.0 + nullable: true + type: integer + region: + default: + name: us-east-1 + description: |- + Bucket region used for signing headers (sigv4). + + This defaults to `us-east-1` which is compatible with other implementations such as Minio. + + WARNING: Some products use the Hadoop S3 implementation which falls back to us-east-2. + properties: + name: + default: us-east-1 + type: string + type: object + tls: + description: Use a TLS connection. If not specified no TLS will be used. + nullable: true + properties: + verification: + description: The verification method used to verify the certificates of the server and/or the client. + oneOf: + - required: + - none + - required: + - server + properties: + none: + description: Use TLS but don't verify certificates. + type: object + server: + description: Use TLS and a CA certificate to verify the server. + properties: + caCert: + description: CA cert to verify the server. + oneOf: + - required: + - webPki + - required: + - secretClass + properties: + secretClass: + description: Name of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) which will provide the CA certificate. Note that a SecretClass does not need to have a key but can also work with just a CA certificate, so if you got provided with a CA cert but don't have access to the key you can still use this method. + type: string + webPki: + description: Use TLS and the CA certificates trusted by the common web browsers to verify the server. This can be useful when you e.g. use public AWS S3 or other public available services. + type: object + type: object + required: + - caCert + type: object + type: object + required: + - verification + type: object + required: + - host + type: object + required: + - spec + title: S3Connection + type: object + served: true + storage: true + subresources: {} From f1f01be3b6d6791c46aecbfc12124678fb7c2809 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 9 May 2025 11:00:20 +0200 Subject: [PATCH 02/11] Add DummyCluster --- Cargo.lock | 5 + crates/stackable-crd-previewer/Cargo.toml | 5 + crates/stackable-crd-previewer/build.rs | 82 +- generated-crd-previews/DummyCluster.yaml | 1128 +++++++++++++++++++++ 4 files changed, 1215 insertions(+), 5 deletions(-) create mode 100644 generated-crd-previews/DummyCluster.yaml diff --git a/Cargo.lock b/Cargo.lock index 8c88c993b..446ddd470 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3123,9 +3123,14 @@ dependencies = [ name = "stackable-crd-previewer" version = "0.1.0" dependencies = [ + "kube", + "schemars", + "serde", + "serde_json", "snafu 0.8.5", "stackable-operator", "stackable-shared", + "stackable-versioned", ] [[package]] diff --git a/crates/stackable-crd-previewer/Cargo.toml b/crates/stackable-crd-previewer/Cargo.toml index 667d3df5b..5d7fd439b 100644 --- a/crates/stackable-crd-previewer/Cargo.toml +++ b/crates/stackable-crd-previewer/Cargo.toml @@ -16,5 +16,10 @@ snafu.workspace = true [build-dependencies] stackable-shared = { path = "../stackable-shared" } stackable-operator = { path = "../stackable-operator" } +stackable-versioned = { path = "../stackable-versioned" } +kube.workspace = true +schemars.workspace = true +serde_json.workspace = true +serde.workspace = true snafu.workspace = true diff --git a/crates/stackable-crd-previewer/build.rs b/crates/stackable-crd-previewer/build.rs index 0ad294935..3ad579d70 100644 --- a/crates/stackable-crd-previewer/build.rs +++ b/crates/stackable-crd-previewer/build.rs @@ -3,14 +3,21 @@ use std::fs::create_dir_all; use snafu::{Report, ResultExt, Snafu}; use stackable_operator::{ YamlSchema, + commons::resources::{JvmHeapLimits, Resources}, + config::fragment::Fragment, crd::{ authentication::core::AuthenticationClass, listener::{Listener, ListenerClass, PodListeners}, s3::{S3Bucket, S3Connection}, }, - kube::core::crd::MergeError, + k8s_openapi::serde::{Deserialize, Serialize}, + kube::{CustomResource, core::crd::MergeError}, + role_utils::Role, + schemars::JsonSchema, shared::yaml::SerializeOptions, + status::condition::ClusterCondition, }; +use stackable_versioned::versioned; const OPERATOR_VERSION: &str = "0.0.0-dev"; const OUTPUT_DIR: &str = "../../generated-crd-previews"; @@ -29,6 +36,10 @@ pub enum Error { }, } +pub fn main() -> Report { + Report::capture(|| write_crds()) +} + macro_rules! write_crd { ($crd_name:ident, $stored_crd_version:ident) => { $crd_name::merged_crd($crd_name::$stored_crd_version) @@ -44,10 +55,6 @@ macro_rules! write_crd { }; } -pub fn main() -> Report { - Report::capture(|| write_crds()) -} - pub fn write_crds() -> Result<(), Error> { create_dir_all(OUTPUT_DIR).with_context(|_| CreateOutputDirSnafu { dir: OUTPUT_DIR.to_string(), @@ -71,5 +78,70 @@ pub fn write_crds() -> Result<(), Error> { write_crd!(S3Bucket, V1Alpha1); write_crd!(S3Connection, V1Alpha1); + // Also write a CRD with all sorts of common structs + write_crd!(DummyCluster, V1Alpha1); + Ok(()) } + +#[versioned(version(name = "v1alpha1"))] +pub mod versioned { + + #[derive(Clone, CustomResource, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] + #[versioned(k8s( + group = "zookeeper.stackable.tech", + kind = "DummyCluster", + status = "v1alpha1::DummyClusterStatus", + namespaced, + crates( + kube_core = "stackable_operator::kube::core", + k8s_openapi = "stackable_operator::k8s_openapi", + schemars = "stackable_operator::schemars" + ) + ))] + #[serde(rename_all = "camelCase")] + pub struct DummyClusterSpec { + pub nodes: Option>, + + stackable_affinity: stackable_operator::commons::affinity::StackableAffinity, + stackable_node_selector: stackable_operator::commons::affinity::StackableNodeSelector, + user_information_cache: stackable_operator::commons::cache::UserInformationCache, + cluster_operation: stackable_operator::commons::cluster_operation::ClusterOperation, + domain_name: stackable_operator::commons::networking::DomainName, + host_name: stackable_operator::commons::networking::HostName, + kerberos_realm_name: stackable_operator::commons::networking::KerberosRealmName, + opa_config: stackable_operator::commons::opa::OpaConfig, + pdb_config: stackable_operator::commons::pdb::PdbConfig, + product_image: stackable_operator::commons::product_image_selection::ProductImage, + secret_class_volume: stackable_operator::commons::secret_class::SecretClassVolume, + secret_reference: stackable_operator::commons::secret::SecretReference, + tls_client_details: stackable_operator::commons::tls_verification::TlsClientDetails, + + client_authentication_details: + stackable_operator::crd::authentication::core::v1alpha1::ClientAuthenticationDetails, + } + + #[derive(Debug, Default, PartialEq, Fragment, JsonSchema)] + #[fragment_attrs( + derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema), + serde(rename_all = "camelCase") + )] + pub struct ProductConfig { + resources: Resources, + } + + #[derive(Debug, Default, PartialEq, Fragment, JsonSchema)] + #[fragment_attrs( + derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema), + serde(rename_all = "camelCase") + )] + pub struct ProductStorageConfig { + data_storage: stackable_operator::commons::resources::PvcConfig, + } + + #[derive(Clone, Default, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] + #[serde(rename_all = "camelCase")] + pub struct DummyClusterStatus { + pub conditions: Vec, + } +} diff --git a/generated-crd-previews/DummyCluster.yaml b/generated-crd-previews/DummyCluster.yaml new file mode 100644 index 000000000..939c7a53b --- /dev/null +++ b/generated-crd-previews/DummyCluster.yaml @@ -0,0 +1,1128 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: dummyclusters.zookeeper.stackable.tech +spec: + group: zookeeper.stackable.tech + names: + categories: [] + kind: DummyCluster + plural: dummyclusters + shortNames: [] + singular: dummycluster + scope: Namespaced + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for DummyClusterSpec via `CustomResource` + properties: + spec: + properties: + clientAuthenticationDetails: + properties: + authenticationClass: + description: |- + Name of the [AuthenticationClass](https://docs.stackable.tech/home/nightly/concepts/authentication) used to authenticate users. + + To get the concrete [`AuthenticationClass`], we must resolve it. This resolution can be achieved by using [`ClientAuthenticationDetails::resolve_class`]. + type: string + oidc: + description: |- + This field contains OIDC-specific configuration. It is only required in case OIDC is used. + + Use [`ClientAuthenticationDetails::oidc_or_error`] to get the value or report an error to the user. + nullable: true + properties: + clientCredentialsSecret: + description: A reference to the OIDC client credentials secret. The secret contains the client id and secret. + type: string + extraScopes: + default: [] + description: |- + An optional list of extra scopes which get merged with the scopes defined in the [`AuthenticationClass`][1]. + + [1]: crate::crd::authentication::core::v1alpha1::AuthenticationClass + items: + type: string + type: array + required: + - clientCredentialsSecret + type: object + required: + - authenticationClass + type: object + clusterOperation: + description: '[Cluster operations](https://docs.stackable.tech/home/nightly/concepts/operations/cluster_operations) properties, allow stopping the product instance as well as pausing reconciliation.' + properties: + reconciliationPaused: + default: false + description: Flag to stop cluster reconciliation by the operator. This means that all changes in the custom resource spec are ignored until this flag is set to false or removed. The operator will however still watch the deployed resources at the time and update the custom resource status field. If applied at the same time with `stopped`, `reconciliationPaused` will take precedence over `stopped` and stop the reconciliation immediately. + type: boolean + stopped: + default: false + description: Flag to stop the cluster. This means all deployed resources (e.g. Services, StatefulSets, ConfigMaps) are kept but all deployed Pods (e.g. replicas from a StatefulSet) are scaled to 0 and therefore stopped and removed. If applied at the same time with `reconciliationPaused`, the latter will pause reconciliation and `stopped` will take no effect until `reconciliationPaused` is set to false or removed. + type: boolean + type: object + domainName: + description: A validated domain name type conforming to RFC 1123, so e.g. not an IP addresses + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\.?$ + type: string + hostName: + type: string + kerberosRealmName: + description: A validated kerberos realm name type, for use in CRDs. + pattern: ^[-.a-zA-Z0-9]+$ + type: string + nodes: + description: This struct represents a role - e.g. HDFS datanodes or Trino workers. It has a key-value-map containing all the roleGroups that are part of this role. Additionally, there is a `config`, which is configurable at the role *and* roleGroup level. Everything at roleGroup level is merged on top of what is configured on role level. There is also a second form of config, which can only be configured at role level, the `roleConfig`. You can learn more about this in the [Roles and role group concept documentation](https://docs.stackable.tech/home/nightly/concepts/roles-and-role-groups). + nullable: true + properties: + cliOverrides: + additionalProperties: + type: string + default: {} + type: object + config: + default: {} + properties: + resources: + description: Resource usage is configured here, this includes CPU usage, memory usage and disk storage usage, if this role needs any. + properties: + cpu: + default: + max: null + min: null + properties: + max: + description: The maximum amount of CPU cores that can be requested by Pods. Equivalent to the `limit` for Pod resource configuration. Cores are specified either as a decimal point number or as milli units. For example:`1.5` will be 1.5 cores, also written as `1500m`. + nullable: true + type: string + min: + description: The minimal amount of CPU cores that Pods need to run. Equivalent to the `request` for Pod resource configuration. Cores are specified either as a decimal point number or as milli units. For example:`1.5` will be 1.5 cores, also written as `1500m`. + nullable: true + type: string + type: object + memory: + properties: + limit: + description: 'The maximum amount of memory that should be available to the Pod. Specified as a byte [Quantity](https://kubernetes.io/docs/reference/kubernetes-api/common-definitions/quantity/), which means these suffixes are supported: E, P, T, G, M, k. You can also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki. For example, the following represent roughly the same value: `128974848, 129e6, 129M, 128974848000m, 123Mi`' + nullable: true + type: string + runtimeLimits: + description: Additional options that can be specified. + properties: + max: + description: "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation." + nullable: true + type: string + min: + description: "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation." + nullable: true + type: string + type: object + type: object + storage: + properties: + dataStorage: + properties: + capacity: + description: "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation." + nullable: true + type: string + selectors: + description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects. + nullable: true + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + storageClass: + nullable: true + type: string + type: object + required: + - dataStorage + type: object + type: object + required: + - resources + type: object + configOverrides: + additionalProperties: + additionalProperties: + type: string + type: object + default: {} + description: The `configOverrides` can be used to configure properties in product config files that are not exposed in the CRD. Read the [config overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#config-overrides) and consult the operator specific usage guide documentation for details on the available config files and settings for the specific product. + type: object + envOverrides: + additionalProperties: + type: string + default: {} + description: '`envOverrides` configure environment variables to be set in the Pods. It is a map from strings to strings - environment variables and the value to set. Read the [environment variable overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#env-overrides) for more information and consult the operator specific usage guide to find out about the product specific environment variables that are available.' + type: object + podOverrides: + default: {} + description: In the `podOverrides` property you can define a [PodTemplateSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#podtemplatespec-v1-core) to override any property that can be set on a Kubernetes Pod. Read the [Pod overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#pod-overrides) for more information. + type: object + x-kubernetes-preserve-unknown-fields: true + roleConfig: + default: + podDisruptionBudget: + enabled: true + maxUnavailable: null + description: This is a product-agnostic RoleConfig, which is sufficient for most of the products. + properties: + podDisruptionBudget: + default: + enabled: true + maxUnavailable: null + description: |- + This struct is used to configure: + + 1. If PodDisruptionBudgets are created by the operator 2. The allowed number of Pods to be unavailable (`maxUnavailable`) + + Learn more in the [allowed Pod disruptions documentation](https://docs.stackable.tech/home/nightly/concepts/operations/pod_disruptions). + properties: + enabled: + default: true + description: Whether a PodDisruptionBudget should be written out for this role. Disabling this enables you to specify your own - custom - one. Defaults to true. + type: boolean + maxUnavailable: + description: The number of Pods that are allowed to be down because of voluntary disruptions. If you don't explicitly set this, the operator will use a sane default based upon knowledge about the individual product. + format: uint16 + minimum: 0.0 + nullable: true + type: integer + type: object + type: object + roleGroups: + additionalProperties: + properties: + cliOverrides: + additionalProperties: + type: string + default: {} + type: object + config: + default: {} + properties: + resources: + description: Resource usage is configured here, this includes CPU usage, memory usage and disk storage usage, if this role needs any. + properties: + cpu: + default: + max: null + min: null + properties: + max: + description: The maximum amount of CPU cores that can be requested by Pods. Equivalent to the `limit` for Pod resource configuration. Cores are specified either as a decimal point number or as milli units. For example:`1.5` will be 1.5 cores, also written as `1500m`. + nullable: true + type: string + min: + description: The minimal amount of CPU cores that Pods need to run. Equivalent to the `request` for Pod resource configuration. Cores are specified either as a decimal point number or as milli units. For example:`1.5` will be 1.5 cores, also written as `1500m`. + nullable: true + type: string + type: object + memory: + properties: + limit: + description: 'The maximum amount of memory that should be available to the Pod. Specified as a byte [Quantity](https://kubernetes.io/docs/reference/kubernetes-api/common-definitions/quantity/), which means these suffixes are supported: E, P, T, G, M, k. You can also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki. For example, the following represent roughly the same value: `128974848, 129e6, 129M, 128974848000m, 123Mi`' + nullable: true + type: string + runtimeLimits: + description: Additional options that can be specified. + properties: + max: + description: "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation." + nullable: true + type: string + min: + description: "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation." + nullable: true + type: string + type: object + type: object + storage: + properties: + dataStorage: + properties: + capacity: + description: "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation." + nullable: true + type: string + selectors: + description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects. + nullable: true + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + storageClass: + nullable: true + type: string + type: object + required: + - dataStorage + type: object + type: object + required: + - resources + type: object + configOverrides: + additionalProperties: + additionalProperties: + type: string + type: object + default: {} + description: The `configOverrides` can be used to configure properties in product config files that are not exposed in the CRD. Read the [config overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#config-overrides) and consult the operator specific usage guide documentation for details on the available config files and settings for the specific product. + type: object + envOverrides: + additionalProperties: + type: string + default: {} + description: '`envOverrides` configure environment variables to be set in the Pods. It is a map from strings to strings - environment variables and the value to set. Read the [environment variable overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#env-overrides) for more information and consult the operator specific usage guide to find out about the product specific environment variables that are available.' + type: object + podOverrides: + default: {} + description: In the `podOverrides` property you can define a [PodTemplateSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#podtemplatespec-v1-core) to override any property that can be set on a Kubernetes Pod. Read the [Pod overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#pod-overrides) for more information. + type: object + x-kubernetes-preserve-unknown-fields: true + replicas: + format: uint16 + minimum: 0.0 + nullable: true + type: integer + type: object + type: object + required: + - roleGroups + type: object + opaConfig: + description: Configure the OPA stacklet [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) and the name of the Rego package containing your authorization rules. Consult the [OPA authorization documentation](https://docs.stackable.tech/home/nightly/concepts/opa) to learn how to deploy Rego authorization rules with OPA. + properties: + configMapName: + description: The [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) for the OPA stacklet that should be used for authorization requests. + type: string + package: + description: The name of the Rego package containing the Rego rules for the product. + nullable: true + type: string + required: + - configMapName + type: object + pdbConfig: + description: |- + This struct is used to configure: + + 1. If PodDisruptionBudgets are created by the operator 2. The allowed number of Pods to be unavailable (`maxUnavailable`) + + Learn more in the [allowed Pod disruptions documentation](https://docs.stackable.tech/home/nightly/concepts/operations/pod_disruptions). + properties: + enabled: + default: true + description: Whether a PodDisruptionBudget should be written out for this role. Disabling this enables you to specify your own - custom - one. Defaults to true. + type: boolean + maxUnavailable: + description: The number of Pods that are allowed to be down because of voluntary disruptions. If you don't explicitly set this, the operator will use a sane default based upon knowledge about the individual product. + format: uint16 + minimum: 0.0 + nullable: true + type: integer + type: object + productImage: + anyOf: + - required: + - custom + - productVersion + - required: + - productVersion + description: |- + Specify which image to use, the easiest way is to only configure the `productVersion`. You can also configure a custom image registry to pull from, as well as completely custom images. + + Consult the [Product image selection documentation](https://docs.stackable.tech/home/nightly/concepts/product_image_selection) for details. + properties: + custom: + description: Overwrite the docker image. Specify the full docker image name, e.g. `oci.stackable.tech/sdp/superset:1.4.1-stackable2.1.0` + type: string + productVersion: + description: Version of the product, e.g. `1.4.1`. + type: string + pullPolicy: + default: Always + description: '[Pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) used when pulling the image.' + enum: + - IfNotPresent + - Always + - Never + type: string + pullSecrets: + description: '[Image pull secrets](https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod) to pull images from a private registry.' + items: + description: LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace. + properties: + name: + description: 'Name of the referent. This field is effectively required, but due to backwards compatibility is allowed to be empty. Instances of this type with an empty value here are almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + required: + - name + type: object + nullable: true + type: array + repo: + description: Name of the docker repo, e.g. `oci.stackable.tech/sdp` + nullable: true + type: string + stackableVersion: + description: Stackable version of the product, e.g. `23.4`, `23.4.1` or `0.0.0-dev`. If not specified, the operator will use its own version, e.g. `23.4.1`. When using a nightly operator or a pr version, it will use the nightly `0.0.0-dev` image. + nullable: true + type: string + type: object + secretClassVolume: + properties: + scope: + description: '[Scope](https://docs.stackable.tech/home/nightly/secret-operator/scope) of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass).' + nullable: true + properties: + listenerVolumes: + default: [] + description: The listener volume scope allows Node and Service scopes to be inferred from the applicable listeners. This must correspond to Volume names in the Pod that mount Listeners. + items: + type: string + type: array + node: + default: false + description: The node scope is resolved to the name of the Kubernetes Node object that the Pod is running on. This will typically be the DNS name of the node. + type: boolean + pod: + default: false + description: The pod scope is resolved to the name of the Kubernetes Pod. This allows the secret to differentiate between StatefulSet replicas. + type: boolean + services: + default: [] + description: The service scope allows Pod objects to specify custom scopes. This should typically correspond to Service objects that the Pod participates in. + items: + type: string + type: array + type: object + secretClass: + description: '[SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) containing the LDAP bind credentials.' + type: string + required: + - secretClass + type: object + secretReference: + description: |- + [`SecretReference`] represents a Kubernetes [`Secret`] reference. + + In order to use this struct, the following two requirements must be met: + + - Must only be used in cluster-scoped objects - Namespaced objects must not be able to define cross-namespace secret references + + This struct is a redefinition of the one provided by k8s-openapi to make name and namespace mandatory. + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + stackableAffinity: + description: These configuration settings control [Pod placement](https://docs.stackable.tech/home/nightly/concepts/operations/pod_placement). + properties: + node_affinity: + description: Same as the `spec.affinity.nodeAffinity` field on the Pod, see the [Kubernetes docs](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node) + nullable: true + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + node_selector: + additionalProperties: + type: string + description: Simple key-value pairs forming a nodeSelector, see the [Kubernetes docs](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node) + nullable: true + type: object + pod_affinity: + description: Same as the `spec.affinity.podAffinity` field on the Pod, see the [Kubernetes docs](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node) + nullable: true + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + matchLabelKeys: + description: MatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + mismatchLabelKeys: + description: MismatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + matchLabelKeys: + description: MatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + mismatchLabelKeys: + description: MismatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + pod_anti_affinity: + description: Same as the `spec.affinity.podAntiAffinity` field on the Pod, see the [Kubernetes docs](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node) + nullable: true + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + matchLabelKeys: + description: MatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + mismatchLabelKeys: + description: MismatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + matchLabelKeys: + description: MatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + mismatchLabelKeys: + description: MismatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + stackableNodeSelector: + additionalProperties: + type: string + description: Simple key-value pairs forming a nodeSelector, see the [Kubernetes docs](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node) + type: object + tlsClientDetails: + properties: + tls: + description: Use a TLS connection. If not specified no TLS will be used. + nullable: true + properties: + verification: + description: The verification method used to verify the certificates of the server and/or the client. + oneOf: + - required: + - none + - required: + - server + properties: + none: + description: Use TLS but don't verify certificates. + type: object + server: + description: Use TLS and a CA certificate to verify the server. + properties: + caCert: + description: CA cert to verify the server. + oneOf: + - required: + - webPki + - required: + - secretClass + properties: + secretClass: + description: Name of the [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) which will provide the CA certificate. Note that a SecretClass does not need to have a key but can also work with just a CA certificate, so if you got provided with a CA cert but don't have access to the key you can still use this method. + type: string + webPki: + description: Use TLS and the CA certificates trusted by the common web browsers to verify the server. This can be useful when you e.g. use public AWS S3 or other public available services. + type: object + type: object + required: + - caCert + type: object + type: object + required: + - verification + type: object + type: object + userInformationCache: + description: Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value. + properties: + entryTimeToLive: + default: 30s + description: Time to live per entry + type: string + maxEntries: + default: 10000 + description: Maximum number of entries in the cache; If this threshold is reached then the least recently used item is removed. + format: uint32 + minimum: 0.0 + type: integer + type: object + required: + - clientAuthenticationDetails + - clusterOperation + - domainName + - hostName + - kerberosRealmName + - opaConfig + - pdbConfig + - productImage + - secretClassVolume + - secretReference + - stackableAffinity + - stackableNodeSelector + - tlsClientDetails + - userInformationCache + type: object + status: + nullable: true + properties: + conditions: + items: + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + format: date-time + nullable: true + type: string + lastUpdateTime: + description: The last time this condition was updated. + format: date-time + nullable: true + type: string + message: + description: A human readable message indicating details about the transition. + nullable: true + type: string + reason: + description: The reason for the condition's last transition. + nullable: true + type: string + status: + description: Status of the condition, one of True, False, Unknown. + enum: + - 'True' + - 'False' + - Unknown + type: string + type: + description: Type of deployment condition. + enum: + - Available + - Degraded + - Progressing + - ReconciliationPaused + - Stopped + type: string + required: + - status + - type + type: object + type: array + required: + - conditions + type: object + required: + - spec + title: DummyCluster + type: object + served: true + storage: true + subresources: + status: {} From 44b53988ec2c97d255eada81b46f0240efef8813 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 9 May 2025 11:02:38 +0200 Subject: [PATCH 03/11] docs --- crates/stackable-crd-previewer/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/stackable-crd-previewer/src/lib.rs b/crates/stackable-crd-previewer/src/lib.rs index 8b1378917..8f4de2ba0 100644 --- a/crates/stackable-crd-previewer/src/lib.rs +++ b/crates/stackable-crd-previewer/src/lib.rs @@ -1 +1,4 @@ - +// We need *something* for this to count as workspace member. +// +// This needs to be a separate workspace member, as can not add a build.rs to stackable-operator, +// that depends on stackable-operator structs. From bf817d2f8b179db96a496fad678bc6178383da7c Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 9 May 2025 11:05:18 +0200 Subject: [PATCH 04/11] Add comment --- crates/stackable-crd-previewer/build.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/stackable-crd-previewer/build.rs b/crates/stackable-crd-previewer/build.rs index 3ad579d70..5c1b55541 100644 --- a/crates/stackable-crd-previewer/build.rs +++ b/crates/stackable-crd-previewer/build.rs @@ -103,6 +103,7 @@ pub mod versioned { pub struct DummyClusterSpec { pub nodes: Option>, + // Not versioned yet stackable_affinity: stackable_operator::commons::affinity::StackableAffinity, stackable_node_selector: stackable_operator::commons::affinity::StackableNodeSelector, user_information_cache: stackable_operator::commons::cache::UserInformationCache, @@ -117,6 +118,7 @@ pub mod versioned { secret_reference: stackable_operator::commons::secret::SecretReference, tls_client_details: stackable_operator::commons::tls_verification::TlsClientDetails, + // Already versioned client_authentication_details: stackable_operator::crd::authentication::core::v1alpha1::ClientAuthenticationDetails, } From 991903cc8fe4819f9fe481fef27db7295a361a74 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 9 May 2025 13:12:15 +0200 Subject: [PATCH 05/11] Fixup DummyServer --- crates/stackable-crd-previewer/build.rs | 3 ++- generated-crd-previews/DummyCluster.yaml | 26 ++++++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/crates/stackable-crd-previewer/build.rs b/crates/stackable-crd-previewer/build.rs index 5c1b55541..d19f9359f 100644 --- a/crates/stackable-crd-previewer/build.rs +++ b/crates/stackable-crd-previewer/build.rs @@ -101,7 +101,7 @@ pub mod versioned { ))] #[serde(rename_all = "camelCase")] pub struct DummyClusterSpec { - pub nodes: Option>, + nodes: Option>, // Not versioned yet stackable_affinity: stackable_operator::commons::affinity::StackableAffinity, @@ -129,6 +129,7 @@ pub mod versioned { serde(rename_all = "camelCase") )] pub struct ProductConfig { + #[fragment_attrs(serde(default))] resources: Resources, } diff --git a/generated-crd-previews/DummyCluster.yaml b/generated-crd-previews/DummyCluster.yaml index 939c7a53b..52f0655e4 100644 --- a/generated-crd-previews/DummyCluster.yaml +++ b/generated-crd-previews/DummyCluster.yaml @@ -89,6 +89,17 @@ spec: default: {} properties: resources: + default: + cpu: + max: null + min: null + memory: + limit: null + runtimeLimits: + max: null + storage: + dataStorage: + capacity: null description: Resource usage is configured here, this includes CPU usage, memory usage and disk storage usage, if this role needs any. properties: cpu: @@ -171,8 +182,6 @@ spec: - dataStorage type: object type: object - required: - - resources type: object configOverrides: additionalProperties: @@ -235,6 +244,17 @@ spec: default: {} properties: resources: + default: + cpu: + max: null + min: null + memory: + limit: null + runtimeLimits: + max: null + storage: + dataStorage: + capacity: null description: Resource usage is configured here, this includes CPU usage, memory usage and disk storage usage, if this role needs any. properties: cpu: @@ -317,8 +337,6 @@ spec: - dataStorage type: object type: object - required: - - resources type: object configOverrides: additionalProperties: From d70edacd52d6b014ad8b9157a38db92e2560f570 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 9 May 2025 13:12:38 +0200 Subject: [PATCH 06/11] Add README --- crates/stackable-crd-previewer/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 crates/stackable-crd-previewer/README.md diff --git a/crates/stackable-crd-previewer/README.md b/crates/stackable-crd-previewer/README.md new file mode 100644 index 000000000..4d334e4d0 --- /dev/null +++ b/crates/stackable-crd-previewer/README.md @@ -0,0 +1,18 @@ +# stackable-crd-previewer + +The purpose of this crate is to preview the effects of code changes on our CRDs. + +Use the following command to re-generate the CRDs: + +```bash +cargo check -p stackable-crd-previewer +``` + +This should implicitly happen by `rust-analyzer` or `cargo check`, so shouldn't be needed to invoke +explicitly normally. + +With an existing Kubernetes context you can run the following command to check if the CRDs are valid: + +```bash +kubectl apply -f generated-crd-previews/ --dry-run=server +``` From 8ccf051dcb11378733862f043432698963b782e2 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 9 May 2025 14:35:35 +0200 Subject: [PATCH 07/11] Add CI check --- .github/workflows/build.yml | 21 +++++++++++++++++++++ crates/stackable-crd-previewer/build.rs | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9202da9fe..6cdafe83e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -102,6 +102,27 @@ jobs: - run: cargo test --no-default-features --workspace - run: cargo test --all-features --workspace + check_crd_previews: + name: Check if CRD previews are up to date + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} + - name: Regenerate CRD previews + run: cargo check -p stackable-crd-previewer + - name: Check if committed CRD previews were up to date + run: git diff --exit-code generated-crd-previews + - name: Git Diff showed uncommitted changes + if: ${{ failure() }} + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + core.setFailed('Committed CRD previews were not up to date, please run "cargo check -p stackable-crd-previewer" and re-commit!') + tests_passed: name: All tests passed needs: diff --git a/crates/stackable-crd-previewer/build.rs b/crates/stackable-crd-previewer/build.rs index d19f9359f..cb25d52ac 100644 --- a/crates/stackable-crd-previewer/build.rs +++ b/crates/stackable-crd-previewer/build.rs @@ -37,7 +37,7 @@ pub enum Error { } pub fn main() -> Report { - Report::capture(|| write_crds()) + Report::capture(write_crds) } macro_rules! write_crd { From 9ba7ca51ec698e0dbcbb6eae4426d1b0f27ea745 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 9 May 2025 14:36:05 +0200 Subject: [PATCH 08/11] Fix CI --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6cdafe83e..35b7deeb6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -128,6 +128,7 @@ jobs: needs: - run_udeps - run_tests + - check_crd_previews runs-on: ubuntu-latest steps: - name: log From 74d782a16d236e11c2a420a429a861f0f1d0dea7 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 9 May 2025 14:52:30 +0200 Subject: [PATCH 09/11] Add changelog --- crates/stackable-crd-previewer/CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 crates/stackable-crd-previewer/CHANGELOG.md diff --git a/crates/stackable-crd-previewer/CHANGELOG.md b/crates/stackable-crd-previewer/CHANGELOG.md new file mode 100644 index 000000000..060d100a5 --- /dev/null +++ b/crates/stackable-crd-previewer/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [Unreleased] + +### Added + +- Initial commit ([#1030]). + +[#1030]: https://github.com/stackabletech/operator-rs/pull/1030 From ba2172aacf45b212cecbbb742921d2fd07179d33 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 9 May 2025 15:00:05 +0200 Subject: [PATCH 10/11] Fix versions script --- .scripts/verify_crate_versions.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.scripts/verify_crate_versions.sh b/.scripts/verify_crate_versions.sh index d0ca5399d..e9383df5d 100755 --- a/.scripts/verify_crate_versions.sh +++ b/.scripts/verify_crate_versions.sh @@ -40,7 +40,8 @@ for CRATE in $(find ./crates/ -mindepth 2 -name Cargo.toml -print0 | xargs -0 -n echo "Ensure the version in ./crates/$CRATE/Cargo.toml matches the version in ./crates/$ASSOCIATED_CRATE/Cargo.toml" >&2 exit 23 ) - + elif [ "$CRATE" = "stackable-crd-previewer" ]; then + : # Skip stackable-crd-previewer crate for now, it's not a "real"/user-facing crate else # Get the latest documented version from the CHANGELOG.md CHANGELOG_VERSION=$(grep -oE '\[[0-9]+\.[0-9]+\.[0-9]+\]' "./crates/$CRATE/CHANGELOG.md" | head -1 | tr -d '[]') From c80c82f2aa245fb9eac82a4f6c28bc83e02057ab Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Mon, 12 May 2025 11:50:02 +0200 Subject: [PATCH 11/11] fix: Dont leak rustdoc in CRD descriptions --- .../src/crd/authentication/core/mod.rs | 3 +++ .../src/crd/authentication/oidc/mod.rs | 3 +++ generated-crd-previews/DummyCluster.yaml | 10 ++-------- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/stackable-operator/src/crd/authentication/core/mod.rs b/crates/stackable-operator/src/crd/authentication/core/mod.rs index 8c79f6128..93c31ec71 100644 --- a/crates/stackable-operator/src/crd/authentication/core/mod.rs +++ b/crates/stackable-operator/src/crd/authentication/core/mod.rs @@ -145,6 +145,9 @@ pub mod versioned { // added, so that user can not configure multiple options at the same time (yes we are aware // that this makes a changing the type of an AuthenticationClass harder). This is a // non-breaking change though :) + #[schemars( + description = "This field contains OIDC-specific configuration. It is only required in case OIDC is used." + )] oidc: Option>, } } diff --git a/crates/stackable-operator/src/crd/authentication/oidc/mod.rs b/crates/stackable-operator/src/crd/authentication/oidc/mod.rs index 7f3fe99a9..dd7aa35aa 100644 --- a/crates/stackable-operator/src/crd/authentication/oidc/mod.rs +++ b/crates/stackable-operator/src/crd/authentication/oidc/mod.rs @@ -105,6 +105,9 @@ pub mod versioned { /// defined in the [`AuthenticationClass`][1]. /// /// [1]: crate::crd::authentication::core::v1alpha1::AuthenticationClass + #[schemars( + description = "An optional list of extra scopes which get merged with the scopes defined in the AuthenticationClass" + )] #[serde(default)] pub extra_scopes: Vec, diff --git a/generated-crd-previews/DummyCluster.yaml b/generated-crd-previews/DummyCluster.yaml index 52f0655e4..989abdfae 100644 --- a/generated-crd-previews/DummyCluster.yaml +++ b/generated-crd-previews/DummyCluster.yaml @@ -30,10 +30,7 @@ spec: To get the concrete [`AuthenticationClass`], we must resolve it. This resolution can be achieved by using [`ClientAuthenticationDetails::resolve_class`]. type: string oidc: - description: |- - This field contains OIDC-specific configuration. It is only required in case OIDC is used. - - Use [`ClientAuthenticationDetails::oidc_or_error`] to get the value or report an error to the user. + description: This field contains OIDC-specific configuration. It is only required in case OIDC is used. nullable: true properties: clientCredentialsSecret: @@ -41,10 +38,7 @@ spec: type: string extraScopes: default: [] - description: |- - An optional list of extra scopes which get merged with the scopes defined in the [`AuthenticationClass`][1]. - - [1]: crate::crd::authentication::core::v1alpha1::AuthenticationClass + description: An optional list of extra scopes which get merged with the scopes defined in the AuthenticationClass items: type: string type: array