diff --git a/CHANGELOG.md b/CHANGELOG.md index 49fca394..924e8e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ All notable changes to this project will be documented in this file. ### Changed -- BREAKING: `ClusterRef` namespace now optional ([#95]). +- BREAKING: CRD changes. The `spec.opa` and `spec.hive` renamed to +`spec.opaConfigMapName` and `spec.hiveConfigMapName` +which only accept a String ([#131]). - BREAKING: In case the namespace is omitted, the operator defaults to the `TrinoCluster` namespace instead of `default` ([#95]). - User authentication now provided via secret instead of custom resource ([#81]). - User authentication not exposed in configmap anymore ([#81]). @@ -18,10 +20,12 @@ All notable changes to this project will be documented in this file. - The Trino version is now a string instead of enum ([#81]). - `operator-rs` `0.4.0` → `0.10.0` ([#81], [#95], [#118]). - `stackable-regorule-crd` `0.2.0` → `0.6.0` ([#81], [#118]). +- Improvements to setting up (easy) insecure clusters ([#131]) [#81]: https://github.com/stackabletech/trino-operator/pull/81 [#95]: https://github.com/stackabletech/trino-operator/pull/95 [#118]: https://github.com/stackabletech/trino-operator/pull/118 +[#131]: https://github.com/stackabletech/trino-operator/pull/131 ## [0.2.0] - 2021-12-06 diff --git a/deploy/config-spec/properties.yaml b/deploy/config-spec/properties.yaml index 43e5e607..8450b4ac 100644 --- a/deploy/config-spec/properties.yaml +++ b/deploy/config-spec/properties.yaml @@ -115,7 +115,7 @@ properties: value: "8443" roles: - name: "coordinator" - required: true + required: false asOfVersion: "0.0.0" - property: &queryMaxMemory diff --git a/deploy/crd/trinocluster.crd.yaml b/deploy/crd/trinocluster.crd.yaml index 2c1e91be..3f887d36 100644 --- a/deploy/crd/trinocluster.crd.yaml +++ b/deploy/crd/trinocluster.crd.yaml @@ -199,36 +199,14 @@ spec: required: - roleGroups type: object - hive: + hiveConfigMapName: nullable: true - properties: - chroot: - nullable: true - type: string - name: - type: string - namespace: - nullable: true - type: string - required: - - name - type: object + type: string nodeEnvironment: type: string - opa: + opaConfigMapName: nullable: true - properties: - chroot: - nullable: true - type: string - name: - type: string - namespace: - nullable: true - type: string - required: - - name - type: object + type: string s3: description: Contains all the required connection information for S3. nullable: true diff --git a/deploy/helm/trino-operator/configs/properties.yaml b/deploy/helm/trino-operator/configs/properties.yaml index 43e5e607..8450b4ac 100644 --- a/deploy/helm/trino-operator/configs/properties.yaml +++ b/deploy/helm/trino-operator/configs/properties.yaml @@ -115,7 +115,7 @@ properties: value: "8443" roles: - name: "coordinator" - required: true + required: false asOfVersion: "0.0.0" - property: &queryMaxMemory diff --git a/deploy/helm/trino-operator/crds/crds.yaml b/deploy/helm/trino-operator/crds/crds.yaml index 8e613350..dc18ac2c 100644 --- a/deploy/helm/trino-operator/crds/crds.yaml +++ b/deploy/helm/trino-operator/crds/crds.yaml @@ -201,36 +201,14 @@ spec: required: - roleGroups type: object - hive: + hiveConfigMapName: nullable: true - properties: - chroot: - nullable: true - type: string - name: - type: string - namespace: - nullable: true - type: string - required: - - name - type: object + type: string nodeEnvironment: type: string - opa: + opaConfigMapName: nullable: true - properties: - chroot: - nullable: true - type: string - name: - type: string - namespace: - nullable: true - type: string - required: - - name - type: object + type: string s3: description: Contains all the required connection information for S3. nullable: true diff --git a/deploy/manifests/configmap.yaml b/deploy/manifests/configmap.yaml index e1ad0903..e5212f96 100644 --- a/deploy/manifests/configmap.yaml +++ b/deploy/manifests/configmap.yaml @@ -39,7 +39,7 @@ data: \ file: \"config.properties\"\n datatype:\n type: \"integer\"\n \ min: \"1024\"\n max: \"65535\"\n defaultValues:\n - fromVersion: \"0.0.0\"\n value: \"8443\"\n roles:\n - name: \"coordinator\"\n - \ required: true\n asOfVersion: \"0.0.0\"\n\n - property: &queryMaxMemory\n + \ required: false\n asOfVersion: \"0.0.0\"\n\n - property: &queryMaxMemory\n \ propertyNames:\n - name: \"query.max-memory\"\n kind:\n type: \"file\"\n file: \"config.properties\"\n datatype:\n type: \"string\"\n unit: *unitMemory\n defaultValues:\n - fromVersion: diff --git a/deploy/manifests/crds.yaml b/deploy/manifests/crds.yaml index 53e85339..a7cfa456 100644 --- a/deploy/manifests/crds.yaml +++ b/deploy/manifests/crds.yaml @@ -203,36 +203,14 @@ spec: required: - roleGroups type: object - hive: + hiveConfigMapName: nullable: true - properties: - chroot: - nullable: true - type: string - name: - type: string - namespace: - nullable: true - type: string - required: - - name - type: object + type: string nodeEnvironment: type: string - opa: + opaConfigMapName: nullable: true - properties: - chroot: - nullable: true - type: string - name: - type: string - namespace: - nullable: true - type: string - required: - - name - type: object + type: string s3: description: Contains all the required connection information for S3. nullable: true diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index f02b99a9..8afe6b63 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -10,6 +10,8 @@ The Stackable Operator for Trino currently supports the following versions of Tr include::partial$supported-versions.adoc[] +== Get Docker image + [source] ---- docker pull docker.stackable.tech/stackable/trino: diff --git a/docs/modules/ROOT/pages/usage.adoc b/docs/modules/ROOT/pages/usage.adoc index de14b645..8d8c5ef5 100644 --- a/docs/modules/ROOT/pages/usage.adoc +++ b/docs/modules/ROOT/pages/usage.adoc @@ -5,12 +5,12 @@ Trino works together with the Apache Hive metastore and S3 bucket. == Prerequisites * Deployed Stackable Apache Hive metastore -* Deployed Stackable https://github.com/stackabletech/secret-operator[secret-operator] * Accessible S3 Bucket ** Endpoint, access-key and secret-key ** Data in the Bucket (we use the https://archive.ics.uci.edu/ml/datasets/iris[Iris] dataset here) -* Optional for authorization: Deployed Stackable OPA cluster + RegoRule server -* Optional to test queries with https://repo.stackable.tech/#browse/browse:packages:trino-cli%2Ftrino-cli-363-executable.jar[Trino CLI] +* Optional deployed Stackable https://github.com/stackabletech/secret-operator[Secret-Operator] for certificates when deploying for HTTPS +* Optional for authorization: Deployed Stackable https://github.com/stackabletech/opa-operator[OPA-Operator] and https://github.com/stackabletech/regorule-operator[Regorule-Operator] +* Optional https://repo.stackable.tech/#browse/browse:packages:trino-cli%2Ftrino-cli-363-executable.jar[Trino CLI] to test SQL queries == Installation @@ -33,33 +33,35 @@ Please refer to the https://github.com/stackabletech/opa-operator[OPA] operator === Authentication We provide user authentication via secret that can be referred in the custom resource: - - authentication: - method: - multiUser: - userCredentialsSecret: - namespace: default - name: simple-trino-users-secret - -These secrets need to be created manually before startup (check `examples/simple-trino-users-secret.yaml`): - - kubectl apply -f examples/simple-trino-users-secret.yaml - -The secret looks the following: - - apiVersion: v1 - kind: Secret - metadata: - name: simple-trino-users-secret - type: kubernetes.io/opaque - stringData: - admin: $2y$10$89xReovvDLacVzRGpjOyAOONnayOgDAyIS2nW9bs5DJT98q17Dy5i - alice: $2y$10$HcCa4k9v2DRrD/g7e5vEz.Bk.1xg00YTEHOZjPX7oK3KqMSt2xT8W - bob: $2y$10$xVRXtYZnYuQu66SmruijPO8WHFM/UK5QPHTr.Nzf4JMcZSqt3W.2. +[source,yaml] +---- +authentication: + method: + multiUser: + userCredentialsSecret: + namespace: default + name: simple-trino-users-secret +---- + +These secrets need to be created manually before startup. The secret may look like the following snippet: +[source,yaml] +---- +apiVersion: v1 +kind: Secret +metadata: + name: simple-trino-users-secret +type: kubernetes.io/opaque +stringData: + admin: $2y$10$89xReovvDLacVzRGpjOyAOONnayOgDAyIS2nW9bs5DJT98q17Dy5i + alice: $2y$10$HcCa4k9v2DRrD/g7e5vEz.Bk.1xg00YTEHOZjPX7oK3KqMSt2xT8W + bob: $2y$10$xVRXtYZnYuQu66SmruijPO8WHFM/UK5QPHTr.Nzf4JMcZSqt3W.2. +---- The : combinations are provided in the `stringData` field. The hashes are created using bcrypt with 10 rounds or more. - - htpasswd -nbBC 10 admin admin +[source] +---- +htpasswd -nbBC 10 admin admin +---- === Regorule Server (Authorization) @@ -69,7 +71,8 @@ Please refer to the https://github.com/stackabletech/regorule-operator[RegoRule] This is an example custom resource for the Stackable RegoRule server: -``` +[source,yaml] +---- apiVersion: opa.stackable.tech/v1alpha1 kind: RegoRule metadata: @@ -115,113 +118,226 @@ spec: } can_view_query_owned_by = true -``` +---- You can let the Trino operator write its own Rego rules by configuring the `authorization` field in the custom resource. This is a rudimentary implementation for user access. +[source,yaml] +---- +authorization: + package: trino + permissions: + admin: + schemas: + read: true + write: true + tables: + iris_parquet: + read: true + write: true + iris_csv: + read: true + write: true +---- + +Here we define permissions for an admin user who can read and write `schemas`, as well as having full access to the `iris_parquet` and `iris_csv` table. Currently, this is more for demonstration purposes. Users should write their own rego rules for more complex OPA authorization. === Trino With the prerequisites fulfilled, the CRD for this operator must be created: - - kubectl apply -f /etc/stackable/trino-operator/crd/trinocluster.crd.yaml - -To create a single node Trino (v362) cluster. Please adapt the `s3` with your credentials. - - cat <: --user=admin --password - -If you use self signed certificates, you also need to add `--insecure` to the command above. - -Create a schema and a table for the Iris data located in S3: - - CREATE SCHEMA IF NOT EXISTS hive.iris - WITH (location = 's3a://iris/'); - - CREATE TABLE IF NOT EXISTS hive.iris.iris_parquet ( - sepal_length DOUBLE, - sepal_width DOUBLE, - petal_length DOUBLE, - petal_width DOUBLE, - class VARCHAR - ) - WITH ( - external_location = 's3a://iris/parq', - format = 'PARQUET' - ); - -Query the data: - - SELECT - sepal_length, - class - FROM hive.iris.iris_parquet - LIMIT 10; +[source] +---- +kubectl apply -f /etc/stackable/trino-operator/crd/trinocluster.crd.yaml +---- + +==== Insecure for testing: + +Create an insecure single node Trino (v362) cluster for testing. You will access the UI/CLI via http and no user / password or authorization is required. Please adapt the `s3` settings with your credentials (check `examples/simple-trino-cluster.yaml` for an example setting up Hive and Trino): +[source,yaml] +---- +apiVersion: trino.stackable.tech/v1alpha1 +kind: TrinoCluster +metadata: + name: simple-trino +spec: + version: "0.0.362" + nodeEnvironment: production + hiveConfigMapName: simple-hive-derby + s3: + endPoint: changeme + accessKey: changeme + secretKey: changeme + sslEnabled: false + pathStyleAccess: true + coordinators: + roleGroups: + default: + selector: + matchLabels: + kubernetes.io/os: linux + replicas: 1 + config: {} + workers: + roleGroups: + default: + selector: + matchLabels: + kubernetes.io/os: linux + replicas: 1 + config: {} +---- + +To access the CLI please execute: +[source] +---- +./trino-cli-362-executable.jar --debug --server http://: --user=admin +---- + +==== Secure (https) for production: + +Create a secure single node Trino (v362) cluster. This will disable the UI access via http and requires username and password from the secret above. Please adapt the `s3` settings with your credentials (check `examples/simple-trino-cluster-authentication-opa-authorization.yaml` for a full example setting up Hive, OPA, Secrets and Trino): + +[source,yaml] +---- +apiVersion: trino.stackable.tech/v1alpha1 +kind: TrinoCluster +metadata: + name: simple-trino +spec: + version: "0.0.362" + nodeEnvironment: production + hiveConfigMapName: simple-hive-derby + opaConfigMapName: simple-opa + authentication: + method: + multiUser: + userCredentialsSecret: + namespace: default + name: simple-trino-users-secret + authorization: + package: trino + permissions: + admin: + schemas: + read: true + write: true + tables: + iris_parquet: + read: true + write: true + iris_csv: + read: true + write: true + bob: + schemas: + read: false + write: false + tables: + iris_parquet: + read: true + s3: + endPoint: changeme + accessKey: changeme + secretKey: changeme + sslEnabled: false + pathStyleAccess: true + coordinators: + roleGroups: + default: + selector: + matchLabels: + kubernetes.io/os: linux + replicas: 1 + config: {} + workers: + roleGroups: + default: + selector: + matchLabels: + kubernetes.io/os: linux + replicas: 1 + config: {} +---- + +To access the CLI please execute: +[source] +---- +./trino-cli-362-executable.jar --debug --server https://: --user=admin --password --insecure +---- + +If you use self signed certificates, you also need the `--insecure` flag above which can be omitted otherwise. + +=== Test Trino with Hive and S3 + +Create a schema and a table for the Iris data located in S3 and query data. This assumes to have the Iris data set in the `PARQUET` format available in the S3 bucket which can be downloaded https://www.kaggle.com/gpreda/iris-dataset/version/2?select=iris.parquet[here] + +==== Create schema +[source,sql] +---- +CREATE SCHEMA IF NOT EXISTS hive.iris +WITH (location = 's3a://iris/'); +---- +which should return: +---- +CREATE SCHEMA +---- + +==== Create table +[source,sql] +---- +CREATE TABLE IF NOT EXISTS hive.iris.iris_parquet ( + sepal_length DOUBLE, + sepal_width DOUBLE, + petal_length DOUBLE, + petal_width DOUBLE, + class VARCHAR +) +WITH ( + external_location = 's3a://iris/parq', + format = 'PARQUET' +); +---- +which should return: +---- +CREATE TABLE +---- + +==== Query data +[source,sql] +---- +SELECT + sepal_length, + class +FROM hive.iris.iris_parquet +LIMIT 10; +---- + +which should return something like this: +---- + sepal_length | class +--------------+------------- + 5.1 | Iris-setosa + 4.9 | Iris-setosa + 4.7 | Iris-setosa + 4.6 | Iris-setosa + 5.0 | Iris-setosa + 5.4 | Iris-setosa + 4.6 | Iris-setosa + 5.0 | Iris-setosa + 4.4 | Iris-setosa + 4.9 | Iris-setosa +(10 rows) + +Query 20220210_161615_00000_a8nka, FINISHED, 1 node +https://172.18.0.5:30299/ui/query.html?20220210_161615_00000_a8nka +Splits: 18 total, 18 done (100.00%) +CPU Time: 0.7s total, 20 rows/s, 11.3KB/s, 74% active +Per Node: 0.3 parallelism, 5 rows/s, 3.02KB/s +Parallelism: 0.3 +Peak Memory: 0B +2.67 [15 rows, 8.08KB] [5 rows/s, 3.02KB/s] +---- + +=== Tips If you work with opa, try changing some RegoRule entries to false and see if you are not allowed to e.g. list tables or schemas. diff --git a/examples/simple-hive-cluster-derby.yaml b/examples/simple-hive-cluster-derby.yaml deleted file mode 100644 index 88413d10..00000000 --- a/examples/simple-hive-cluster-derby.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: hive.stackable.tech/v1alpha1 -kind: HiveCluster -metadata: - name: simple-hive-derby -spec: - version: 2.3.9 - metastore: - roleGroups: - default: - selector: - matchLabels: - kubernetes.io/os: linux - replicas: 1 - config: - database: - connString: jdbc:derby:;databaseName=/stackable/metastore_db;create=true - user: APP - password: mine - dbType: derby - s3Connection: - endPoint: changeme - accessKey: changeme - secretKey: changeme - sslEnabled: false - pathStyleAccess: true diff --git a/examples/simple-opa-cluster.yaml b/examples/simple-opa-cluster.yaml deleted file mode 100644 index c9609cfd..00000000 --- a/examples/simple-opa-cluster.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: opa.stackable.tech/v1alpha1 -kind: OpenPolicyAgent -metadata: - name: simple-opa -spec: - version: "0.27.1" - servers: - roleGroups: - default: - selector: - matchLabels: - kubernetes.io/os: linux - config: - regoRuleReference: "http://regorule-operator:3030/opa/v1" diff --git a/examples/simple-trino-cluster-authentication-opa-authorization.yaml b/examples/simple-trino-cluster-authentication-opa-authorization.yaml new file mode 100644 index 00000000..c82634a3 --- /dev/null +++ b/examples/simple-trino-cluster-authentication-opa-authorization.yaml @@ -0,0 +1,113 @@ +--- +apiVersion: hive.stackable.tech/v1alpha1 +kind: HiveCluster +metadata: + name: simple-hive-derby +spec: + version: 2.3.9 + metastore: + roleGroups: + default: + selector: + matchLabels: + kubernetes.io/os: linux + replicas: 1 + config: + database: + connString: jdbc:derby:;databaseName=/stackable/metastore_db;create=true + user: APP + password: mine + dbType: derby + s3Connection: + endPoint: changeme + accessKey: changeme + secretKey: changeme + sslEnabled: false + pathStyleAccess: true +--- +apiVersion: opa.stackable.tech/v1alpha1 +kind: OpenPolicyAgent +metadata: + name: simple-opa +spec: + version: "0.27.1" + servers: + roleGroups: + default: + selector: + matchLabels: + kubernetes.io/os: linux + config: + regoRuleReference: "http://regorule-operator:3030/opa/v1" +--- +apiVersion: v1 +kind: Secret +metadata: + name: simple-trino-users-secret +type: kubernetes.io/opaque +stringData: + # admin:admin + admin: $2y$10$89xReovvDLacVzRGpjOyAOONnayOgDAyIS2nW9bs5DJT98q17Dy5i + # alice:alice + alice: $2y$10$HcCa4k9v2DRrD/g7e5vEz.Bk.1xg00YTEHOZjPX7oK3KqMSt2xT8W + # bob:bob + bob: $2y$10$xVRXtYZnYuQu66SmruijPO8WHFM/UK5QPHTr.Nzf4JMcZSqt3W.2. +--- +apiVersion: trino.stackable.tech/v1alpha1 +kind: TrinoCluster +metadata: + name: simple-trino +spec: + version: "0.0.362" + nodeEnvironment: production + hiveConfigMapName: simple-hive-derby + opaConfigMapName: simple-opa + authentication: + method: + multiUser: + userCredentialsSecret: + namespace: default + name: simple-trino-users-secret + authorization: + package: trino + permissions: + admin: + schemas: + read: true + write: true + tables: + iris_parquet: + read: true + write: true + iris_csv: + read: true + write: true + bob: + schemas: + read: false + write: false + tables: + iris_parquet: + read: true + s3: + endPoint: changeme + accessKey: changeme + secretKey: changeme + sslEnabled: false + pathStyleAccess: true + coordinators: + roleGroups: + default: + selector: + matchLabels: + kubernetes.io/os: linux + replicas: 1 + config: {} + workers: + roleGroups: + default: + selector: + matchLabels: + kubernetes.io/os: linux + replicas: 1 + config: {} diff --git a/examples/simple-trino-cluster.yaml b/examples/simple-trino-cluster.yaml index ef7728fb..b3fb0e48 100644 --- a/examples/simple-trino-cluster.yaml +++ b/examples/simple-trino-cluster.yaml @@ -1,3 +1,30 @@ +--- +apiVersion: hive.stackable.tech/v1alpha1 +kind: HiveCluster +metadata: + name: simple-hive-derby +spec: + version: 2.3.9 + metastore: + roleGroups: + default: + selector: + matchLabels: + kubernetes.io/os: linux + replicas: 1 + config: + database: + connString: jdbc:derby:;databaseName=/stackable/metastore_db;create=true + user: APP + password: mine + dbType: derby + s3Connection: + endPoint: changeme + accessKey: changeme + secretKey: changeme + sslEnabled: false + pathStyleAccess: true +--- apiVersion: trino.stackable.tech/v1alpha1 kind: TrinoCluster metadata: @@ -5,39 +32,7 @@ metadata: spec: version: "0.0.362" nodeEnvironment: production - hive: - namespace: default - name: simple-hive-derby - opa: - namespace: default - name: simple-opa - authentication: - method: - multiUser: - userCredentialsSecret: - namespace: default - name: simple-trino-users-secret - authorization: - package: trino - permissions: - admin: - schemas: - read: true - write: true - tables: - iris_parquet: - read: true - write: true - iris_csv: - read: true - write: true - bob: - schemas: - read: false - write: false - tables: - iris_parquet: - read: true + hiveConfigMapName: simple-hive-derby s3: endPoint: changeme accessKey: changeme diff --git a/examples/simple-trino-users-secret.yaml b/examples/simple-trino-users-secret.yaml deleted file mode 100644 index 1552f243..00000000 --- a/examples/simple-trino-users-secret.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: simple-trino-users-secret -type: kubernetes.io/opaque -stringData: - # admin:admin - admin: $2y$10$89xReovvDLacVzRGpjOyAOONnayOgDAyIS2nW9bs5DJT98q17Dy5i - # alice:alice - alice: $2y$10$HcCa4k9v2DRrD/g7e5vEz.Bk.1xg00YTEHOZjPX7oK3KqMSt2xT8W - # bob:bob - bob: $2y$10$xVRXtYZnYuQu66SmruijPO8WHFM/UK5QPHTr.Nzf4JMcZSqt3W.2. diff --git a/rust/crd/src/authentication.rs b/rust/crd/src/authentication.rs index 124f0b10..216cabcb 100644 --- a/rust/crd/src/authentication.rs +++ b/rust/crd/src/authentication.rs @@ -50,13 +50,6 @@ pub enum TrinoAuthenticationMethod { }, } -#[derive(Clone, Debug, PartialEq)] -pub enum TrinoAuthenticationConfig { - MultiUser { - user_credentials: BTreeMap, - }, -} - impl TrinoAuthenticationMethod { pub async fn materialize( &self, @@ -108,3 +101,22 @@ impl TrinoAuthenticationMethod { } } } + +#[derive(Clone, Debug, PartialEq)] +pub enum TrinoAuthenticationConfig { + MultiUser { + user_credentials: BTreeMap, + }, +} + +impl TrinoAuthenticationConfig { + pub fn to_trino_user_data(&self) -> String { + match self { + TrinoAuthenticationConfig::MultiUser { user_credentials } => user_credentials + .iter() + .map(|(user, password)| format!("{}:{}", user, password)) + .collect::>() + .join("\n"), + } + } +} diff --git a/rust/crd/src/lib.rs b/rust/crd/src/lib.rs index d6aad459..265cb283 100644 --- a/rust/crd/src/lib.rs +++ b/rust/crd/src/lib.rs @@ -39,6 +39,7 @@ pub const LOG_PROPERTIES: &str = "log.properties"; pub const PASSWORD_AUTHENTICATOR_PROPERTIES: &str = "password-authenticator.properties"; pub const PASSWORD_DB: &str = "password.db"; pub const HIVE_PROPERTIES: &str = "hive.properties"; +pub const ACCESS_CONTROL_PROPERTIES: &str = "access-control.properties"; // node.properties pub const NODE_ENVIRONMENT: &str = "node.environment"; pub const NODE_ID: &str = "node.id"; @@ -74,6 +75,7 @@ pub const IO_TRINO: &str = "io.trino"; pub const METRICS_PORT_PROPERTY: &str = "metricsPort"; // directories pub const CONFIG_DIR_NAME: &str = "/stackable/conf"; +pub const RW_CONFIG_DIR_NAME: &str = "/stackable/rwconf"; pub const DATA_DIR_NAME: &str = "/stackable/data"; pub const KEYSTORE_DIR_NAME: &str = "/stackable/keystore"; pub const USER_PASSWORD_DATA: &str = "/stackable/users"; @@ -81,7 +83,7 @@ pub const USER_PASSWORD_DATA: &str = "/stackable/users"; #[derive(Snafu, Debug)] pub enum Error { #[snafu(display("object has no namespace associated"))] - NoNamespaceError, + NoNamespace, #[snafu(display("Unknown Trino role found {role}. Should be one of {roles:?}"))] UnknownTrinoRole { role: String, roles: Vec }, } @@ -110,9 +112,9 @@ pub struct TrinoClusterSpec { pub version: Option, pub node_environment: String, #[serde(default, skip_serializing_if = "Option::is_none")] - pub hive: Option, + pub hive_config_map_name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub opa: Option, + pub opa_config_map_name: Option, /// A reference to a secret containing username/password for defined users #[serde(default, skip_serializing_if = "Option::is_none")] pub authentication: Option, @@ -196,15 +198,6 @@ impl FromStr for TrinoRole { #[serde(rename_all = "camelCase")] pub struct TrinoClusterStatus {} -// TODO: move to operator-rs? Used for hive, opa, zookeeper ... -#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ClusterRef { - pub name: String, - pub namespace: Option, - pub chroot: Option, -} - // TODO: move to operator-rs? Copied from hive operator. /// Contains all the required connection information for S3. #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)] @@ -296,6 +289,10 @@ impl Configuration for TrinoConfig { HTTP_SERVER_HTTPS_ENABLED.to_string(), Some(true.to_string()), ); + result.insert( + HTTP_SERVER_HTTPS_PORT.to_string(), + Some(HTTPS_PORT.to_string()), + ); result.insert( HTTP_SERVER_KEYSTORE_PATH.to_string(), Some(format!("{}/{}", KEYSTORE_DIR_NAME, "keystore.p12")), @@ -305,15 +302,14 @@ impl Configuration for TrinoConfig { "http-server.https.keystore.key".to_string(), Some("secret".to_string()), ); - } - if resource.spec.authentication.is_some() - && role_name == TrinoRole::Coordinator.to_string() - { - result.insert( - HTTP_SERVER_AUTHENTICATION_TYPE.to_string(), - Some(HTTP_SERVER_AUTHENTICATION_TYPE_PASSWORD.to_string()), - ); + // password ui login + if role_name == TrinoRole::Coordinator.to_string() { + result.insert( + HTTP_SERVER_AUTHENTICATION_TYPE.to_string(), + Some(HTTP_SERVER_AUTHENTICATION_TYPE_PASSWORD.to_string()), + ); + } } } PASSWORD_AUTHENTICATOR_PROPERTIES => { diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 595c1adf..fc01ad53 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -2,10 +2,10 @@ use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::client::Client; use stackable_operator::k8s_openapi::api::core::v1::{ - CSIVolumeSource, ContainerPort, Probe, SecurityContext, TCPSocketAction, + CSIVolumeSource, ConfigMapKeySelector, ContainerPort, EmptyDirVolumeSource, EnvVarSource, + Probe, SecurityContext, TCPSocketAction, }; use stackable_operator::k8s_openapi::apimachinery::pkg::util::intstr::IntOrString; -use stackable_operator::kube::runtime::reflector::ObjectRef; use stackable_operator::kube::ResourceExt; use stackable_operator::product_config_utils::ValidatedRoleConfigByPropertyKind; use stackable_operator::role_utils::RoleGroupRef; @@ -35,11 +35,11 @@ use stackable_trino_crd::authentication::TrinoAuthenticationConfig; use stackable_trino_crd::authorization::create_rego_rules; use stackable_trino_crd::discovery::{TrinoDiscovery, TrinoDiscoveryProtocol, TrinoPodRef}; use stackable_trino_crd::{ - authentication, authorization, ClusterRef, TrinoCluster, TrinoClusterSpec, CONFIG_DIR_NAME, - CONFIG_PROPERTIES, DATA_DIR_NAME, DISCOVERY_URI, FIELD_MANAGER_SCOPE, HIVE_PROPERTIES, - HTTPS_PORT, HTTPS_PORT_NAME, HTTP_PORT_NAME, JVM_CONFIG, KEYSTORE_DIR_NAME, LOG_PROPERTIES, - METRICS_PORT, METRICS_PORT_NAME, NODE_PROPERTIES, PASSWORD_AUTHENTICATOR_PROPERTIES, - PASSWORD_DB, USER_PASSWORD_DATA, + authentication, authorization, TrinoCluster, TrinoClusterSpec, ACCESS_CONTROL_PROPERTIES, + CONFIG_DIR_NAME, CONFIG_PROPERTIES, DATA_DIR_NAME, DISCOVERY_URI, FIELD_MANAGER_SCOPE, + HIVE_PROPERTIES, HTTPS_PORT, HTTPS_PORT_NAME, HTTP_PORT_NAME, JVM_CONFIG, KEYSTORE_DIR_NAME, + LOG_PROPERTIES, METRICS_PORT, METRICS_PORT_NAME, NODE_PROPERTIES, + PASSWORD_AUTHENTICATOR_PROPERTIES, PASSWORD_DB, RW_CONFIG_DIR_NAME, USER_PASSWORD_DATA, }; use stackable_trino_crd::{TrinoRole, APP_NAME, HTTP_PORT}; use std::{ @@ -48,6 +48,7 @@ use std::{ sync::Arc, time::Duration, }; +use tracing::warn; pub struct Ctx { pub client: stackable_operator::client::Client, @@ -102,27 +103,13 @@ pub enum Error { source: stackable_operator::product_config_utils::ConfigError, }, #[snafu(display("failed to format runtime properties"))] - PropertiesWriteError { + FailedToWriteJavaProperties { source: stackable_operator::product_config::writer::PropertiesWriterError, }, - #[snafu(display("Failed to load Product Config"))] + #[snafu(display("failed to load Product Config"))] ProductConfigLoadFailed, - #[snafu(display("failed to create rego rules for authorization"))] - RegoRuleAuthorizationError { source: authorization::Error }, - #[snafu(display("failed to get config map {}", config_map))] - MissingConfigMap { - source: stackable_operator::error::Error, - config_map: ObjectRef, - }, - #[snafu(display( - "failed to get [{}] connection string from config map {}", - product, - config_map - ))] - MissingConnectString { - product: String, - config_map: ObjectRef, - }, + #[snafu(display("failed to write rego rules for authorization"))] + WriteRegoRuleAuthorizationFailed { source: authorization::Error }, #[snafu(display("failed to processing authentication config element from k8s"))] FailedProcessingAuthentication { source: authentication::Error }, #[snafu(display("internal operator failure"))] @@ -145,10 +132,12 @@ pub async fn reconcile_trino( let validated_config = validated_product_config(&trino, version, &ctx.get_ref().product_config)?; + let authentication_config = user_authentication(&trino, client).await?; + // rego rules create_rego_rules(client, &trino) .await - .context(RegoRuleAuthorizationSnafu)?; + .context(WriteRegoRuleAuthorizationFailedSnafu)?; let coordinator_role_service = build_coordinator_role_service(&trino)?; client @@ -160,41 +149,21 @@ pub async fn reconcile_trino( .await .context(ApplyRoleServiceSnafu)?; - let opa_connect = opa_connect(&trino, &ctx.get_ref().client).await?; - let hive_connect = hive_connect(&trino, &ctx.get_ref().client).await?; - - let authentication_config = match &trino.spec.authentication { - Some(authentication) => Some( - authentication - .method - .materialize( - client, - trino - .namespace() - .as_deref() - .context(ObjectHasNoNamespaceSnafu)?, - ) - .await - .context(FailedProcessingAuthenticationSnafu)?, - ), - _ => None, - }; - for (role, role_config) in validated_config { let trino_role = TrinoRole::from_str(&role).context(InternalOperatorFailureSnafu)?; for (role_group, config) in role_config { let rolegroup = trino_role.rolegroup_ref(&trino, role_group); let rg_service = build_rolegroup_service(&trino, &rolegroup)?; let rg_configmap = - build_rolegroup_config_map(&trino, &trino_role, &rolegroup, &config, &opa_connect)?; + build_rolegroup_config_map(&trino, &trino_role, &rolegroup, &config)?; let rg_catalog_configmap = - build_rolegroup_catalog_config_map(&trino, &rolegroup, &config, &hive_connect)?; + build_rolegroup_catalog_config_map(&trino, &rolegroup, &config)?; let rg_stateful_set = build_rolegroup_statefulset( &trino, &trino_role, &rolegroup, &config, - &authentication_config, + authentication_config.to_owned(), )?; client @@ -252,7 +221,7 @@ pub fn build_coordinator_role_service(trino: &TrinoCluster) -> Result { .with_recommended_labels(trino, APP_NAME, trino_version(trino)?, &role_name, "global") .build(), spec: Some(ServiceSpec { - ports: Some(service_ports()), + ports: Some(service_ports(trino)), selector: Some(role_selector_labels(trino, APP_NAME, &role_name)), type_: Some("NodePort".to_string()), ..ServiceSpec::default() @@ -267,7 +236,6 @@ fn build_rolegroup_config_map( _role: &TrinoRole, rolegroup_ref: &RoleGroupRef, config: &HashMap>, - opa_connect: &Option, ) -> Result { let mut cm_conf_data = BTreeMap::new(); @@ -313,7 +281,7 @@ fn build_rolegroup_config_map( let config_properties = product_config::writer::to_java_properties_string(transformed_config.iter()) - .context(PropertiesWriteSnafu)?; + .context(FailedToWriteJavaPropertiesSnafu)?; cm_conf_data.insert(file_name.to_string(), config_properties); } @@ -321,21 +289,21 @@ fn build_rolegroup_config_map( PropertyNameKind::File(file_name) if file_name == NODE_PROPERTIES => { let node_properties = product_config::writer::to_java_properties_string(transformed_config.iter()) - .context(PropertiesWriteSnafu)?; + .context(FailedToWriteJavaPropertiesSnafu)?; cm_conf_data.insert(file_name.to_string(), node_properties); } PropertyNameKind::File(file_name) if file_name == LOG_PROPERTIES => { let log_properties = product_config::writer::to_java_properties_string(transformed_config.iter()) - .context(PropertiesWriteSnafu)?; + .context(FailedToWriteJavaPropertiesSnafu)?; cm_conf_data.insert(file_name.to_string(), log_properties); } PropertyNameKind::File(file_name) if file_name == PASSWORD_AUTHENTICATOR_PROPERTIES => { let pw_properties = product_config::writer::to_java_properties_string(transformed_config.iter()) - .context(PropertiesWriteSnafu)?; + .context(FailedToWriteJavaPropertiesSnafu)?; cm_conf_data.insert(file_name.to_string(), pw_properties); } PropertyNameKind::File(file_name) if file_name == PASSWORD_DB => { @@ -349,31 +317,19 @@ fn build_rolegroup_config_map( } } - if let Some(opa) = opa_connect { - let package = match trino.spec.authorization.as_ref() { - Some(auth) => auth.package.clone(), - None => { - println!("No package specified in 'authorization'. Defaulting to 'trino'."); - "trino".to_string() - } - }; - + if trino.spec.opa_config_map_name.is_some() { let mut opa_config = BTreeMap::new(); - + // the "opa.policy.uri" property will be added via command script later from the env variable "OPA" opa_config.insert( "access-control.name".to_string(), Some("tech.stackable.trino.opa.OpaAuthorizer".to_string()), ); - opa_config.insert( - "opa.policy.uri".to_string(), - Some(format!("{}v1/data/{}/", opa, package)), - ); let config_properties = product_config::writer::to_java_properties_string(opa_config.iter()) - .context(PropertiesWriteSnafu)?; + .context(FailedToWriteJavaPropertiesSnafu)?; - cm_conf_data.insert("access-control.properties".to_string(), config_properties); + cm_conf_data.insert(ACCESS_CONTROL_PROPERTIES.to_string(), config_properties); } cm_conf_data.insert(JVM_CONFIG.to_string(), jvm_config.to_string()); @@ -407,7 +363,6 @@ fn build_rolegroup_catalog_config_map( trino: &TrinoCluster, rolegroup_ref: &RoleGroupRef, config: &HashMap>, - hive_connect: &Option, ) -> Result { let mut cm_hive_data = BTreeMap::new(); @@ -419,16 +374,16 @@ fn build_rolegroup_catalog_config_map( match property_name_kind { PropertyNameKind::File(file_name) if file_name == HIVE_PROPERTIES => { - if let Some(hive_connect) = &hive_connect { + if trino.spec.hive_config_map_name.is_some() { + // hive.metastore.uri will be added later via command script from the + // "HIVE" env variable transformed_config .insert("connector.name".to_string(), Some("hive".to_string())); - transformed_config - .insert("hive.metastore.uri".to_string(), Some(hive_connect.clone())); let config_properties = product_config::writer::to_java_properties_string( transformed_config.iter(), ) - .context(PropertiesWriteSnafu)?; + .context(FailedToWriteJavaPropertiesSnafu)?; cm_hive_data.insert(file_name.to_string(), config_properties); } @@ -469,7 +424,7 @@ fn build_rolegroup_statefulset( role: &TrinoRole, rolegroup_ref: &RoleGroupRef, config: &HashMap>, - authentication_config: &Option, + authentication_config: Option, ) -> Result { let rolegroup = role .get_spec(trino) @@ -479,11 +434,8 @@ fn build_rolegroup_statefulset( .role_groups .get(&rolegroup_ref.role_group); let trino_version = trino_version_trim(trino)?; - let image = format!( - "docker.stackable.tech/stackable/trino:{}-stackable0", - trino_version - ); - let env = config + + let mut env = config .get(&PropertyNameKind::Env) .iter() .flat_map(|env_vars| env_vars.iter()) @@ -494,36 +446,20 @@ fn build_rolegroup_statefulset( }) .collect::>(); - let user_data = match authentication_config { - Some(TrinoAuthenticationConfig::MultiUser { user_credentials }) => user_credentials - .iter() - .map(|(user, password)| format!("{}:{}", user, password)) - .collect::>() - .join("\n"), - None => String::new(), + if let Some(opa) = env_var_from_discovery_config_map(&trino.spec.opa_config_map_name, "OPA") { + env.push(opa); + }; + + if let Some(hive) = env_var_from_discovery_config_map(&trino.spec.hive_config_map_name, "HIVE") + { + env.push(hive); }; let mut container_prepare = ContainerBuilder::new("prepare") - .image(&image) + .image("docker.stackable.tech/stackable/tools:0.2.0-stackable0") .command(vec!["/bin/bash".to_string(), "-c".to_string()]) - .args(vec![[ - "microdnf install openssl", - "echo Storing password", - "echo secret > /stackable/keystore/password", - "echo Creating truststore", - "keytool -importcert -file /stackable/keystore/ca.crt -keystore /stackable/keystore/truststore.p12 -storetype pkcs12 -noprompt -alias ca_cert -storepass secret", - "echo Creating certificate chain", - "cat /stackable/keystore/ca.crt /stackable/keystore/tls.crt > /stackable/keystore/chain.crt", - "echo Creating keystore", - "openssl pkcs12 -export -in /stackable/keystore/chain.crt -inkey /stackable/keystore/tls.key -out /stackable/keystore/keystore.p12 --passout file:/stackable/keystore/password", - "echo Cleaning up password", - "rm -f /stackable/keystore/password", - "echo chowning keystore directory", - "chown -R stackable:stackable /stackable/keystore", - "echo chmodding keystore directory", - "chmod -R a=,u=rwX /stackable/keystore", - ].join(" && ")]) - .add_volume_mount("keystore", "/stackable/keystore") + .args(container_prepare_args()) + .add_volume_mount("keystore", KEYSTORE_DIR_NAME) .build(); container_prepare @@ -532,46 +468,21 @@ fn build_rolegroup_statefulset( .run_as_user = Some(0); let container_trino = ContainerBuilder::new(APP_NAME) - .image(image) + .image(format!( + "docker.stackable.tech/stackable/trino:{}-stackable0", + trino_version + )) .command(vec!["/bin/bash".to_string(), "-c".to_string()]) - .args(vec![[ - format!( - "echo Writing user data to {}/{}", - USER_PASSWORD_DATA, PASSWORD_DB - ), - format!("mkdir {}", USER_PASSWORD_DATA), - format!( - "echo '{}' > {}/{} ", - user_data, USER_PASSWORD_DATA, PASSWORD_DB - ), - format!("bin/launcher run --etc-dir={}", CONFIG_DIR_NAME), - ] - .join(" && ")]) + .args(container_trino_args(trino, authentication_config)) .add_env_vars(env) .add_volume_mount("data", DATA_DIR_NAME) .add_volume_mount("conf", CONFIG_DIR_NAME) + .add_volume_mount("rwconf", RW_CONFIG_DIR_NAME) .add_volume_mount("keystore", KEYSTORE_DIR_NAME) .add_volume_mount("catalog", format!("{}/catalog", CONFIG_DIR_NAME)) - .add_container_ports(container_ports()) - .readiness_probe(Probe { - initial_delay_seconds: Some(10), - period_seconds: Some(10), - failure_threshold: Some(5), - tcp_socket: Some(TCPSocketAction { - port: IntOrString::String(HTTPS_PORT_NAME.to_string()), - ..TCPSocketAction::default() - }), - ..Probe::default() - }) - .liveness_probe(Probe { - initial_delay_seconds: Some(30), - period_seconds: Some(10), - tcp_socket: Some(TCPSocketAction { - port: IntOrString::String(HTTPS_PORT_NAME.to_string()), - ..TCPSocketAction::default() - }), - ..Probe::default() - }) + .add_container_ports(container_ports(trino)) + .readiness_probe(readiness_probe(trino)) + .liveness_probe(liveness_probe(trino)) .build(); Ok(StatefulSet { metadata: ObjectMetaBuilder::new() @@ -624,6 +535,14 @@ fn build_rolegroup_statefulset( }), ..Volume::default() }) + .add_volume(Volume { + empty_dir: Some(EmptyDirVolumeSource { + medium: None, + size_limit: None, + }), + name: "rwconf".to_string(), + ..Volume::default() + }) .add_volume(Volume { name: "catalog".to_string(), config_map: Some(ConfigMapVolumeSource { @@ -691,7 +610,7 @@ fn build_rolegroup_service( .build(), spec: Some(ServiceSpec { cluster_ip: Some("None".to_string()), - ports: Some(service_ports()), + ports: Some(service_ports(trino)), selector: Some(role_group_selector_labels( trino, APP_NAME, @@ -729,55 +648,139 @@ pub fn error_policy(_error: &Error, _ctx: Context) -> ReconcilerAction { } } -async fn opa_connect(trino: &TrinoCluster, client: &Client) -> Result> { - let mut opa_connect_string = None; - - let spec: &TrinoClusterSpec = &trino.spec; - - if let Some(opa_reference) = &spec.opa { - let product = "OPA"; - - let (name, namespace) = name_and_namespace(trino, opa_reference)?; - - opa_connect_string = Some(cluster_ref_cm_data(client, &name, &namespace, product).await?); - } - - Ok(opa_connect_string) +async fn user_authentication( + trino: &TrinoCluster, + client: &Client, +) -> Result> { + Ok(match &trino.spec.authentication { + Some(authentication) => Some( + authentication + .method + .materialize( + client, + trino + .namespace() + .as_deref() + .context(ObjectHasNoNamespaceSnafu)?, + ) + .await + .context(FailedProcessingAuthenticationSnafu)?, + ), + _ => None, + }) } -async fn hive_connect(trino: &TrinoCluster, client: &Client) -> Result> { - let mut hive_connect_string = None; +fn container_prepare_args() -> Vec { + vec![[ + "echo Storing password", + "echo secret > /stackable/keystore/password", + "echo Cleaning up truststore - just in case", + "rm -f /stackable/keystore/truststore.p12", + "echo Creating truststore", + "keytool -importcert -file /stackable/keystore/ca.crt -keystore /stackable/keystore/truststore.p12 -storetype pkcs12 -noprompt -alias ca_cert -storepass secret", + "echo Creating certificate chain", + "cat /stackable/keystore/ca.crt /stackable/keystore/tls.crt > /stackable/keystore/chain.crt", + "echo Creating keystore", + "openssl pkcs12 -export -in /stackable/keystore/chain.crt -inkey /stackable/keystore/tls.key -out /stackable/keystore/keystore.p12 --passout file:/stackable/keystore/password", + "echo Cleaning up password", + "rm -f /stackable/keystore/password", + "echo chowning keystore directory", + "chown -R stackable:stackable /stackable/keystore", + "echo chmodding keystore directory", + "chmod -R a=,u=rwX /stackable/keystore", + ].join(" && ")] +} - if let Some(hive_reference) = &trino.spec.hive { - let product = "HIVE"; +fn container_trino_args( + trino: &TrinoCluster, + user_authentication: Option, +) -> Vec { + let mut args = vec![ + // copy config files to a writeable empty folder + format!( + "echo Copying {conf} to {rw_conf}", + conf = CONFIG_DIR_NAME, + rw_conf = RW_CONFIG_DIR_NAME + ), + format!( + "cp -RL {conf}/* {rw_conf}", + conf = CONFIG_DIR_NAME, + rw_conf = RW_CONFIG_DIR_NAME + ), + ]; - let (name, namespace) = name_and_namespace(trino, hive_reference)?; + if let Some(auth) = user_authentication { + let user_data = auth.to_trino_user_data(); + args.extend(vec![ + format!( + "echo Writing user data to {path}/{db}", + path = USER_PASSWORD_DATA, + db = PASSWORD_DB + ), + format!("mkdir {}", USER_PASSWORD_DATA), + format!( + "echo '{data}' > {path}/{db} ", + data = user_data, + path = USER_PASSWORD_DATA, + db = PASSWORD_DB + ), + ]) + } + // hive required? + if trino.spec.hive_config_map_name.is_some() { + args.extend(vec![ + format!( "echo Writing HIVE connect string \"hive.metastore.uri=${{HIVE}}\" to {rw_conf}/catalog/{hive_properties}", + rw_conf = RW_CONFIG_DIR_NAME, hive_properties = HIVE_PROPERTIES + ), + format!( "echo \"hive.metastore.uri=${{HIVE}}\" >> {rw_conf}/catalog/{hive_properties}", + rw_conf = RW_CONFIG_DIR_NAME, hive_properties = HIVE_PROPERTIES + )]) + } + // opa required? + if trino.spec.opa_config_map_name.is_some() { + let opa_package_name = match trino.spec.authorization.as_ref() { + Some(auth) => auth.package.clone(), + None => { + warn!("No package specified in 'spec.authorization'. Defaulting to 'trino'."); + "trino".to_string() + } + }; - hive_connect_string = cluster_ref_cm_data(client, &name, &namespace, product) - .await? - // TODO: hive now offers all pods fqdn(s) instead of the service - // this should be removed - .split('\n') - .collect::>() - .into_iter() - .next() - .map(|s| s.to_string()); + args.extend(vec![ + format!( "echo Writing OPA connect string \"opa.policy.uri=${{OPA}}v1/data/{package_name}\" to {rw_conf}/{access_control}", + package_name = opa_package_name, rw_conf = RW_CONFIG_DIR_NAME, access_control = ACCESS_CONTROL_PROPERTIES + ), + format!( "echo \"opa.policy.uri=${{OPA}}v1/data/{package_name}/\" >> {rw_conf}/{access_control}", + package_name = opa_package_name, rw_conf = RW_CONFIG_DIR_NAME, access_control = ACCESS_CONTROL_PROPERTIES + )]) } - Ok(hive_connect_string) -} + // start command + args.push(format!( + "bin/launcher run --etc-dir={conf} --data-dir={data}", + conf = RW_CONFIG_DIR_NAME, + data = DATA_DIR_NAME + )); -/// Return the name and adapted namespace of the `cluster_ref`. -/// If `cluster_ref` has no namespace defined we default to the `trino` namespace. -/// If `trino` has no namespace defined we throw an error. -fn name_and_namespace(trino: &TrinoCluster, cluster_ref: &ClusterRef) -> Result<(String, String)> { - let name = cluster_ref.name.clone(); - let namespace = match &cluster_ref.namespace { - Some(ns) => ns.clone(), - None => trino.namespace().context(ObjectHasNoNamespaceSnafu)?, - }; + vec![args.join(" && ")] +} - Ok((name, namespace)) +fn env_var_from_discovery_config_map( + config_map_name: &Option, + env_var: &str, +) -> Option { + config_map_name.as_ref().map(|cm_name| EnvVar { + name: env_var.to_string(), + value_from: Some(EnvVarSource { + config_map_key_ref: Some(ConfigMapKeySelector { + name: Some(cm_name.to_string()), + key: env_var.to_string(), + ..ConfigMapKeySelector::default() + }), + ..EnvVarSource::default() + }), + ..EnvVar::default() + }) } /// Defines all required roles and their required configuration. @@ -845,70 +848,60 @@ fn validated_product_config( .context(InvalidProductConfigSnafu) } -async fn cluster_ref_cm_data( - client: &Client, - name: &str, - namespace: &str, - product_name: &str, -) -> Result { - Ok(client - .get::(name, Some(namespace)) - .await - .with_context(|_| MissingConfigMapSnafu { - config_map: ObjectRef::new(name).within(namespace), - })? - .data - .and_then(|mut data| data.remove(product_name)) - .with_context(|| MissingConnectStringSnafu { - product: product_name.to_string(), - config_map: ObjectRef::new(name).within(namespace), - })?) -} - -fn service_ports() -> Vec { - vec![ +fn service_ports(trino: &TrinoCluster) -> Vec { + let mut ports = vec![ ServicePort { name: Some(HTTP_PORT_NAME.to_string()), port: HTTP_PORT.into(), protocol: Some("TCP".to_string()), ..ServicePort::default() }, - ServicePort { - name: Some(HTTPS_PORT_NAME.to_string()), - port: HTTPS_PORT.into(), - protocol: Some("TCP".to_string()), - ..ServicePort::default() - }, ServicePort { name: Some(METRICS_PORT_NAME.to_string()), port: METRICS_PORT.into(), protocol: Some("TCP".to_string()), ..ServicePort::default() }, - ] + ]; + + if trino.spec.authentication.is_some() { + ports.push(ServicePort { + name: Some(HTTPS_PORT_NAME.to_string()), + port: HTTPS_PORT.into(), + protocol: Some("TCP".to_string()), + ..ServicePort::default() + }); + } + + ports } -fn container_ports() -> Vec { - vec![ +fn container_ports(trino: &TrinoCluster) -> Vec { + let mut ports = vec![ ContainerPort { name: Some(HTTP_PORT_NAME.to_string()), container_port: HTTP_PORT.into(), protocol: Some("TCP".to_string()), ..ContainerPort::default() }, - ContainerPort { - name: Some(HTTPS_PORT_NAME.to_string()), - container_port: HTTPS_PORT.into(), - protocol: Some("TCP".to_string()), - ..ContainerPort::default() - }, ContainerPort { name: Some(METRICS_PORT_NAME.to_string()), container_port: METRICS_PORT.into(), protocol: Some("TCP".to_string()), ..ContainerPort::default() }, - ] + ]; + + if trino.spec.authentication.is_some() { + ports.push(ContainerPort { + name: Some(HTTPS_PORT_NAME.to_string()), + container_port: HTTPS_PORT.into(), + protocol: Some("TCP".to_string()), + ..ContainerPort::default() + }); + } + + ports } fn get_stackable_secret_volume_attributes() -> BTreeMap { @@ -923,3 +916,38 @@ fn get_stackable_secret_volume_attributes() -> BTreeMap { ); result } + +fn readiness_probe(trino: &TrinoCluster) -> Probe { + let port_name = match trino.spec.authentication { + Some(_) => HTTPS_PORT_NAME, + _ => HTTP_PORT_NAME, + }; + + Probe { + initial_delay_seconds: Some(10), + period_seconds: Some(10), + failure_threshold: Some(5), + tcp_socket: Some(TCPSocketAction { + port: IntOrString::String(port_name.to_string()), + ..TCPSocketAction::default() + }), + ..Probe::default() + } +} + +fn liveness_probe(trino: &TrinoCluster) -> Probe { + let port_name = match trino.spec.authentication { + Some(_) => HTTPS_PORT_NAME, + _ => HTTP_PORT_NAME, + }; + + Probe { + initial_delay_seconds: Some(30), + period_seconds: Some(10), + tcp_socket: Some(TCPSocketAction { + port: IntOrString::String(port_name.to_string()), + ..TCPSocketAction::default() + }), + ..Probe::default() + } +}