diff --git a/.github/workflows/deploy-to-k8s.yaml b/.github/workflows/deploy-to-k8s.yaml index 480d98c..928337e 100644 --- a/.github/workflows/deploy-to-k8s.yaml +++ b/.github/workflows/deploy-to-k8s.yaml @@ -36,13 +36,18 @@ jobs: uats: env: feast-remote extra-args: "" - - module: kubeflow-spark - tf-vars-file: examples/tfvars.json - risk: edge # TODO: Run Spark tests with Kubeflow from `stable` risk once all changes for Spark integration are released to stable - uats_branch: main # TODO: Run Spark tests against ${{ inputs.uats_branch }} once UATs backported to track/1.x + # - module: kubeflow-spark + # tf-vars-file: examples/tfvars.json + # risk: edge # TODO: Run Spark tests with Kubeflow from `stable` risk once all changes for Spark integration are released to stable + # uats_branch: main # TODO: Run Spark tests against ${{ inputs.uats_branch }} once UATs backported to track/1.x + # uats: + # env: spark-remote + # extra-args: --test-image ghcr.io/canonical/charmed-spark-jupyterlab:3.5-22.04_edge@sha256:72a6e89985e35e0920fb40c063b3287425760ebf823b129a87143d5ec0e99af7 --bundle "" + - module: kubeflow-ambient + uats_branch: fix-increase-katib-experiment-timeout uats: - env: spark-remote - extra-args: --test-image ghcr.io/canonical/charmed-spark-jupyterlab:3.5-22.04_edge@sha256:72a6e89985e35e0920fb40c063b3287425760ebf823b129a87143d5ec0e99af7 --bundle "" + env: kubeflow-remote + extra-args: --include-kubeflow-trainer-tests steps: - name: Checkout repository @@ -103,6 +108,14 @@ jobs: sudo concierge prepare --trace cd .. + - name: Configure Cilium for Canonical K8s + if: matrix.bundle.module == 'kubeflow-ambient' + run: | + # Configure Cilium for Canonical K8s to work with Charmed Istio (Ambient mode) + # See https://canonical-service-mesh-documentation.readthedocs-hosted.com/en/latest/how-to/use-charmed-istio-with-canonical-kubernetes/ + kubectl -n kube-system patch configmap cilium-config --type merge --patch '{"data":{"bpf-lb-sock-hostns-only":"true"}}' + kubectl -n kube-system rollout restart daemonset cilium + - name: Setup terraform run: | sudo snap install terraform --channel latest/stable --classic @@ -119,7 +132,14 @@ jobs: TF_VARS_ARG="--tf-vars-file ${{ matrix.bundle.tf-vars-file }}" fi - tox -c ./modules/${{ matrix.bundle.module }} -e test_deployment -- -vv -s --risk ${{ env.RISK }} $TF_VARS_ARG + ISTIO_PLATFORM_ARG="" + RISK_ARG="--risk ${{ env.RISK }}" + if [[ "${{ matrix.bundle.module }}" == "kubeflow-ambient" ]]; then + ISTIO_PLATFORM_ARG='--istio-k8s-platform' + RISK_ARG="" + fi + + tox -c ./modules/${{ matrix.bundle.module }} -e test_deployment -- -vv -s $RISK_ARG $TF_VARS_ARG $ISTIO_PLATFORM_ARG - name: Run UATs run: | diff --git a/.github/workflows/deploy-to-microk8s.yaml b/.github/workflows/deploy-to-microk8s.yaml index bae7b8a..da904ca 100644 --- a/.github/workflows/deploy-to-microk8s.yaml +++ b/.github/workflows/deploy-to-microk8s.yaml @@ -37,14 +37,19 @@ jobs: uats: env: feast-remote extra-args: "" - - module: kubeflow-spark - risk: edge # TODO: Run Spark tests with Kubeflow from `stable` risk once all changes for Spark integration are released to stable - uats_branch: main # TODO: Run Spark tests against ${{ inputs.uats_branch }} once UATs backported to track/1.x - juju-agent-version: 3.6.9 # TODO: remove pin once Spark can work on recent Juju agent versions - tf-vars-file: examples/tfvars.json + # - module: kubeflow-spark + # risk: edge # TODO: Run Spark tests with Kubeflow from `stable` risk once all changes for Spark integration are released to stable + # uats_branch: main # TODO: Run Spark tests against ${{ inputs.uats_branch }} once UATs backported to track/1.x + # juju-agent-version: 3.6.9 # TODO: remove pin once Spark can work on recent Juju agent versions + # tf-vars-file: examples/tfvars.json + # uats: + # env: spark-remote + # extra-args: --test-image ghcr.io/canonical/charmed-spark-jupyterlab:3.5-22.04_edge@sha256:72a6e89985e35e0920fb40c063b3287425760ebf823b129a87143d5ec0e99af7 --bundle "" + - module: kubeflow-ambient + uats_branch: fix-increase-katib-experiment-timeout uats: - env: spark-remote - extra-args: --test-image ghcr.io/canonical/charmed-spark-jupyterlab:3.5-22.04_edge@sha256:72a6e89985e35e0920fb40c063b3287425760ebf823b129a87143d5ec0e99af7 --bundle "" + env: kubeflow-remote + extra-args: --include-kubeflow-trainer-tests pod-security-standards: - policy: privileged istio-cni-bin-dir: "" # meaning Istio CNI disabled @@ -158,7 +163,15 @@ jobs: TF_VARS_ARG="--tf-vars-file ${{ matrix.bundle.tf-vars-file }}" fi - tox -c ./modules/${{ matrix.bundle.module }} -e test_deployment -- -vv -s --risk ${{ env.RISK }} --istio-cni-bin-dir ${{ matrix.pod-security-standards.istio-cni-bin-dir }} --istio-cni-conf-dir ${{ matrix.pod-security-standards.istio-cni-conf-dir }} --pss ${{ matrix.pod-security-standards.policy }} $TF_VARS_ARG + CNI_ARGS="" + RISK_ARG="--risk ${{ env.RISK }}" + if [[ "${{ matrix.bundle.module }}" != "kubeflow-ambient" ]]; then + CNI_ARGS="--istio-cni-bin-dir ${{ matrix.pod-security-standards.istio-cni-bin-dir }} --istio-cni-conf-dir ${{ matrix.pod-security-standards.istio-cni-conf-dir }}" + else + RISK_ARG="" + fi + + tox -c ./modules/${{ matrix.bundle.module }} -e test_deployment -- -vv -s $RISK_ARG $CNI_ARGS --pss ${{ matrix.pod-security-standards.policy }} $TF_VARS_ARG - name: Run UATs run: | diff --git a/modules/kubeflow-ambient/README.md b/modules/kubeflow-ambient/README.md new file mode 100644 index 0000000..2c3fb4d --- /dev/null +++ b/modules/kubeflow-ambient/README.md @@ -0,0 +1,86 @@ +# Charmed Kubeflow Ambient Terraform solution + +> **⚠️ WARNING: EXPERIMENTAL** +> +> This solution is experimental and currently deploys charms from the `latest/edge` channel. It is **NOT recommended for production use**. + +This is a Terraform module facilitating the deployment of Charmed Kubeflow in ambient mode, using the [Terraform juju provider](https://github.com/juju/terraform-provider-juju/). For more information, refer to the provider [documentation](https://registry.terraform.io/providers/juju/juju/latest/docs). + +The ambient mode provides an alternative deployment of Kubeflow that includes all core components along with KServe for model serving capabilities. + +## API + +### Inputs +The solution module offers the following configurable inputs: + +| Name | Type | Description | Required | +| - | - | - | - | +| `_revision`| number | For each charm of the solution, the revision of the charm to deploy | False | +| `argo_controller_bucket`| string | The name of the bucket to be used by Argo controller in the object store | False | +| `create_model` | bool | Allows to skip Juju model creation and re-use a model created in a higher level module. When re-using a model, if this is created by Terraform, make sure that the current module depends on the resource using the `depends_on` option. | False | +| `cos_configuration`| bool | Boolean value that enables COS configuration | False | +| `dex_connectors`| string | dex-auth connectors in yaml format | False | +| `dex_static_username`| string | dex-auth static username | False | +| `dex_static_password`| string | dex-auth static password | False | +| `existing_opentelemetry_collector_name`| string | Name of an existing opentelemetry-collector-k8s deployment | False | +| `opentelemetry_collector_k8s_size`| string | OpenTelemetry collector storage size | False | +| `http_proxy`| string | Value of the http_proxy environment variable | False | +| `https_proxy`| string | Value of the https_proxy environment variable | False | +| `istio_k8s_platform`| string | Platform for istio-k8s | False | +| `jupyter_ui_config`| map(string) | Map of config values passed to jupyter-ui | False | +| `katib_db_size`| string | Katib database storage size | False | +| `kfp_api_object_store_bucket_name`| string | The name of the bucket to be used by KFP API in the object store | False | +| `kfp_db_size`| string | KFP database storage size | False | +| `kubeflow_dashboard_registration_flow`| string | Whether to enable the registration flow on sign-in for kubeflow-dashboard | False | +| `kubeflow_profiles_security_policy`| string | Security policy for pod security standards enforced in user workloads. Only `privileged` and `baseline` are supported | False | +| `kubeflow_trainer_v2`| bool | Boolean value that enables deployment of Kubeflow Trainer V2 (experimental) | False | +| `minio_access_key`| string | MinIO access key | False | +| `minio_gateway_storage_service`| string | Gateway storage service configuration for MinIO when in 'gateway' mode | False | +| `minio_mode`| string | MinIO mode, either 'server' or 'gateway' | False | +| `minio_secret_key`| string | MinIO secret key | False | +| `minio_size`| string | MinIO database storage size | False | +| `minio_storage_service_endpoint`| string | MinIO storage service endpoint, required if minio_mode is 'gateway' | False | +| `mlmd_size`| string | MLMD database storage size | False | +| `no_proxy`| string | Value of the no_proxy environment variable | False | +| `oidc_gatekeeper_ca_bundle`| string | Custom CA to be trusted by OIDC gatekeeper | False | +| `public_url`| string | Public URL of Kubeflow for auth/OIDC | False | + +### Outputs +Upon applied, the solution module exports the following outputs: + +| Name | Description | +| - | - | +| `dashboard_links_provider`| Map containing the `app_name` and `provides` endpoints of the kubeflow-dashboard charm | +| `kserve_controller`| Map containing the `app_name`, `provides` and `requires` fields of the kserve-controller charm | +| `opentelemetry_collector_k8s`| Map containing the `app_name`, `provides` and `requires` endpoints of the opentelemetry-collector-k8s charm used | +| `model`| Model name that Charmed Kubeflow is deployed on | + +## Usage + +This solution module is intended to be used either on its own or as part of a higher-level module. + +### Model +This solution always creates a model of the name `kubeflow`, since Charmed Kubeflow cannot be deployed in a different model. + +### COS configuration + +#### Enable COS configuration +The `cos_configuration` input enables the solution to configure Charmed Kubeflow to integrate with COS. This is done by deploying a `opentelemetry-collector-k8s` charm and adding all the required relations. +``` +terraform apply -var cos_configuration=true +``` + +#### Use an existing opentelemetry-collector-k8s +If there is already an instance of the opentelemetry-collector-k8s charm in the `kubeflow` model, then it can be used instead of deploying a new one. This is achieved with the use of `existing_opentelemetry_collector_name` input. By default, its value is `null`. +``` +terraform apply -var cos_configuration=true -var existing_opentelemetry_collector_name="dummy-opentelemetry-collector" +``` +> :warning: Setting this input without `cos_configuration` will not have any effect. + +### Kubeflow Trainer V2 + +#### Enable Kubeflow Trainer V2 (Experimental) +The `kubeflow_trainer_v2` input enables the solution to deploy Kubeflow Trainer V2 charm and all the required resources. +```shell +terraform apply -var kubeflow_trainer_v2=true +``` diff --git a/modules/kubeflow-ambient/applications.tf b/modules/kubeflow-ambient/applications.tf new file mode 100644 index 0000000..08c2602 --- /dev/null +++ b/modules/kubeflow-ambient/applications.tf @@ -0,0 +1,328 @@ + +module "admission_webhook" { + source = "git::https://github.com/canonical/admission-webhook-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.admission_webhook_revision + channel = "latest/edge" +} + +module "argo_controller" { + source = "git::https://github.com/canonical/argo-operators//charms/argo-controller/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.argo_controller_revision + channel = "latest/edge" + config = { + bucket = var.argo_controller_bucket + } +} + +module "dex_auth" { + source = "git::https://github.com/canonical/dex-auth-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + config = { + "public-url" : var.public_url, + "connectors" : var.dex_connectors + "static-username" : var.dex_static_username + "static-password" : var.dex_static_password + } + revision = var.dex_auth_revision + channel = "latest/edge" +} + +module "envoy" { + source = "git::https://github.com/canonical/envoy-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.envoy_revision + channel = "latest/edge" +} + +module "jupyter_controller" { + source = "git::https://github.com/canonical/notebook-operators//charms/jupyter-controller/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.jupyter_controller_revision + channel = "latest/edge" +} + +module "jupyter_ui" { + source = "git::https://github.com/canonical/notebook-operators//charms/jupyter-ui/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + config = var.jupyter_ui_config + revision = var.jupyter_ui_revision + channel = "latest/edge" +} + +module "katib_controller" { + source = "git::https://github.com/canonical/katib-operators//charms/katib-controller/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.katib_controller_revision + channel = "latest/edge" +} + +module "katib_db" { + # tflint-ignore: terraform_module_pinned_source + source = "git::https://github.com/canonical/mysql-k8s-operator//terraform?ref=eb6261e6fd1830d80aa4fa260d091c9110c24ba4" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + app_name = "katib-db" + channel = "8.0/stable" + # The following config is equivalent to "constraints: mem=2G" + config = { + profile-limit-memory = "2048" + } + storage_size = var.katib_db_size + revision = var.katib_db_revision +} + +module "katib_db_manager" { + source = "git::https://github.com/canonical/katib-operators//charms/katib-db-manager/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.katib_db_manager_revision + channel = "latest/edge" +} + +module "katib_ui" { + source = "git::https://github.com/canonical/katib-operators//charms/katib-ui/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.katib_ui_revision + channel = "latest/edge" +} + +module "kfp_api" { + source = "git::https://github.com/canonical/kfp-operators//charms/kfp-api/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.kfp_api_revision + config = { + object-store-bucket-name = var.kfp_api_object_store_bucket_name + } + channel = "latest/edge" +} + +module "kfp_db" { + # tflint-ignore: terraform_module_pinned_source + source = "git::https://github.com/canonical/mysql-k8s-operator//terraform?ref=eb6261e6fd1830d80aa4fa260d091c9110c24ba4" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + app_name = "kfp-db" + channel = "8.0/stable" + # The following config is equivalent to "constraints: mem=2G" + config = { + profile-limit-memory = "2048" + } + storage_size = var.kfp_db_size + revision = var.kfp_db_revision +} + +module "kfp_metadata_writer" { + source = "git::https://github.com/canonical/kfp-operators//charms/kfp-metadata-writer/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.kfp_metadata_writer_revision + channel = "latest/edge" +} + +module "kfp_persistence" { + source = "git::https://github.com/canonical/kfp-operators//charms/kfp-persistence/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.kfp_persistence_revision + channel = "latest/edge" +} + +module "kfp_profile_controller" { + source = "git::https://github.com/canonical/kfp-operators//charms/kfp-profile-controller/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.kfp_profile_controller_revision + channel = "latest/edge" +} + +module "kfp_schedwf" { + source = "git::https://github.com/canonical/kfp-operators//charms/kfp-schedwf/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.kfp_schedwf_revision + channel = "latest/edge" +} + +module "kfp_ui" { + source = "git::https://github.com/canonical/kfp-operators//charms/kfp-ui/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.kfp_ui_revision + channel = "latest/edge" +} + +module "kfp_viewer" { + source = "git::https://github.com/canonical/kfp-operators//charms/kfp-viewer/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.kfp_viewer_revision + channel = "latest/edge" +} + +module "kfp_viz" { + source = "git::https://github.com/canonical/kfp-operators//charms/kfp-viz/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.kfp_viz_revision + channel = "latest/edge" +} + +module "kserve_controller" { + source = "git::https://github.com/canonical/kserve-operators//charms/kserve-controller/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + config = { + deployment-mode = "rawdeployment", + http-proxy = var.http_proxy, + https-proxy = var.https_proxy, + no-proxy = var.no_proxy, + } + revision = var.kserve_controller_revision + channel = "latest/edge" +} + +module "kubeflow_dashboard" { + source = "git::https://github.com/canonical/kubeflow-dashboard-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + config = { + "registration-flow" : var.kubeflow_dashboard_registration_flow + } + revision = var.kubeflow_dashboard_revision + channel = "latest/edge" +} + +module "kubeflow_profiles" { + source = "git::https://github.com/canonical/kubeflow-profiles-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + config = { + "security-policy" : var.kubeflow_profiles_security_policy + "service-mesh-mode" : "istio-ambient" + "istio-gateway-service-account" : "istio-ingress-k8s-istio" + } + revision = var.kubeflow_profiles_revision + channel = "latest/edge" +} + +module "kubeflow_roles" { + source = "git::https://github.com/canonical/kubeflow-roles-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.kubeflow_roles_revision + channel = "latest/edge" +} + +module "kubeflow_trainer" { + count = var.kubeflow_trainer_v2 ? 1 : 0 + source = "git::https://github.com/canonical/training-operator//terraform?ref=main-v2" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.kubeflow_trainer_revision + channel = "latest/edge" +} + +module "kubeflow_volumes" { + source = "git::https://github.com/canonical/kubeflow-volumes-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.kubeflow_volumes_revision + channel = "latest/edge" +} + +module "metacontroller_operator" { + source = "git::https://github.com/canonical/metacontroller-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.metacontroller_operator_revision + channel = "latest/edge" +} + +module "mlmd" { + source = "git::https://github.com/canonical/mlmd-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + storage_directives = { + mlmd-data = var.mlmd_size + } + revision = var.mlmd_revision + channel = "latest/edge" +} + +module "minio" { + source = "git::https://github.com/canonical/minio-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + config = { + access-key = var.minio_access_key, + secret-key = var.minio_secret_key, + mode = var.minio_mode, + gateway-storage-service = var.minio_gateway_storage_service, + storage-service-endpoint = var.minio_storage_service_endpoint, + } + storage_directives = { + minio-data = var.minio_size + } + revision = var.minio_revision + channel = "latest/edge" +} + +module "oidc_gatekeeper" { + source = "git::https://github.com/canonical/oidc-gatekeeper-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + config = { + ca-bundle = var.oidc_gatekeeper_ca_bundle, + } + revision = var.oidc_gatekeeper_revision + channel = "latest/edge" +} + +module "pvcviewer_operator" { + source = "git::https://github.com/canonical/pvcviewer-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.pvcviewer_operator_revision + channel = "latest/edge" +} + +module "tensorboard_controller" { + source = "git::https://github.com/canonical/kubeflow-tensorboards-operator//charms/tensorboard-controller/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.tensorboard_controller_revision + channel = "latest/edge" +} + +module "tensorboards_web_app" { + source = "git::https://github.com/canonical/kubeflow-tensorboards-operator//charms/tensorboards-web-app/terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.tensorboards_web_app_revision + channel = "latest/edge" +} + +module "training_operator" { + source = "git::https://github.com/canonical/training-operator//terraform?ref=main" + model_name = var.create_model ? juju_model.kubeflow[0].name : local.model + revision = var.training_operator_revision + channel = "latest/edge" +} + +resource "juju_application" "istio_k8s" { + name = "istio-k8s" + model = var.create_model ? juju_model.kubeflow[0].name : local.model + trust = true + config = { + platform = var.istio_k8s_platform + } + + charm { + name = "istio-k8s" + channel = "2/stable" + revision = var.istio_k8s_revision + } +} + +resource "juju_application" "istio_ingress_k8s" { + name = "istio-ingress-k8s" + model = var.create_model ? juju_model.kubeflow[0].name : local.model + trust = true + + charm { + name = "istio-ingress-k8s" + channel = "2/stable" + revision = var.istio_ingress_k8s_revision + } +} + +resource "juju_application" "istio_beacon_k8s" { + name = "istio-beacon-k8s" + model = var.create_model ? juju_model.kubeflow[0].name : local.model + trust = true + + charm { + name = "istio-beacon-k8s" + channel = "2/stable" + revision = var.istio_beacon_k8s_revision + } +} diff --git a/modules/kubeflow-ambient/cos_configuration.tf b/modules/kubeflow-ambient/cos_configuration.tf new file mode 100644 index 0000000..1f471f1 --- /dev/null +++ b/modules/kubeflow-ambient/cos_configuration.tf @@ -0,0 +1,920 @@ +# TODO: Update to use a reusable module instead of defining +# a `juju_application` resource +resource "juju_application" "opentelemetry_collector_k8s" { + count = var.cos_configuration && var.existing_opentelemetry_collector_name == null ? 1 : 0 + charm { + name = "opentelemetry-collector-k8s" + channel = "2/stable" + revision = var.opentelemetry_collector_k8s_revision + } + model = var.create_model ? juju_model.kubeflow[0].name : local.model + name = "opentelemetry-collector-k8s-kubeflow" + storage_directives = { + persisted = var.opentelemetry_collector_k8s_size + } + trust = true + units = 1 +} + +resource "juju_integration" "argo_controller_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.argo_controller.app_name + endpoint = module.argo_controller.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "argo_controller_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.argo_controller.app_name + endpoint = module.argo_controller.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "argo_controller_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.argo_controller.app_name + endpoint = module.argo_controller.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "admission_webhook_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.admission_webhook.app_name + endpoint = module.admission_webhook.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "dex_auth_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.dex_auth.app_name + endpoint = module.dex_auth.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "dex_auth_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.dex_auth.app_name + endpoint = module.dex_auth.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "dex_auth_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.dex_auth.app_name + endpoint = module.dex_auth.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "envoy_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.envoy.app_name + endpoint = module.envoy.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "envoy_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.envoy.app_name + endpoint = module.envoy.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "envoy_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.envoy.app_name + endpoint = module.envoy.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "jupyter_controller_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.jupyter_controller.app_name + endpoint = module.jupyter_controller.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "jupyter_controller_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.jupyter_controller.app_name + endpoint = module.jupyter_controller.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "jupyter_controller_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.jupyter_controller.app_name + endpoint = module.jupyter_controller.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "jupyter_ui_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.jupyter_ui.app_name + endpoint = module.jupyter_ui.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "katib_controller_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.katib_controller.app_name + endpoint = module.katib_controller.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "katib_controller_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.katib_controller.app_name + endpoint = module.katib_controller.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "katib_controller_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.katib_controller.app_name + endpoint = module.katib_controller.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "katib_db_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.katib_db.app_name + endpoint = module.katib_db.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "katib_db_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.katib_db.app_name + endpoint = module.katib_db.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "katib_db_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.katib_db.app_name + endpoint = module.katib_db.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "katib_db_manager_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.katib_db_manager.app_name + endpoint = module.katib_db_manager.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "katib_ui_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.katib_ui.app_name + endpoint = module.katib_ui.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "kfp_api_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_api.app_name + endpoint = module.kfp_api.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "kfp_api_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_api.app_name + endpoint = module.kfp_api.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "kfp_api_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_api.app_name + endpoint = module.kfp_api.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + + +resource "juju_integration" "kfp_db_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_db.app_name + endpoint = module.kfp_db.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "kfp_db_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_db.app_name + endpoint = module.kfp_db.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "kfp_db_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_db.app_name + endpoint = module.kfp_db.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "kfp_metadata_writer_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_metadata_writer.app_name + endpoint = module.kfp_metadata_writer.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "kfp_persistence_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_persistence.app_name + endpoint = module.kfp_persistence.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "kfp_profile_controller_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_profile_controller.app_name + endpoint = module.kfp_profile_controller.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "kfp_schedwf_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_schedwf.app_name + endpoint = module.kfp_schedwf.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "kfp_ui_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_ui.app_name + endpoint = module.kfp_ui.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "kfp_viewer_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_viewer.app_name + endpoint = module.kfp_viewer.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "kfp_viz_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_viz.app_name + endpoint = module.kfp_viz.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "kserve_controller_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kserve_controller.app_name + endpoint = module.kserve_controller.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "kserve_controller_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kserve_controller.app_name + endpoint = module.kserve_controller.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "kubeflow_dashboard_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "kubeflow_dashboard_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "kubeflow_dashboard_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + + +resource "juju_integration" "kubeflow_profiles_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_profiles.app_name + endpoint = module.kubeflow_profiles.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "kubeflow_profiles_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_profiles.app_name + endpoint = module.kubeflow_profiles.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "kubeflow_trainer_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration && var.kubeflow_trainer_v2 ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_trainer[0].app_name + endpoint = module.kubeflow_trainer[0].provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "kubeflow_trainer_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration && var.kubeflow_trainer_v2 ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_trainer[0].app_name + endpoint = module.kubeflow_trainer[0].provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + + +resource "juju_integration" "kubeflow_volumes_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_volumes.app_name + endpoint = module.kubeflow_volumes.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "metacontroller_operator_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.metacontroller_operator.app_name + endpoint = module.metacontroller_operator.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "metacontroller_operator_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.metacontroller_operator.app_name + endpoint = module.metacontroller_operator.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "mlmd_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.mlmd.app_name + endpoint = module.mlmd.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "minio_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.minio.app_name + endpoint = module.minio.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "minio_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.minio.app_name + endpoint = module.minio.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "oidc_gatekeeper_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.oidc_gatekeeper.app_name + endpoint = module.oidc_gatekeeper.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "pvcviewer_operator_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.pvcviewer_operator.app_name + endpoint = module.pvcviewer_operator.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "pvcviewer_operator_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.pvcviewer_operator.app_name + endpoint = module.pvcviewer_operator.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "pvcviewer_operator_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.pvcviewer_operator.app_name + endpoint = module.pvcviewer_operator.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "tensorboard_controller_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.tensorboard_controller.app_name + endpoint = module.tensorboard_controller.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "tensorboard_controller_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.tensorboard_controller.app_name + endpoint = module.tensorboard_controller.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "tensorboards_web_app_opentelemetry_collector_k8s_grafana_logging" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.tensorboards_web_app.app_name + endpoint = module.tensorboards_web_app.requires.logging + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "receive-loki-logs" + } +} + +resource "juju_integration" "training_operator_opentelemetry_collector_k8s_grafana_dashboard" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.training_operator.app_name + endpoint = module.training_operator.provides.grafana_dashboard + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "grafana-dashboards-consumer" + } +} + +resource "juju_integration" "training_operator_opentelemetry_collector_k8s_metrics_endpoint" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.training_operator.app_name + endpoint = module.training_operator.provides.metrics_endpoint + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "metrics-endpoint" + } +} + +resource "juju_integration" "istio_beacon_k8s_opentelemetry_collector_k8s_service_mesh" { + count = var.cos_configuration ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = var.existing_opentelemetry_collector_name == null ? juju_application.opentelemetry_collector_k8s[count.index].name : var.existing_opentelemetry_collector_name + endpoint = "service-mesh" + } +} diff --git a/modules/kubeflow-ambient/integrations.tf b/modules/kubeflow-ambient/integrations.tf new file mode 100644 index 0000000..567d3f1 --- /dev/null +++ b/modules/kubeflow-ambient/integrations.tf @@ -0,0 +1,884 @@ + +resource "juju_integration" "argo_controller_minio" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.argo_controller.app_name + endpoint = module.argo_controller.requires.object_storage + } + + application { + name = module.minio.app_name + endpoint = module.minio.provides.object_storage + } +} + +resource "juju_integration" "dex_auth_oidc_gatekeeper_dex_oidc_config" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.dex_auth.app_name + endpoint = module.dex_auth.provides.dex_oidc_config + } + + application { + name = module.oidc_gatekeeper.app_name + endpoint = module.oidc_gatekeeper.requires.dex_oidc_config + } +} + +resource "juju_integration" "dex_auth_oidc_gatekeeper_oidc_client" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.dex_auth.app_name + endpoint = module.dex_auth.requires.oidc_client + } + + application { + name = module.oidc_gatekeeper.app_name + endpoint = module.oidc_gatekeeper.provides.oidc_client + } +} + +resource "juju_integration" "katib_db_manager_katib_controller_k8s_service_info" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.katib_db_manager.app_name + endpoint = module.katib_db_manager.provides.k8s_service_info + } + + application { + name = module.katib_controller.app_name + endpoint = module.katib_controller.requires.k8s_service_info + } +} + +resource "juju_integration" "katib_db_manager_katib_db_relational_db" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.katib_db_manager.app_name + endpoint = module.katib_db_manager.requires.relational_db + } + + application { + name = module.katib_db.app_name + endpoint = module.katib_db.provides.database + } +} + +resource "juju_integration" "kfp_api_kfp_db_database" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_api.app_name + endpoint = module.kfp_api.requires.relational_db + } + + application { + name = module.kfp_db.app_name + endpoint = module.kfp_db.provides.database + } +} + +resource "juju_integration" "kfp_api_kfp_persistence_database" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_api.app_name + endpoint = module.kfp_api.provides.kfp_api + } + + application { + name = module.kfp_persistence.app_name + endpoint = module.kfp_persistence.requires.kfp_api + } +} + +resource "juju_integration" "kfp_api_kfp_ui_database" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_api.app_name + endpoint = module.kfp_api.provides.kfp_api + } + + application { + name = module.kfp_ui.app_name + endpoint = module.kfp_ui.requires.kfp_api + } +} + +resource "juju_integration" "kfp_api_kfp_viz_database" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_api.app_name + endpoint = module.kfp_api.requires.kfp_viz + } + + application { + name = module.kfp_viz.app_name + endpoint = module.kfp_viz.provides.kfp_viz + } +} + +resource "juju_integration" "kfp_api_minio_object_storage" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_api.app_name + endpoint = module.kfp_api.requires.object_storage + } + + application { + name = module.minio.app_name + endpoint = module.minio.provides.object_storage + } +} + +resource "juju_integration" "kfp_profile_controller_minio_object_storage" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_profile_controller.app_name + endpoint = module.kfp_profile_controller.requires.object_storage + } + + application { + name = module.minio.app_name + endpoint = module.minio.provides.object_storage + } +} + +resource "juju_integration" "kfp_ui_minio_object_storage" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_ui.app_name + endpoint = module.kfp_ui.requires.object_storage + } + + application { + name = module.minio.app_name + endpoint = module.minio.provides.object_storage + } +} + +resource "juju_integration" "kubeflow_profiles_kubeflow_dashboard_kubeflow_profiles" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_profiles.app_name + endpoint = module.kubeflow_profiles.provides.kubeflow_profiles + } + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.requires.kubeflow_profiles + } +} + +resource "juju_integration" "kubeflow_dashboard_jupyter_ui_links" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.provides.links + } + + application { + name = module.jupyter_ui.app_name + endpoint = module.jupyter_ui.requires.dashboard_links + } +} + +resource "juju_integration" "kubeflow_dashboard_katib_ui_links" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.provides.links + } + + application { + name = module.katib_ui.app_name + endpoint = module.katib_ui.requires.dashboard_links + } +} + +resource "juju_integration" "kubeflow_dashboard_kfp_ui_links" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.provides.links + } + + application { + name = module.kfp_ui.app_name + endpoint = module.kfp_ui.requires.dashboard_links + } +} + +resource "juju_integration" "kubeflow_dashboard_kubeflow_trainer_links" { + count = var.kubeflow_trainer_v2 ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.provides.links + } + + application { + name = module.kubeflow_trainer[0].app_name + endpoint = module.kubeflow_trainer[0].requires.dashboard_links + } +} + +resource "juju_integration" "kubeflow_dashboard_kubeflow_volumes_links" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.provides.links + } + + application { + name = module.kubeflow_volumes.app_name + endpoint = module.kubeflow_volumes.requires.dashboard_links + } +} + +resource "juju_integration" "kubeflow_dashboard_tensorboards_web_app_links" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.provides.links + } + + application { + name = module.tensorboards_web_app.app_name + endpoint = module.tensorboards_web_app.requires.dashboard_links + } +} + +resource "juju_integration" "kubeflow_dashboard_training_operator_links" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.provides.links + } + + application { + name = module.training_operator.app_name + endpoint = module.training_operator.requires.dashboard_links + } +} + +resource "juju_integration" "mlmd_envoy_grpc" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.mlmd.app_name + endpoint = module.mlmd.provides.grpc + } + + application { + name = module.envoy.app_name + endpoint = module.envoy.requires.grpc + } +} + +resource "juju_integration" "mlmd_kfp_metadata_writer_grpc" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.mlmd.app_name + endpoint = module.mlmd.provides.grpc + } + + application { + name = module.kfp_metadata_writer.app_name + endpoint = module.kfp_metadata_writer.requires.grpc + } +} + +resource "juju_integration" "istio_beacon_k8s_admission_webhook" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.admission_webhook.app_name + endpoint = module.admission_webhook.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_dex_auth" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.dex_auth.app_name + endpoint = module.dex_auth.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_katib_controller" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.katib_controller.app_name + endpoint = module.katib_controller.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_katib_ui" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.katib_ui.app_name + endpoint = module.katib_ui.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_kfp_api" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.kfp_api.app_name + endpoint = module.kfp_api.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_kfp_persistence" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.kfp_persistence.app_name + endpoint = module.kfp_persistence.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_kfp_profile_controller" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.kfp_profile_controller.app_name + endpoint = module.kfp_profile_controller.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_kfp_schedwf" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.kfp_schedwf.app_name + endpoint = module.kfp_schedwf.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_kfp_ui" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.kfp_ui.app_name + endpoint = module.kfp_ui.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_kfp_viz" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.kfp_viz.app_name + endpoint = module.kfp_viz.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_kubeflow_dashboard" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_kubeflow_profiles" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.kubeflow_profiles.app_name + endpoint = module.kubeflow_profiles.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_kubeflow_volumes" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.kubeflow_volumes.app_name + endpoint = module.kubeflow_volumes.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_minio" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.minio.app_name + endpoint = module.minio.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_oidc_gatekeeper" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.oidc_gatekeeper.app_name + endpoint = module.oidc_gatekeeper.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_pvcviewer_operator" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.pvcviewer_operator.app_name + endpoint = module.pvcviewer_operator.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_tensorboard_controller" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.tensorboard_controller.app_name + endpoint = module.tensorboard_controller.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_tensorboards_web_app" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.tensorboards_web_app.app_name + endpoint = module.tensorboards_web_app.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_jupyter_controller" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.jupyter_controller.app_name + endpoint = module.jupyter_controller.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_jupyter_ui" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.jupyter_ui.app_name + endpoint = module.jupyter_ui.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_envoy" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.envoy.app_name + endpoint = module.envoy.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_kserve_controller" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.kserve_controller.app_name + endpoint = module.kserve_controller.requires.service_mesh + } +} + +resource "juju_integration" "istio_ingress_k8s_kubeflow_volumes" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "istio-ingress-route" + } + + application { + name = module.kubeflow_volumes.app_name + endpoint = module.kubeflow_volumes.requires.istio_ingress_route + } +} + +resource "juju_integration" "istio_ingress_k8s_kfp_ui" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "istio-ingress-route" + } + + application { + name = module.kfp_ui.app_name + endpoint = module.kfp_ui.requires.istio_ingress_route + } +} + +resource "juju_integration" "istio_ingress_k8s_envoy" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "istio-ingress-route" + } + + application { + name = module.envoy.app_name + endpoint = module.envoy.requires.istio_ingress_route + } +} + +resource "juju_integration" "istio_ingress_k8s_katib_ui" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "istio-ingress-route" + } + + application { + name = module.katib_ui.app_name + endpoint = module.katib_ui.requires.istio_ingress_route + } +} + +resource "juju_integration" "istio_ingress_k8s_kubeflow_dashboard" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "istio-ingress-route" + } + + application { + name = module.kubeflow_dashboard.app_name + endpoint = module.kubeflow_dashboard.requires.istio_ingress_route + } +} + +resource "juju_integration" "istio_ingress_k8s_jupyter_ui" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "istio-ingress-route" + } + + application { + name = module.jupyter_ui.app_name + endpoint = module.jupyter_ui.requires.istio_ingress_route + } +} + +resource "juju_integration" "istio_ingress_k8s_tensorboards_web_app" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "istio-ingress-route" + } + + application { + name = module.tensorboards_web_app.app_name + endpoint = module.tensorboards_web_app.requires.istio_ingress_route + } +} + +resource "juju_integration" "oidc_gatekeeper_istio_ingress_k8s_unauthenticated" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.oidc_gatekeeper.app_name + endpoint = module.oidc_gatekeeper.requires.istio_ingress_route_unauthenticated + } + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "istio-ingress-route-unauthenticated" + } +} + +resource "juju_integration" "oidc_gatekeeper_istio_ingress_k8s_forward_auth" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.oidc_gatekeeper.app_name + endpoint = module.oidc_gatekeeper.provides.forward_auth + } + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "forward-auth" + } +} + +resource "juju_integration" "dex_auth_istio_ingress_k8s_unauthenticated" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.dex_auth.app_name + endpoint = module.dex_auth.requires.istio_ingress_route_unauthenticated + } + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "istio-ingress-route-unauthenticated" + } +} + +resource "juju_integration" "istio_k8s_istio_ingress_k8s_ingress_config" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_k8s.name + endpoint = "istio-ingress-config" + } + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "istio-ingress-config" + } +} + +resource "juju_integration" "jupyter_controller_istio_ingress_k8s_gateway_metadata" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.jupyter_controller.app_name + endpoint = module.jupyter_controller.requires.gateway_metadata + } + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "gateway-metadata" + } +} + +resource "juju_integration" "pvcviewer_operator_istio_ingress_k8s_gateway_metadata" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.pvcviewer_operator.app_name + endpoint = module.pvcviewer_operator.requires.gateway_metadata + } + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "gateway-metadata" + } +} + +resource "juju_integration" "tensorboard_controller_istio_ingress_k8s_gateway_metadata" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.tensorboard_controller.app_name + endpoint = module.tensorboard_controller.requires.gateway_metadata + } + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "gateway-metadata" + } +} + +resource "juju_integration" "kserve_controller_istio_ingress_k8s_gateway_metadata" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kserve_controller.app_name + endpoint = module.kserve_controller.requires.gateway_metadata + } + + application { + name = juju_application.istio_ingress_k8s.name + endpoint = "gateway-metadata" + } +} + +resource "juju_integration" "kfp_api_kfp_persistence_grpc" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_api.app_name + endpoint = module.kfp_api.provides.kfp_api_grpc + } + + application { + name = module.kfp_persistence.app_name + endpoint = module.kfp_persistence.requires.kfp_api_grpc + } +} + +resource "juju_integration" "istio_beacon_k8s_training_operator" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.training_operator.app_name + endpoint = module.training_operator.requires.service_mesh + } +} + +resource "juju_integration" "istio_beacon_k8s_kubeflow_trainer" { + count = var.kubeflow_trainer_v2 ? 1 : 0 + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = juju_application.istio_beacon_k8s.name + endpoint = "service-mesh" + } + + application { + name = module.kubeflow_trainer[0].app_name + endpoint = module.kubeflow_trainer[0].requires.service_mesh + } +} + +resource "juju_integration" "kfp_api_kfp_schedwf_grpc" { + model = var.create_model ? juju_model.kubeflow[0].name : local.model + + application { + name = module.kfp_api.app_name + endpoint = module.kfp_api.provides.kfp_api_grpc + } + + application { + name = module.kfp_schedwf.app_name + endpoint = module.kfp_schedwf.requires.kfp_api_grpc + } +} diff --git a/modules/kubeflow-ambient/main.tf b/modules/kubeflow-ambient/main.tf new file mode 100644 index 0000000..b8996ea --- /dev/null +++ b/modules/kubeflow-ambient/main.tf @@ -0,0 +1,14 @@ +resource "juju_model" "kubeflow" { + count = var.create_model ? 1 : 0 + name = local.model + + config = { + juju-http-proxy = var.http_proxy + juju-https-proxy = var.https_proxy + juju-no-proxy = var.no_proxy + } +} + +locals { + model = "kubeflow" +} diff --git a/modules/kubeflow-ambient/outputs.tf b/modules/kubeflow-ambient/outputs.tf new file mode 100644 index 0000000..206bf0c --- /dev/null +++ b/modules/kubeflow-ambient/outputs.tf @@ -0,0 +1,32 @@ +output "dashboard_links_provider" { + value = { + app_name = module.kubeflow_dashboard.app_name, + provides = module.kubeflow_dashboard.provides, + } +} + +output "opentelemetry_collector_k8s" { + value = var.cos_configuration ? { + app_name = var.existing_opentelemetry_collector_name == null ? one(juju_application.opentelemetry_collector_k8s[*].name) : var.existing_opentelemetry_collector_name + provides = { + grafana_dashboards_provider = "grafana-dashboards-provider", + } + requires = { + send_loki_logs = "send-loki-logs", + send_remote_write = "send-remote-write", + } + } : null +} + +output "kserve_controller" { + value = { + app_name = module.kserve_controller.app_name, + provides = module.kserve_controller.provides, + requires = module.kserve_controller.requires, + } +} + +output "model" { + value = var.create_model ? one(juju_model.kubeflow[*].name) : local.model +} + diff --git a/modules/kubeflow-ambient/tests/conftest.py b/modules/kubeflow-ambient/tests/conftest.py new file mode 100644 index 0000000..d9e486a --- /dev/null +++ b/modules/kubeflow-ambient/tests/conftest.py @@ -0,0 +1,82 @@ +# tests/integration/conftest.py + +import jubilant +import pytest + +MODEL_NAME = "kubeflow" + +@pytest.fixture(scope="module") +def juju(request: pytest.FixtureRequest): + + def print_debug_log(juju_instance: jubilant.Juju): + if request.session.testsfailed: + print(f"[DEBUG] Fetching debug log for model: {juju_instance.model}") + log = juju_instance.debug_log(limit=1000) + print(log, end="") + + juju_instance = jubilant.Juju() + juju_instance.add_model(MODEL_NAME) + + try: + yield juju_instance + finally: + print_debug_log(juju_instance) + +def pytest_addoption(parser): + """Add CLI options to pytest.""" + parser.addoption( + "--istio-k8s-platform", + nargs="?", + const="", + default="microk8s", + type=str, + help="Platform for istio-k8s (e.g., microk8s, or empty string for Canonical K8s)", + ) + parser.addoption( + "--pss", + nargs="?", + choices=["privileged", "baseline"], + const="privileged", + default="privileged", + type=str, + help="Pod security standards enforced in Profiles' namespaces", + ) + parser.addoption( + "--db-size", + default="1G", + type=str, + help="Size to be used for the databases.", + ) + +@pytest.fixture(scope="module") +def db_sizes(request) -> list[str]: + """Terraform module customization for the db sizes.""" + size = request.config.getoption("--db-size") or "1G" + return [ + "-var", f"kfp_db_size={size}", + "-var", f"katib_db_size={size}", + "-var", f"opentelemetry_collector_k8s_size={size}", + ] + +@pytest.fixture(scope="module") +def pss(request) -> list[str]: + """Pod security standards enforced in Profiles' namespaces.""" + pss = request.config.getoption("--pss") + istio_k8s_platform = request.config.getoption("--istio-k8s-platform") + if istio_k8s_platform is None: + istio_k8s_platform = "microk8s" + return [ + "-var", + f"istio_k8s_platform={istio_k8s_platform}", + "-var", + f"kubeflow_profiles_security_policy={pss}", + ] + +@pytest.fixture(scope="module") +def tf_vars(request, db_sizes, pss) -> list[str]: + """Overall Terraform module customization.""" + return db_sizes + pss + [ + "-var", "create_model=false", + "-var", "cos_configuration=true", + "-var", "kubeflow_trainer_v2=true", + ] diff --git a/modules/kubeflow-ambient/tests/test_deployment.py b/modules/kubeflow-ambient/tests/test_deployment.py new file mode 100644 index 0000000..c71b366 --- /dev/null +++ b/modules/kubeflow-ambient/tests/test_deployment.py @@ -0,0 +1,74 @@ +import subprocess + +import aiohttp +import jubilant +import lightkube +import pytest +from lightkube.resources.core_v1 import Service + + +@pytest.fixture() +def lightkube_client() -> lightkube.Client: + client = lightkube.Client(field_manager="kubeflow") + return client + + +class TestCharm: + @pytest.mark.dependency() + async def test_apply_terraform_solution(self, juju: jubilant.Juju, tf_vars): + """Initialize and apply the kubeflow-ambient Terraform solution module.""" + subprocess.run(["terraform", "init"], check=True) + subprocess.run( + [ + "terraform", + "apply", + "-auto-approve", + ] + tf_vars, + check=True, + ) + + @pytest.mark.dependency(depends=["TestCharm::test_apply_terraform_solution"]) + async def test_assert_deployment(self, juju: jubilant.Juju, lightkube_client): + """ + Wait for the applications to become active and idle and verify its public URL access. + """ + + apps = list(juju.status().apps.keys()) + + # Remove opentelemetry-collector-k8s from the apps list because it remains + # `blocked` until it's related to one of the COS charms + apps.remove("opentelemetry-collector-k8s-kubeflow") + + juju.wait(lambda status: jubilant.all_active(status, *apps), timeout=3600) + + # Verify deployment by checking the public URL + url = get_public_url(lightkube_client, "kubeflow") + result_status, result_text = await fetch_response(url) + assert result_status == 200 + assert "Log in to Your Account" in result_text + assert "Email Address" in result_text + assert "Password" in result_text + + +def get_public_url(lightkube_client: lightkube.Client, bundle_name: str): + """Extracts public URL from service istio-ingress-k8s-istio.""" + istio_ingress_k8s_svc = lightkube_client.get( + Service, "istio-ingress-k8s-istio", namespace=bundle_name + ) + address = ( + istio_ingress_k8s_svc.status.loadBalancer.ingress[0].hostname + or istio_ingress_k8s_svc.status.loadBalancer.ingress[0].ip + ) + public_url = f"http://{address}" + return public_url + + +async def fetch_response(url, headers=None): + """Fetch provided URL and return (status, text).""" + result_status = 0 + result_text = "" + async with aiohttp.ClientSession() as session: + async with session.get(url=url, headers=headers) as response: + result_status = response.status + result_text = await response.text() + return result_status, str(result_text) diff --git a/modules/kubeflow-ambient/tox.ini b/modules/kubeflow-ambient/tox.ini new file mode 100644 index 0000000..d8758f3 --- /dev/null +++ b/modules/kubeflow-ambient/tox.ini @@ -0,0 +1,37 @@ +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +[tox] +skipsdist = True +envlist = tflint, test_deployment + +[vars] +tst_path = {toxinidir}/tests + +[testenv] +passenv = + KUBECONFIG + PYTHONPATH +setenv = + # Needed for juju cli to work correctly + HOME={env:HOME} + +[testenv:tflint] +description = Check Terraform code against coding style standards +allowlist_externals = + tflint +commands = + tflint --recursive + +[testenv:test_deployment] +commands = + pytest -v --tb native --asyncio-mode=auto tests/test_deployment.py --log-cli-level=INFO -s {posargs} +deps = + aiohttp + lightkube + tenacity + jubilant + juju<4.0.0 + pytest-dependency + pytest-asyncio +description = Test bundle deployment diff --git a/modules/kubeflow-ambient/variables.tf b/modules/kubeflow-ambient/variables.tf new file mode 100644 index 0000000..a4b49f3 --- /dev/null +++ b/modules/kubeflow-ambient/variables.tf @@ -0,0 +1,397 @@ +variable "argo_controller_bucket" { + description = "The name of the bucket to be used by Argo controller in the object store" + type = string + default = "mlpipeline" +} + +variable "cos_configuration" { + description = "Boolean value that enables COS configuration" + type = bool + default = false +} + +variable "create_model" { + description = "Allows to skip Juju model creation and re-use a model created in a higher level module" + type = bool + default = true +} + +variable "dex_connectors" { + description = "dex-auth connectors in yaml format" + type = string + default = "" +} + +variable "dex_static_username" { + description = "dex-auth static username value" + type = string + default = "" + sensitive = true +} + +variable "dex_static_password" { + description = "dex-auth static password" + type = string + default = "" + sensitive = true +} + +variable "existing_opentelemetry_collector_name" { + description = "Name of an existing opentelemetry-collector-k8s deployment" + type = string + default = null +} + +variable "opentelemetry_collector_k8s_size" { + description = "OpenTelemetry collector storage size" + type = string + default = "10G" +} + +variable "http_proxy" { + description = "Value of the http_proxy environment variable" + type = string + default = "" +} + +variable "https_proxy" { + description = "Value of the https_proxy environment variable" + type = string + default = "" +} + +variable "jupyter_ui_config" { + description = "Map of config values passed to jupyter-ui" + type = map(string) + default = {} +} + +variable "katib_db_size" { + description = "Katib database storage size" + type = string + default = "10G" +} + +variable "kfp_api_object_store_bucket_name" { + description = "The name of the bucket to be used by KFP API in the object store" + type = string + default = "mlpipeline" +} + +variable "kfp_db_size" { + description = "KFP database storage size" + type = string + default = "10G" +} + +variable "kubeflow_profiles_security_policy" { + description = "Security policy for pod security standards enforced in user workloads. Only `privileged` and `baseline` are supported" + type = string + default = "privileged" + + validation { + condition = contains(["baseline", "privileged"], var.kubeflow_profiles_security_policy) + error_message = "Valid values for var.kubeflow_profiles_security_policy are (baseline, privileged)" + } +} +variable "kubeflow_trainer_v2" { + description = "Boolean value that enables deployment of Kubeflow Trainer V2 (experimental)" + type = bool + default = false +} + +variable "minio_access_key" { + description = "MinIO access key" + type = string + default = "minio" + sensitive = true +} + +variable "minio_gateway_storage_service" { + description = "Gateway storage service configuration for MinIO when in 'gateway' mode" + type = string + default = "" +} + +variable "minio_mode" { + description = "MinIO mode, either 'server' or 'gateway'" + type = string + default = "server" +} + +variable "minio_secret_key" { + description = "MinIO secret key" + type = string + default = "" + sensitive = true +} + +variable "minio_size" { + description = "MinIO database storage size" + type = string + default = "10G" +} + +variable "minio_storage_service_endpoint" { + description = "MinIO storage service endpoint, required if minio_mode is 'gateway'" + type = string + default = "" +} + +variable "mlmd_size" { + description = "MLMD database storage size" + type = string + default = "10G" +} + +variable "oidc_gatekeeper_ca_bundle" { + description = "Custom CA to be trusted by OIDC gatekeeper" + type = string + default = "" +} + +variable "no_proxy" { + description = "Value of the no_proxy environment variable" + type = string + default = "" +} + +variable "public_url" { + description = "Public URL of Kubeflow for auth/OIDC" + type = string + default = "http://dex-auth.kubeflow.svc:5556" +} + +variable "admission_webhook_revision" { + description = "Charm revision for admission-webhook" + type = number + default = null +} + +variable "argo_controller_revision" { + description = "Charm revision for argo-controller" + type = number + default = null +} + +variable "dex_auth_revision" { + description = "Charm revision for dex-auth" + type = number + default = null +} + +variable "envoy_revision" { + description = "Charm revision for envoy" + type = number + default = null +} + +variable "opentelemetry_collector_k8s_revision" { + description = "Charm revision for opentelemetry-collector-k8s" + type = number + default = null +} + +variable "jupyter_controller_revision" { + description = "Charm revision for jupyter-controller" + type = number + default = null +} + +variable "jupyter_ui_revision" { + description = "Charm revision for jupyter-ui" + type = number + default = null +} + +variable "katib_controller_revision" { + description = "Charm revision for katib-controller" + type = number + default = null +} + +variable "katib_db_revision" { + description = "Charm revision for katib-db" + type = number + default = null +} + +variable "katib_db_manager_revision" { + description = "Charm revision for katib-db-manager" + type = number + default = null +} + +variable "katib_ui_revision" { + description = "Charm revision for katib-ui" + type = number + default = null +} + +variable "kfp_api_revision" { + description = "Charm revision for kfp-api" + type = number + default = null +} + +variable "kfp_db_revision" { + description = "Charm revision for kfp-db" + type = number + default = null +} + +variable "kfp_metadata_writer_revision" { + description = "Charm revision for kfp-metadata-writer" + type = number + default = null +} + +variable "kfp_persistence_revision" { + description = "Charm revision for kfp-persistence" + type = number + default = null +} + +variable "kfp_profile_controller_revision" { + description = "Charm revision for kfp-profile-controller" + type = number + default = null +} + +variable "kfp_schedwf_revision" { + description = "Charm revision for kfp-schedwf" + type = number + default = null +} + +variable "kfp_ui_revision" { + description = "Charm revision for kfp-ui" + type = number + default = null +} + +variable "kfp_viewer_revision" { + description = "Charm revision for kfp-viewer" + type = number + default = null +} + +variable "kfp_viz_revision" { + description = "Charm revision for kfp-viz" + type = number + default = null +} + +variable "kserve_controller_revision" { + description = "Charm revision for kserve-controller" + type = number + default = null +} + +variable "kubeflow_dashboard_revision" { + description = "Charm revision for kubeflow-dashboard" + type = number + default = null +} + +variable "kubeflow_dashboard_registration_flow" { + description = "Whether to enable the registration flow on sign-in for kubeflow-dashboard" + type = string + default = "true" +} + +variable "kubeflow_profiles_revision" { + description = "Charm revision for kubeflow-profiles" + type = number + default = null +} + +variable "kubeflow_roles_revision" { + description = "Charm revision for kubeflow-roles" + type = number + default = null +} + +variable "kubeflow_trainer_revision" { + description = "Charm revision for kubeflow-trainer" + type = number + default = null +} + +variable "kubeflow_volumes_revision" { + description = "Charm revision for kubeflow-volumes" + type = number + default = null +} + +variable "metacontroller_operator_revision" { + description = "Charm revision for metacontroller-operator" + type = number + default = null +} + +variable "mlmd_revision" { + description = "Charm revision for mlmd" + type = number + default = null +} + +variable "minio_revision" { + description = "Charm revision for minio" + type = number + default = null +} + +variable "oidc_gatekeeper_revision" { + description = "Charm revision for oidc-gatekeeper" + type = number + default = null +} + +variable "pvcviewer_operator_revision" { + description = "Charm revision for pvcviewer-operator" + type = number + default = null +} + +variable "tensorboard_controller_revision" { + description = "Charm revision for tensorboard-controller" + type = number + default = null +} + +variable "tensorboards_web_app_revision" { + description = "Charm revision for tensorboards-web-app" + type = number + default = null +} + +variable "training_operator_revision" { + description = "Charm revision for training-operator" + type = number + default = null +} + +variable "istio_k8s_revision" { + description = "Charm revision for istio-k8s" + type = number + default = null +} + +variable "istio_k8s_platform" { + description = "Platform for istio-k8s" + type = string + default = "microk8s" +} + +variable "istio_ingress_k8s_revision" { + description = "Charm revision for istio-ingress-k8s" + type = number + default = null +} + +variable "istio_beacon_k8s_revision" { + description = "Charm revision for istio-beacon-k8s" + type = number + default = null +} diff --git a/modules/kubeflow-ambient/versions.tf b/modules/kubeflow-ambient/versions.tf new file mode 100644 index 0000000..caaacf5 --- /dev/null +++ b/modules/kubeflow-ambient/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.6" + required_providers { + juju = { + source = "juju/juju" + version = ">= 0.14.0, < 1.0.0" + } + } +}