diff --git a/.catalog-onboard-pipeline.yaml b/.catalog-onboard-pipeline.yaml new file mode 100644 index 00000000..6379969f --- /dev/null +++ b/.catalog-onboard-pipeline.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +offerings: + - name: deploy-arch-ibm-logs-agent + kind: solution + catalog_id: 7df1e4ca-d54c-4fd0-82ce-3d13247308cd + offering_id: b3fb668c-6f9d-4db3-9073-9a677ccf4394 + variations: + - name: fully-configurable + mark_ready: true + install_type: fullstack + pre_validation: "tests/scripts/pre-validation-deploy-ocp-and-logs-instances.sh" + post_validation: "tests/scripts/post-validation-destroy-ocp-and-logs-instances.sh" diff --git a/.github/settings.yml b/.github/settings.yml index f72dd194..bbcfdfe9 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -22,7 +22,7 @@ repository: # Uncomment this description property # and update the description to the current repo description. - # description: "" + description: "Terraform module to deploy the IBM Cloud Logs Agent on a Cluster" # Use a comma-separated list of topics to set on the repo (ensure not to use any caps in the topic string). - topics: terraform, ibm-cloud, terraform-module, core-team, logs-agent, observability, agent, cloud-logs, logging-agent + topics: terraform, ibm-cloud, terraform-module, core-team, logs-agent, observability, logs, agent, cloud-logs, logging-agent diff --git a/.releaserc b/.releaserc index 708916f7..622ce915 100644 --- a/.releaserc +++ b/.releaserc @@ -10,6 +10,9 @@ }], ["@semantic-release/exec", { "successCmd": "echo \"SEMVER_VERSION=${nextRelease.version}\" >> $GITHUB_ENV" + }], + ["@semantic-release/exec", { + "publishCmd": "./ci/trigger-catalog-onboarding-pipeline.sh --version=v${nextRelease.version}" }] ] } diff --git a/.secrets.baseline b/.secrets.baseline index 0a4efa4a..7ae5bf9e 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "go.sum|^.secrets.baseline$", "lines": null }, - "generated_at": "2024-11-22T17:36:38Z", + "generated_at": "2025-03-13T13:24:51Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -76,18 +76,7 @@ "name": "TwilioKeyDetector" } ], - "results": { - "README.md": [ - { - "hashed_secret": "ff9ee043d85595eb255c05dfe32ece02a53efbb2", - "is_secret": false, - "is_verified": false, - "line_number": 74, - "type": "Secret Keyword", - "verified_result": null - } - ] - }, + "results": {}, "version": "0.13.1+ibm.62.dss", "word_list": { "file": null, diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..713bd26e --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +# Primary owner should be listed first in list of global owners, followed by any secondary owners +* @jor2 @Aashiq-J diff --git a/README.md b/README.md index 26b296aa..2b5525cd 100644 --- a/README.md +++ b/README.md @@ -1,129 +1,92 @@ - -# Terraform modules template project - - -[![Incubating (Not yet consumable)](https://img.shields.io/badge/status-Incubating%20(Not%20yet%20consumable)-red)](https://terraform-ibm-modules.github.io/documentation/#/badge-status) -[![latest release](https://img.shields.io/github/v/release/terraform-ibm-modules/terraform-ibm-logs-agent?logo=GitHub&sort=semver)](https://github.com/terraform-ibm-modules/terraform-ibm-logs-agent/releases/latest) + +# Terraform IBM Logs agent module + +[![Graduated (Supported)](https://img.shields.io/badge/Status-Graduated%20(Supported)-brightgreen)](https://terraform-ibm-modules.github.io/documentation/#/badge-status) [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) +[![latest release](https://img.shields.io/github/v/release/terraform-ibm-modules/terraform-ibm-logs-agent?logo=GitHub&sort=semver)](https://github.com/terraform-ibm-modules/terraform-ibm-logs-agent/releases/latest) [![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com/) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) - - -TODO: Replace this with a description of the modules in this repo. +This module deploys the following logs agent to an IBM Cloud Red Hat OpenShift Container Platform or Kubernetes cluster: +- [Logs agent](https://cloud.ibm.com/docs/cloud-logs?topic=cloud-logs-agent-about) - + ## Overview * [terraform-ibm-logs-agent](#terraform-ibm-logs-agent) * [Examples](./examples) - * [Advanced example](./examples/advanced) - * [Basic example](./examples/basic) + * [Logs agent on Kubernetes using CSE ingress endpoint with an apikey](./examples/logs-agent-iks) + * [Logs agent on OCP using VPE ingress endpoint with a Trusted Profile](./examples/logs-agent-ocp) * [Contributing](#contributing) - - - - - - ## terraform-ibm-logs-agent ### Usage - - ```hcl -terraform { - required_version = ">= 1.9.0" - required_providers { - ibm = { - source = "IBM-Cloud/ibm" - version = "X.Y.Z" # Lock into a provider version that satisfies the module constraints - } - } -} +# ############################################################################ +# Init cluster config for helm +# ############################################################################ -locals { - region = "us-south" +data "ibm_container_cluster_config" "cluster_config" { + # update this value with the Id of the cluster where the agent will be provisioned + cluster_name_id = "cluster_id" } +# ############################################################################ +# Config providers +# ############################################################################ + provider "ibm" { - ibmcloud_api_key = "XXXXXXXXXX" # replace with apikey value - region = local.region + # update this value with your IBM Cloud API key value + ibmcloud_api_key = "api key value" # pragma: allowlist secret } -module "module_template" { - source = "terraform-ibm-modules//ibm" - version = "X.Y.Z" # Replace "X.Y.Z" with a release version to lock into a specific release - region = local.region - name = "instance-name" - resource_group_id = "xxXXxxXXxXxXXXXxxXxxxXXXXxXXXXX" # Replace with the actual ID of resource group to use +provider "helm" { + kubernetes { + host = data.ibm_container_cluster_config.cluster_config.host + token = data.ibm_container_cluster_config.cluster_config.token + cluster_ca_certificate = data.ibm_container_cluster_config.cluster_config.ca_certificate + } } -``` - -### Required access policies - +# ############################################################################ +# Install Logs Agent +# ############################################################################ + +module "logs_agent_module" { + source = "terraform-ibm-modules/logs-agent/ibm" + # update this with your cluster id where the agent will be installed + cluster_id = "cluster id" + # update this with the Id of your IBM Cloud resource group + cluster_resource_group_id = "resource group id" + # Logs Agent variables + logs_agent_trusted_profile_id = "XXXXXXXX" + cloud_logs_ingress_endpoint = ".ingress.us-south.logs.cloud.ibm.com" + cloud_logs_ingress_port = 443 +} +``` - - - - - - - ### Requirements | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.9.0 | -| [ibm](#requirement\_ibm) | >= 1.71.2, < 2.0.0 | +| [helm](#requirement\_helm) | >= 2.15.0, <3.0.0 | +| [ibm](#requirement\_ibm) | >= 1.76.1, <2.0.0 | ### Modules @@ -133,28 +96,49 @@ No modules. | Name | Type | |------|------| -| [ibm_resource_instance.cos_instance](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/resource_instance) | resource | +| [helm_release.logs_agent](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [ibm_container_cluster.cluster](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/data-sources/container_cluster) | data source | +| [ibm_container_cluster_config.cluster_config](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/data-sources/container_cluster_config) | data source | +| [ibm_container_vpc_cluster.cluster](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/data-sources/container_vpc_cluster) | data source | ### Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [name](#input\_name) | A descriptive name used to identify the resource instance. | `string` | n/a | yes | -| [plan](#input\_plan) | The name of the plan type supported by service. | `string` | `"standard"` | no | -| [resource\_group\_id](#input\_resource\_group\_id) | The ID of the resource group where you want to create the service. | `string` | n/a | yes | -| [resource\_tags](#input\_resource\_tags) | List of resource tag to associate with the instance. | `list(string)` | `[]` | no | +| [cloud\_logs\_ingress\_endpoint](#input\_cloud\_logs\_ingress\_endpoint) | The host for IBM Cloud Logs ingestion. Ensure you use the ingress endpoint. See https://cloud.ibm.com/docs/cloud-logs?topic=cloud-logs-endpoints_ingress. | `string` | n/a | yes | +| [cloud\_logs\_ingress\_port](#input\_cloud\_logs\_ingress\_port) | The target port for the IBM Cloud Logs ingestion endpoint. The port must be 443 if you connect by using a VPE gateway, or port 3443 when you connect by using CSEs. | `number` | `3443` | no | +| [cluster\_config\_endpoint\_type](#input\_cluster\_config\_endpoint\_type) | The type of endpoint to use for the cluster config access: `default`, `private`, `vpe`, or `link`. The `default` value uses the default endpoint of the cluster. | `string` | `"default"` | no | +| [cluster\_id](#input\_cluster\_id) | The ID of the cluster to deploy the agent. | `string` | n/a | yes | +| [cluster\_resource\_group\_id](#input\_cluster\_resource\_group\_id) | The resource group ID of the cluster. | `string` | n/a | yes | +| [is\_vpc\_cluster](#input\_is\_vpc\_cluster) | Specify true if the target cluster for the agent is a VPC cluster, false if it is a classic cluster. | `bool` | `true` | no | +| [logs\_agent\_additional\_log\_source\_paths](#input\_logs\_agent\_additional\_log\_source\_paths) | The list of additional log sources. By default, the Logs agent collects logs from a single source at `/var/log/containers/*.log`. | `list(string)` | `[]` | no | +| [logs\_agent\_additional\_metadata](#input\_logs\_agent\_additional\_metadata) | The list of additional metadata fields to add to the routed logs. |
list(object({
key = optional(string)
value = optional(string)
}))
| `[]` | no | +| [logs\_agent\_chart](#input\_logs\_agent\_chart) | The name of the Helm chart to deploy. | `string` | `"logs-agent-helm"` | no | +| [logs\_agent\_chart\_location](#input\_logs\_agent\_chart\_location) | The location of the Logs agent helm chart. | `string` | `"oci://icr.io/ibm/observe"` | no | +| [logs\_agent\_chart\_version](#input\_logs\_agent\_chart\_version) | The version of the Helm chart to deploy. | `string` | `"1.5.1"` | no | +| [logs\_agent\_enable\_scc](#input\_logs\_agent\_enable\_scc) | Whether to enable creation of Security Context Constraints in Openshift. When installing on an OpenShift cluster, this setting is mandatory to configure permissions for pods within your cluster. | `bool` | `true` | no | +| [logs\_agent\_exclude\_log\_source\_paths](#input\_logs\_agent\_exclude\_log\_source\_paths) | The list of log sources to exclude. Specify the paths that the Logs agent ignores. | `list(string)` | `[]` | no | +| [logs\_agent\_iam\_api\_key](#input\_logs\_agent\_iam\_api\_key) | The IBM Cloud API key for the Logs agent to authenticate and communicate with the IBM Cloud Logs. It is required if `logs_agent_iam_mode` is set to `IAMAPIKey`. | `string` | `null` | no | +| [logs\_agent\_iam\_environment](#input\_logs\_agent\_iam\_environment) | IAM authentication Environment: `Production` or `PrivateProduction` or `Staging` or `PrivateStaging`. `Production` specifies the public endpoint & `PrivateProduction` specifies the private endpoint. | `string` | `"PrivateProduction"` | no | +| [logs\_agent\_iam\_mode](#input\_logs\_agent\_iam\_mode) | IAM authentication mode: `TrustedProfile` or `IAMAPIKey`. | `string` | `"TrustedProfile"` | no | +| [logs\_agent\_image\_version](#input\_logs\_agent\_image\_version) | The version of the Logs agent image to deploy. | `string` | `"1.5.1"` | no | +| [logs\_agent\_log\_source\_namespaces](#input\_logs\_agent\_log\_source\_namespaces) | The list of namespaces from which logs should be forwarded by agent. If namespaces are not listed, logs from all namespaces will be sent. | `list(string)` | `[]` | no | +| [logs\_agent\_name](#input\_logs\_agent\_name) | The name of the Logs agent. The name is used in all Kubernetes and Helm resources in the cluster. | `string` | `"logs-agent"` | no | +| [logs\_agent\_namespace](#input\_logs\_agent\_namespace) | The namespace where the Logs agent is deployed. The default value is `ibm-observe`. | `string` | `"ibm-observe"` | no | +| [logs\_agent\_resources](#input\_logs\_agent\_resources) | The resources configuration for cpu/memory/storage. [Learn More](https://cloud.ibm.com/docs/cloud-logs?topic=cloud-logs-agent-helm-template-clusters#agent-helm-template-clusters-chart-options-resources) |
object({
limits = object({
cpu = string
memory = string
})
requests = object({
cpu = string
memory = string
})
})
|
{
"limits": {
"cpu": "500m",
"memory": "3Gi"
},
"requests": {
"cpu": "100m",
"memory": "1Gi"
}
}
| no | +| [logs\_agent\_selected\_log\_source\_paths](#input\_logs\_agent\_selected\_log\_source\_paths) | The list of specific log sources paths. Logs will only be collected from the specified log source paths. If no paths are specified, it will send logs from `/var/log/containers`. | `list(string)` | `[]` | no | +| [logs\_agent\_tolerations](#input\_logs\_agent\_tolerations) | List of tolerations to apply to Logs agent. The default value means a pod will run on every node. |
list(object({
key = optional(string)
operator = optional(string)
value = optional(string)
effect = optional(string)
tolerationSeconds = optional(number)
}))
|
[
{
"operator": "Exists"
}
]
| no | +| [logs\_agent\_trusted\_profile\_id](#input\_logs\_agent\_trusted\_profile\_id) | The IBM Cloud trusted profile ID. Used only when `logs_agent_iam_mode` is set to `TrustedProfile`. The trusted profile must have an IBM Cloud Logs `Sender` role. | `string` | `null` | no | +| [wait\_till](#input\_wait\_till) | To avoid long wait times when you run your Terraform code, you can specify the stage when you want Terraform to mark the cluster resource creation as completed. Depending on what stage you choose, the cluster creation might not be fully completed and continues to run in the background. However, your Terraform code can continue to run without waiting for the cluster to be fully created. Supported args are `MasterNodeReady`, `OneWorkerNodeReady`, `IngressReady` and `Normal` | `string` | `"Normal"` | no | +| [wait\_till\_timeout](#input\_wait\_till\_timeout) | Timeout for wait\_till in minutes. | `number` | `90` | no | ### Outputs -| Name | Description | -|------|-------------| -| [account\_id](#output\_account\_id) | An alpha-numeric value identifying the account ID. | -| [crn](#output\_crn) | The CRN of the resource instance. | -| [guid](#output\_guid) | The GUID of the resource instance. | -| [id](#output\_id) | The unique identifier of the resource instance. | +No outputs. - + + ## Contributing You can report issues and request features for this module in GitHub issues in the module repo. See [Report an issue or request a feature](https://github.com/terraform-ibm-modules/.github/blob/main/.github/SUPPORT.md). diff --git a/cra-config.yaml b/cra-config.yaml index 9a4c7faa..d495e495 100644 --- a/cra-config.yaml +++ b/cra-config.yaml @@ -1,17 +1,6 @@ -# -# Developer tips: -# - CRA = Code Risk Analyzer (more info on CRA: https://cloud.ibm.com/docs/code-risk-analyzer-cli-plugin?topic=code-risk-analyzer-cli-plugin-cra-cli-plugin) -# - Multiple directories can be scanned by CRA. Ensure if there are any deployable architecture in the repository that they are all scanned -# - More info about supported configurations at https://github.com/terraform-ibm-modules/common-pipeline-assets/blob/main/.github/workflows/terraform-test-pipeline.md#cra-config-yaml -# - +# More info about this file at https://github.com/terraform-ibm-modules/common-pipeline-assets/blob/main/.github/workflows/terraform-test-pipeline.md#cra-config-yaml version: "v1" CRA_TARGETS: - - CRA_TARGET: "examples/advanced" # Target directory for CRA scan. If not provided, the CRA Scan will not be run. - CRA_IGNORE_RULES_FILE: "cra-tf-validate-ignore-rules.json" - PROFILE_ID: "fe96bd4d-9b37-40f2-b39f-a62760e326a3" # SCC profile ID (currently set to 'IBM Cloud Framework for Financial Services' '1.7.0' profile). - # SCC_INSTANCE_ID: "" # The SCC instance ID to use to download profile for CRA scan. If not provided, a default global value will be used. - # SCC_REGION: "" # The IBM Cloud region that the SCC instance is in. If not provided, a default global value will be used. - CRA_ENVIRONMENT_VARIABLES: # An optional map of environment variables for CRA, where the key is the variable name and value is the value. Useful for providing TF_VARs. - TF_VAR_prefix: "mock" - TF_VAR_region: "us-south" + - CRA_TARGET: "examples/logs-agent-ocp" # Target directory for CRA scan. If not provided, the CRA Scan will not be run. + CRA_IGNORE_RULES_FILE: "cra-tf-validate-ignore-rules.json" # CRA Ignore file to use. If not provided, it checks the repo root directory for `cra-tf-validate-ignore-rules.json` + PROFILE_ID: "fe96bd4d-9b37-40f2-b39f-a62760e326a3" # SCC profile ID (currently set to 'IBM Cloud Framework for Financial Services' '1.7.0' profile). diff --git a/cra-tf-validate-ignore-rules.json b/cra-tf-validate-ignore-rules.json index adbff6e0..45bef2ac 100644 --- a/cra-tf-validate-ignore-rules.json +++ b/cra-tf-validate-ignore-rules.json @@ -1,3 +1,23 @@ + { - "scc_rules": [] + "scc_rules": [ + { + "scc_rule_id": "rule-216e2449-27d7-4afc-929a-b66e196a9cf9", + "description": "Check whether Flow Logs for VPC are enabled", + "ignore_reason": "This rule is not relevant to the module itself, just the VPC resource that is used in the example that is scanned", + "is_valid": false + }, + { + "scc_rule_id": "rule-2325054a-c338-474a-9740-0b7034487e40", + "description:": "Check whether OpenShift clusters are accessible only by using private endpoints", + "ignore_reason": "This rule is not relevant to the module itself, just the cluster resource that is used in the example that is scanned", + "is_valid": false + }, + { + "scc_rule_id": "rule-64c0bea0-8760-4a6b-a56c-ee375a48961e", + "description:": "Check whether Virtual Private Cloud (VPC) has no public gateways attached", + "ignore_reason": "This rule is not relevant to the module itself, just the VPC resource that is used in the example that is scanned", + "is_valid": false + } + ] } diff --git a/examples/advanced/README.md b/examples/advanced/README.md deleted file mode 100644 index d52511a3..00000000 --- a/examples/advanced/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Advanced example - - - diff --git a/examples/advanced/main.tf b/examples/advanced/main.tf deleted file mode 100644 index 88360afb..00000000 --- a/examples/advanced/main.tf +++ /dev/null @@ -1,32 +0,0 @@ -######################################################################################################################## -# Resource group -######################################################################################################################## - -module "resource_group" { - source = "terraform-ibm-modules/resource-group/ibm" - version = "1.2.0" - # if an existing resource group is not set (null) create a new one using prefix - resource_group_name = var.resource_group == null ? "${var.prefix}-resource-group" : null - existing_resource_group_name = var.resource_group -} - -######################################################################################################################## -# COS -######################################################################################################################## - -# -# Developer tips: -# - Call the local module / modules in the example to show how they can be consumed -# - Include the actual module source as a code comment like below so consumers know how to consume from correct location -# - -module "cos" { - source = "../.." - # remove the above line and uncomment the below 2 lines to consume the module from the registry - # source = "terraform-ibm-modules//ibm" - # version = "X.Y.Z" # Replace "X.Y.Z" with a release version to lock into a specific release - name = "${var.prefix}-cos" - resource_group_id = module.resource_group.resource_group_id - resource_tags = var.resource_tags - plan = "cos-one-rate-plan" -} diff --git a/examples/advanced/outputs.tf b/examples/advanced/outputs.tf deleted file mode 100644 index 316751fb..00000000 --- a/examples/advanced/outputs.tf +++ /dev/null @@ -1,38 +0,0 @@ -############################################################################## -# Outputs -############################################################################## - -# -# Developer tips: -# - Include all relevant outputs from the modules being called in the example -# - -output "account_id" { - description = "An alpha-numeric value identifying the account ID." - value = module.cos.account_id -} - -output "guid" { - description = "The GUID of the resource instance." - value = module.cos.account_id -} - -output "id" { - description = "The unique identifier of the resource instance." - value = module.cos.id -} - -output "crn" { - description = "The CRN of the resource instance." - value = module.cos.crn -} - -output "resource_group_name" { - description = "Resource group name." - value = module.resource_group.resource_group_name -} - -output "resource_group_id" { - description = "Resource group ID." - value = module.resource_group.resource_group_id -} diff --git a/examples/advanced/variables.tf b/examples/advanced/variables.tf deleted file mode 100644 index d4603642..00000000 --- a/examples/advanced/variables.tf +++ /dev/null @@ -1,39 +0,0 @@ -######################################################################################################################## -# Input variables -######################################################################################################################## - -# -# Module developer tips: -# - Examples are references that consumers can use to see how the module can be consumed. They are not designed to be -# flexible re-usable solutions for general consumption, so do not expose any more variables here and instead hard -# code things in the example main.tf with code comments explaining the different configurations. -# - For the same reason as above, do not add default values to the example inputs. -# - -variable "ibmcloud_api_key" { - type = string - description = "The IBM Cloud API Key." - sensitive = true -} - -variable "region" { - type = string - description = "Region to provision all resources created by this example." -} - -variable "prefix" { - type = string - description = "A string value to prefix to all resources created by this example." -} - -variable "resource_group" { - type = string - description = "The name of an existing resource group to provision resources in to. If not set a new resource group will be created using the prefix variable." - default = null -} - -variable "resource_tags" { - type = list(string) - description = "List of resource tag to associate with all resource instances created by this example." - default = [] -} diff --git a/examples/advanced/version.tf b/examples/advanced/version.tf deleted file mode 100644 index ecfa9780..00000000 --- a/examples/advanced/version.tf +++ /dev/null @@ -1,16 +0,0 @@ -terraform { - required_version = ">= 1.9.0" - - # - # Developer tips: - # - Ensure that there is always 1 example locked into the lowest provider version of the range defined in the main - # module's version.tf (usually a basic example), and 1 example that will always use the latest provider version. - # - - required_providers { - ibm = { - source = "IBM-Cloud/ibm" - version = ">= 1.71.2, < 2.0.0" - } - } -} diff --git a/examples/basic/README.md b/examples/basic/README.md deleted file mode 100644 index e5977ae2..00000000 --- a/examples/basic/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Basic example - - - -An end-to-end basic example that will provision the following: -- A new resource group if one is not passed in. -- A new standard plan Cloud Object Storage instance using the root level module. diff --git a/examples/basic/main.tf b/examples/basic/main.tf deleted file mode 100644 index cf665dbd..00000000 --- a/examples/basic/main.tf +++ /dev/null @@ -1,31 +0,0 @@ -######################################################################################################################## -# Resource group -######################################################################################################################## - -module "resource_group" { - source = "terraform-ibm-modules/resource-group/ibm" - version = "1.2.0" - # if an existing resource group is not set (null) create a new one using prefix - resource_group_name = var.resource_group == null ? "${var.prefix}-resource-group" : null - existing_resource_group_name = var.resource_group -} - -######################################################################################################################## -# COS -######################################################################################################################## - -# -# Developer tips: -# - Call the local module / modules in the example to show how they can be consumed -# - include the actual module source as a code comment like below so consumers know how to consume from correct location -# - -module "cos" { - source = "../.." - # remove the above line and uncomment the below 2 lines to consume the module from the registry - # source = "terraform-ibm-modules//ibm" - # version = "X.Y.Z" # Replace "X.Y.Z" with a release version to lock into a specific release - name = "${var.prefix}-cos" - resource_group_id = module.resource_group.resource_group_id - resource_tags = var.resource_tags -} diff --git a/examples/basic/outputs.tf b/examples/basic/outputs.tf deleted file mode 100644 index 552db482..00000000 --- a/examples/basic/outputs.tf +++ /dev/null @@ -1,38 +0,0 @@ -######################################################################################################################## -# Outputs -######################################################################################################################## - -# -# Developer tips: -# - Include all relevant outputs from the modules being called in the example -# - -output "account_id" { - description = "An alpha-numeric value identifying the account ID." - value = module.cos.account_id -} - -output "guid" { - description = "The GUID of the resource instance." - value = module.cos.account_id -} - -output "id" { - description = "The unique identifier of the resource instance." - value = module.cos.id -} - -output "crn" { - description = "The CRN of the resource instance." - value = module.cos.crn -} - -output "resource_group_name" { - description = "Resource group name." - value = module.resource_group.resource_group_name -} - -output "resource_group_id" { - description = "Resource group ID." - value = module.resource_group.resource_group_id -} diff --git a/examples/basic/provider.tf b/examples/basic/provider.tf deleted file mode 100644 index 84b69850..00000000 --- a/examples/basic/provider.tf +++ /dev/null @@ -1,8 +0,0 @@ -######################################################################################################################## -# Provider config -######################################################################################################################## - -provider "ibm" { - ibmcloud_api_key = var.ibmcloud_api_key - region = var.region -} diff --git a/examples/basic/variables.tf b/examples/basic/variables.tf deleted file mode 100644 index d4603642..00000000 --- a/examples/basic/variables.tf +++ /dev/null @@ -1,39 +0,0 @@ -######################################################################################################################## -# Input variables -######################################################################################################################## - -# -# Module developer tips: -# - Examples are references that consumers can use to see how the module can be consumed. They are not designed to be -# flexible re-usable solutions for general consumption, so do not expose any more variables here and instead hard -# code things in the example main.tf with code comments explaining the different configurations. -# - For the same reason as above, do not add default values to the example inputs. -# - -variable "ibmcloud_api_key" { - type = string - description = "The IBM Cloud API Key." - sensitive = true -} - -variable "region" { - type = string - description = "Region to provision all resources created by this example." -} - -variable "prefix" { - type = string - description = "A string value to prefix to all resources created by this example." -} - -variable "resource_group" { - type = string - description = "The name of an existing resource group to provision resources in to. If not set a new resource group will be created using the prefix variable." - default = null -} - -variable "resource_tags" { - type = list(string) - description = "List of resource tag to associate with all resource instances created by this example." - default = [] -} diff --git a/examples/basic/version.tf b/examples/basic/version.tf deleted file mode 100644 index 401504c5..00000000 --- a/examples/basic/version.tf +++ /dev/null @@ -1,16 +0,0 @@ -terraform { - required_version = ">= 1.9.0" - - # - # Developer tips: - # - Ensure that there is always 1 example locked into the lowest provider version of the range defined in the main - # module's version.tf (usually a basic example), and 1 example that will always use the latest provider version. - # - - required_providers { - ibm = { - source = "IBM-Cloud/ibm" - version = "1.71.2" - } - } -} diff --git a/examples/logs-agent-iks/README.md b/examples/logs-agent-iks/README.md new file mode 100644 index 00000000..b13f2e14 --- /dev/null +++ b/examples/logs-agent-iks/README.md @@ -0,0 +1,11 @@ +# Logs agent on Kubernetes using CSE ingress endpoint with an apikey + +An example that shows how to deploy Logs agent in a Kubernetes cluster to send Logs directly to IBM Cloud Logs instance. + +The example provisions the following resources: +- A new resource group, if an existing one is not passed in. +- A basic VPC (if `is_vpc_cluster` is true). +- A Kubernetes cluster. +- A Service ID with `Sender` role to `logs` service and an apikey. +- An IBM Cloud Logs instance. +- Logs agent. diff --git a/examples/logs-agent-iks/main.tf b/examples/logs-agent-iks/main.tf new file mode 100644 index 00000000..4653c380 --- /dev/null +++ b/examples/logs-agent-iks/main.tf @@ -0,0 +1,156 @@ +############################################################################## +# Resource Group +############################################################################## + +module "resource_group" { + source = "terraform-ibm-modules/resource-group/ibm" + version = "1.1.6" + # if an existing resource group is not set (null) create a new one using prefix + resource_group_name = var.resource_group == null ? "${var.prefix}-resource-group" : null + existing_resource_group_name = var.resource_group +} + +############################################################################## +# Service ID with logs sender role + apikey +############################################################################## + +# As a `Sender`, you can send logs to your IBM Cloud Logs service instance - but not query or tail logs. This role is meant to be used by agent and routers sending logs. +module "iam_service_id" { + source = "terraform-ibm-modules/iam-service-id/ibm" + version = "1.2.0" + iam_service_id_name = "${var.prefix}-service-id" + iam_service_id_description = "Logs Agent service id" + iam_service_id_apikey_provision = true + iam_service_policies = { + logs = { + roles = ["Sender"] + resources = [{ + service = "logs" + }] + } + } +} + +############################################################################## +# Create VPC and IKS Cluster +############################################################################## + +resource "ibm_is_vpc" "example_vpc" { + count = var.is_vpc_cluster ? 1 : 0 + name = "${var.prefix}-vpc" + resource_group = module.resource_group.resource_group_id + tags = var.resource_tags +} + +resource "ibm_is_subnet" "testacc_subnet" { + count = var.is_vpc_cluster ? 1 : 0 + name = "${var.prefix}-subnet" + vpc = ibm_is_vpc.example_vpc[0].id + zone = "${var.region}-1" + total_ipv4_address_count = 256 + resource_group = module.resource_group.resource_group_id +} + +# Lookup the current default kube version +data "ibm_container_cluster_versions" "cluster_versions" {} +locals { + default_version = data.ibm_container_cluster_versions.cluster_versions.default_kube_version +} + +resource "ibm_container_vpc_cluster" "cluster" { + count = var.is_vpc_cluster ? 1 : 0 + name = var.prefix + vpc_id = ibm_is_vpc.example_vpc[0].id + kube_version = local.default_version + flavor = "bx2.4x16" + worker_count = "2" + force_delete_storage = true + wait_till = "IngressReady" + zones { + subnet_id = ibm_is_subnet.testacc_subnet[0].id + name = "${var.region}-1" + } + resource_group_id = module.resource_group.resource_group_id + tags = var.resource_tags +} + +resource "ibm_container_cluster" "cluster" { + #checkov:skip=CKV2_IBM_7:Public endpoint is required for testing purposes + count = var.is_vpc_cluster ? 0 : 1 + name = var.prefix + datacenter = var.datacenter + default_pool_size = 2 + hardware = "shared" + kube_version = local.default_version + force_delete_storage = true + machine_type = "b3c.4x16" + public_vlan_id = ibm_network_vlan.public_vlan[0].id + private_vlan_id = ibm_network_vlan.private_vlan[0].id + wait_till = "Normal" + resource_group_id = module.resource_group.resource_group_id + tags = var.resource_tags + + timeouts { + delete = "2h" + create = "3h" + } +} + +locals { + cluster_name_id = var.is_vpc_cluster ? ibm_container_vpc_cluster.cluster[0].id : ibm_container_cluster.cluster[0].id +} + +resource "ibm_network_vlan" "public_vlan" { + count = var.is_vpc_cluster ? 0 : 1 + datacenter = var.datacenter + type = "PUBLIC" +} + +resource "ibm_network_vlan" "private_vlan" { + count = var.is_vpc_cluster ? 0 : 1 + datacenter = var.datacenter + type = "PRIVATE" +} + +data "ibm_container_cluster_config" "cluster_config" { + cluster_name_id = local.cluster_name_id + resource_group_id = module.resource_group.resource_group_id +} + +# Sleep to allow RBAC sync on cluster +resource "time_sleep" "wait_operators" { + depends_on = [data.ibm_container_cluster_config.cluster_config] + create_duration = "45s" +} + +############################################################################## +# Cloud Logs Instance +############################################################################## + +module "cloud_logs" { + source = "terraform-ibm-modules/cloud-logs/ibm" + version = "1.0.0" + resource_group_id = module.resource_group.resource_group_id + plan = "standard" + region = var.region + instance_name = "${var.prefix}-cloud-logs" + resource_tags = var.resource_tags +} + +############################################################################## +# Logs Agent +############################################################################## + +module "logs_agent" { + source = "../.." + depends_on = [time_sleep.wait_operators] + cluster_id = local.cluster_name_id + is_vpc_cluster = var.is_vpc_cluster + cluster_resource_group_id = module.resource_group.resource_group_id + # Logs Agent + logs_agent_iam_mode = "IAMAPIKey" + logs_agent_iam_api_key = module.iam_service_id.service_id_apikey + cloud_logs_ingress_endpoint = module.cloud_logs.ingress_private_endpoint + cloud_logs_ingress_port = 3443 + logs_agent_enable_scc = false # only true for Openshift +} diff --git a/examples/logs-agent-iks/outputs.tf b/examples/logs-agent-iks/outputs.tf new file mode 100644 index 00000000..135186b7 --- /dev/null +++ b/examples/logs-agent-iks/outputs.tf @@ -0,0 +1,11 @@ +############################################################################## +# Outputs +############################################################################## + +#output "myoutput" { +# description = "Description of my output" +# value = "value" +# depends_on = [] +#} + +############################################################################## diff --git a/examples/logs-agent-iks/provider.tf b/examples/logs-agent-iks/provider.tf new file mode 100644 index 00000000..76346099 --- /dev/null +++ b/examples/logs-agent-iks/provider.tf @@ -0,0 +1,24 @@ +provider "ibm" { + ibmcloud_api_key = var.ibmcloud_api_key + region = var.region +} + +provider "helm" { + kubernetes { + host = data.ibm_container_cluster_config.cluster_config.host + token = data.ibm_container_cluster_config.cluster_config.token + cluster_ca_certificate = data.ibm_container_cluster_config.cluster_config.ca_certificate + } + # IBM Cloud credentials are required to authenticate to the helm repo + registry { + url = "oci://icr.io/ibm/observe/logs-agent-helm" + username = "iamapikey" + password = var.ibmcloud_api_key + } +} + +provider "kubernetes" { + host = data.ibm_container_cluster_config.cluster_config.host + token = data.ibm_container_cluster_config.cluster_config.token + cluster_ca_certificate = data.ibm_container_cluster_config.cluster_config.ca_certificate +} diff --git a/examples/logs-agent-iks/variables.tf b/examples/logs-agent-iks/variables.tf new file mode 100644 index 00000000..f582f21a --- /dev/null +++ b/examples/logs-agent-iks/variables.tf @@ -0,0 +1,41 @@ +variable "ibmcloud_api_key" { + type = string + description = "The IBM Cloud api token" + sensitive = true +} + +variable "prefix" { + type = string + description = "A prefix for the name of all resources that are created by this example" + default = "logs-agent-iks" +} + +variable "resource_group" { + type = string + description = "An existing resource group name to use for this example. If not specified, a new resource group is created." + default = null +} + +variable "resource_tags" { + type = list(string) + description = "A list of tags to add to the resources that are created." + default = [] +} + +variable "region" { + type = string + description = "The region where the resources are created." + default = "au-syd" +} + +variable "is_vpc_cluster" { + type = bool + description = "Specify true if the target cluster for the logs agent is a VPC cluster, false if it is classic cluster." + default = true +} + +variable "datacenter" { + type = string + description = "If creating a classic cluster, the data center where the cluster is created" + default = "syd01" +} diff --git a/examples/logs-agent-iks/version.tf b/examples/logs-agent-iks/version.tf new file mode 100644 index 00000000..8033b514 --- /dev/null +++ b/examples/logs-agent-iks/version.tf @@ -0,0 +1,26 @@ +terraform { + required_version = ">= 1.9.0" + + # Ensure that there is always 1 example locked into the lowest provider version of the range defined in the main + # module's version.tf (this example), and 1 example that will always use the latest provider version (logs-agent-ocp). + required_providers { + ibm = { + source = "ibm-cloud/ibm" + version = "1.76.1" + } + helm = { + source = "hashicorp/helm" + version = "2.15.0" + } + # The kubernetes provider is not actually required by the module itself, just this example, so OK to use ">=" here instead of locking into a version + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.16.1" + } + # The time provider is not actually required by the module itself, just this example, so OK to use ">=" here instead of locking into a version + time = { + source = "hashicorp/time" + version = ">= 0.9.1" + } + } +} diff --git a/examples/logs-agent-ocp/README.md b/examples/logs-agent-ocp/README.md new file mode 100644 index 00000000..88b76942 --- /dev/null +++ b/examples/logs-agent-ocp/README.md @@ -0,0 +1,13 @@ +# Logs agent on OCP using VPE ingress endpoint with a Trusted Profile + +An example that shows how to deploy Logs Routing agent in an Red Hat OpenShift container platform cluster to send Logs directly to IBM Cloud Logs instance respectively. + +The example provisions the following resources: + +- A new resource group, if an existing one is not passed in. +- A basic VPC. +- A Red Hat OpenShift Container Platform VPC cluster. +- A Trusted Profile with `Sender` role to `logs` service. +- An IBM Cloud Logs instance. +- A Virtual Private Endpoint for Cloud Logs. +- Logs agent. diff --git a/examples/logs-agent-ocp/main.tf b/examples/logs-agent-ocp/main.tf new file mode 100644 index 00000000..e2ac676c --- /dev/null +++ b/examples/logs-agent-ocp/main.tf @@ -0,0 +1,203 @@ +############################################################################## +# Resource Group +############################################################################## + +module "resource_group" { + source = "terraform-ibm-modules/resource-group/ibm" + version = "1.1.6" + # if an existing resource group is not set (null) create a new one using prefix + resource_group_name = var.resource_group == null ? "${var.prefix}-resource-group" : null + existing_resource_group_name = var.resource_group +} + +############################################################################## +# Trusted Profile +############################################################################## + +locals { + logs_agent_namespace = "ibm-observe" + logs_agent_name = "logs-agent" +} + + +module "trusted_profile" { + source = "terraform-ibm-modules/trusted-profile/ibm" + version = "1.0.5" + trusted_profile_name = "${var.prefix}-profile" + trusted_profile_description = "Logs agent Trusted Profile" + # As a `Sender`, you can send logs to your IBM Cloud Logs service instance - but not query or tail logs. This role is meant to be used by agent and routers sending logs. + trusted_profile_policies = [{ + roles = ["Sender"] + resources = [{ + service = "logs" + }] + }] + # Set up fine-grained authorization for `logs-agent` running in ROKS cluster in `ibm-observe` namespace. + trusted_profile_links = [{ + cr_type = "ROKS_SA" + links = [{ + crn = module.ocp_base.cluster_crn + namespace = local.logs_agent_namespace + name = local.logs_agent_name + }] + } + ] +} + +######################################################################################################################## +# VPC + Subnet + Public Gateway +# +# NOTE: This is a very simple VPC with single subnet in a single zone with a public gateway enabled, that will allow +# all traffic ingress/egress by default. +# For production use cases this would need to be enhanced by adding more subnets and zones for resiliency, and +# ACLs/Security Groups for network security. +######################################################################################################################## + +resource "ibm_is_vpc" "vpc" { + name = "${var.prefix}-vpc" + resource_group = module.resource_group.resource_group_id + address_prefix_management = "auto" + tags = var.resource_tags +} + +resource "ibm_is_public_gateway" "gateway" { + name = "${var.prefix}-gateway-1" + vpc = ibm_is_vpc.vpc.id + resource_group = module.resource_group.resource_group_id + zone = "${var.region}-1" +} + +resource "ibm_is_subnet" "subnet_zone_1" { + name = "${var.prefix}-subnet-1" + vpc = ibm_is_vpc.vpc.id + resource_group = module.resource_group.resource_group_id + zone = "${var.region}-1" + total_ipv4_address_count = 256 + public_gateway = ibm_is_public_gateway.gateway.id +} + +######################################################################################################################## +# OCP VPC cluster (single zone) +######################################################################################################################## + +locals { + cluster_vpc_subnets = { + default = [ + { + id = ibm_is_subnet.subnet_zone_1.id + cidr_block = ibm_is_subnet.subnet_zone_1.ipv4_cidr_block + zone = ibm_is_subnet.subnet_zone_1.zone + } + ] + } + + worker_pools = [ + { + subnet_prefix = "default" + pool_name = "default" # ibm_container_vpc_cluster automatically names default pool "default" (See https://github.com/IBM-Cloud/terraform-provider-ibm/issues/2849) + machine_type = "bx2.4x16" + operating_system = "REDHAT_8_64" + workers_per_zone = 2 # minimum of 2 is allowed when using single zone + } + ] +} + +module "ocp_base" { + source = "terraform-ibm-modules/base-ocp-vpc/ibm" + version = "3.41.3" + resource_group_id = module.resource_group.resource_group_id + region = var.region + tags = var.resource_tags + cluster_name = var.prefix + force_delete_storage = true + vpc_id = ibm_is_vpc.vpc.id + vpc_subnets = local.cluster_vpc_subnets + ocp_version = var.ocp_version + worker_pools = local.worker_pools + access_tags = var.access_tags + ocp_entitlement = var.ocp_entitlement +} + +data "ibm_container_cluster_config" "cluster_config" { + cluster_name_id = module.ocp_base.cluster_id + resource_group_id = module.resource_group.resource_group_id +} + +############################################################################## +# Cloud Logs Instance +############################################################################## + +module "cloud_logs" { + source = "terraform-ibm-modules/cloud-logs/ibm" + version = "1.0.0" + resource_group_id = module.resource_group.resource_group_id + plan = "standard" + region = var.region + instance_name = "${var.prefix}-cloud-logs" + resource_tags = var.resource_tags +} + +data "ibm_is_security_groups" "vpc_security_groups" { + depends_on = [module.ocp_base] + vpc_id = ibm_is_vpc.vpc.id +} + +# The below code creates a VPE for Cloud logs in the provisioned VPC which allows the agent to access the private Cloud Logs Ingress endpoint. +module "vpe" { + source = "terraform-ibm-modules/vpe-gateway/ibm" + version = "4.3.0" + region = var.region + prefix = var.prefix + vpc_id = ibm_is_vpc.vpc.id + vpc_name = "${var.prefix}-vpc" + subnet_zone_list = [ + { + id = ibm_is_subnet.subnet_zone_1.id + name = ibm_is_subnet.subnet_zone_1.name + zone = ibm_is_subnet.subnet_zone_1.zone + } + ] + resource_group_id = module.resource_group.resource_group_id + security_group_ids = [for group in data.ibm_is_security_groups.vpc_security_groups.security_groups : group.id if group.name == "kube-${module.ocp_base.cluster_id}"] # Select only security group attached to the Cluster + cloud_service_by_crn = [ + { + crn = module.cloud_logs.crn + service_name = "logs" + } + ] + service_endpoints = "private" +} + +############################################################################## +# Logs Agent +############################################################################## + +module "logs_agent" { + source = "../.." + depends_on = [module.vpe] + cluster_id = module.ocp_base.cluster_id + cluster_resource_group_id = module.resource_group.resource_group_id + # Logs agent + logs_agent_trusted_profile_id = module.trusted_profile.trusted_profile.id + logs_agent_namespace = local.logs_agent_namespace + logs_agent_name = local.logs_agent_name + cloud_logs_ingress_endpoint = module.cloud_logs.ingress_private_endpoint + cloud_logs_ingress_port = 443 + # example of how to add additional metadata to the logs agent + logs_agent_additional_metadata = [{ + key = "cluster_id" + value = module.ocp_base.cluster_id + }] + logs_agent_resources = { + limits = { + cpu = "500m" + memory = "3Gi" + } + requests = { + cpu = "100m" + memory = "1Gi" + } + } + # example of how to add additional log source path + logs_agent_additional_log_source_paths = ["/logs/*.log"] +} diff --git a/examples/logs-agent-ocp/outputs.tf b/examples/logs-agent-ocp/outputs.tf new file mode 100644 index 00000000..2bb20333 --- /dev/null +++ b/examples/logs-agent-ocp/outputs.tf @@ -0,0 +1,12 @@ + +############################################################################## +# Outputs +############################################################################## + +#output "myoutput" { +# description = "Description of my output" +# value = "value" +# depends_on = [] +#} + +############################################################################## diff --git a/examples/logs-agent-ocp/provider.tf b/examples/logs-agent-ocp/provider.tf new file mode 100644 index 00000000..76346099 --- /dev/null +++ b/examples/logs-agent-ocp/provider.tf @@ -0,0 +1,24 @@ +provider "ibm" { + ibmcloud_api_key = var.ibmcloud_api_key + region = var.region +} + +provider "helm" { + kubernetes { + host = data.ibm_container_cluster_config.cluster_config.host + token = data.ibm_container_cluster_config.cluster_config.token + cluster_ca_certificate = data.ibm_container_cluster_config.cluster_config.ca_certificate + } + # IBM Cloud credentials are required to authenticate to the helm repo + registry { + url = "oci://icr.io/ibm/observe/logs-agent-helm" + username = "iamapikey" + password = var.ibmcloud_api_key + } +} + +provider "kubernetes" { + host = data.ibm_container_cluster_config.cluster_config.host + token = data.ibm_container_cluster_config.cluster_config.token + cluster_ca_certificate = data.ibm_container_cluster_config.cluster_config.ca_certificate +} diff --git a/examples/logs-agent-ocp/variables.tf b/examples/logs-agent-ocp/variables.tf new file mode 100644 index 00000000..2abf3487 --- /dev/null +++ b/examples/logs-agent-ocp/variables.tf @@ -0,0 +1,47 @@ +variable "ibmcloud_api_key" { + type = string + description = "The IBM Cloud api token" + sensitive = true +} + +variable "prefix" { + type = string + description = "A prefix for the name of all resources that are created by this example" + default = "logs-agent-ocp" +} + +variable "resource_group" { + type = string + description = "An existing resource group name to use for this example. If not specified, a new resource group is created." + default = null +} + +variable "resource_tags" { + type = list(string) + description = "A list of tags to add to the resources that are created." + default = [] +} + +variable "access_tags" { + type = list(string) + description = "Optional list of access management tags to add to resources that are created" + default = [] +} + +variable "region" { + type = string + description = "The region where the resources are created." + default = "au-syd" +} + +variable "ocp_version" { + type = string + description = "Version of the OCP cluster to provision" + default = null +} + +variable "ocp_entitlement" { + type = string + description = "Value that is applied to the entitlements for OCP cluster provisioning" + default = null +} diff --git a/examples/logs-agent-ocp/version.tf b/examples/logs-agent-ocp/version.tf new file mode 100644 index 00000000..895b5a46 --- /dev/null +++ b/examples/logs-agent-ocp/version.tf @@ -0,0 +1,21 @@ + +terraform { + required_version = ">= 1.9.0" + + # Ensure that there is always 1 example locked into the lowest provider version of the range defined in the main + # module's version.tf (logs-agent-iks), and 1 example that will always use the latest provider version (this exammple). + required_providers { + ibm = { + source = "ibm-cloud/ibm" + version = "1.76.1" + } + helm = { + source = "hashicorp/helm" + version = ">= 2.15.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.16.1" + } + } +} diff --git a/ibm_catalog.json b/ibm_catalog.json new file mode 100644 index 00000000..b5b3389d --- /dev/null +++ b/ibm_catalog.json @@ -0,0 +1,229 @@ +{ + "products": [ + { + "label": "Cloud automation for Logs agent", + "name": "deploy-arch-ibm-logs-agent", + "product_kind": "solution", + "tags": [ + "ibm_created", + "logging", + "logging_monitoring", + "terraform", + "target_terraform", + "solution" + ], + "keywords": [ + "logs", + "agent", + "IaC", + "infrastructure as code", + "terraform", + "solution" + ], + "provider_name": "IBM", + "short_description": "Deploys IBM Logs Agent to a cluster", + "long_description": "Solution that support deploying IBM Logs Agent for logging.", + "offering_docs_url": "https://github.com/terraform-ibm-modules/terraform-ibm-logs-agent/blob/main/solutions/fully-configurable/README.md", + "offering_icon_url": "https://raw.githubusercontent.com/terraform-ibm-modules/terraform-ibm-logs-agent/refs/heads/main/images/logs-icon.svg", + "flavors": [ + { + "label": "Fully Configurable", + "name": "fully-configurable", + "working_directory": "solutions/fully-configurable", + "iam_permissions": [ + { + "service_name": "containers-kubernetes", + "role_crns": [ + "crn:v1:bluemix:public:iam::::serviceRole:Manager", + "crn:v1:bluemix:public:iam::::role:Viewer" + ] + } + ], + "architecture": { + "features": [ + { + "title": "Works with any Openshift or k8s cluster", + "description": "Yes" + }, + { + "title": "Deploys the IBM Logs Agent on an existing cluster.", + "description": "Yes" + } + ], + "diagrams": [ + { + "diagram": { + "url": "https://raw.githubusercontent.com/terraform-ibm-modules/terraform-ibm-logs-agent/refs/heads/main/reference-architecture/deployable-architecture-logs-agent.svg", + "caption": "Logs Agent on a cluster", + "type": "image/svg+xml" + }, + "description": "Logs Agent deployed on a cluster." + } + ] + }, + "configuration": [ + { + "key": "ibmcloud_api_key", + "required": true + }, + { + "key": "cluster_id", + "custom_config": { + "type": "cluster_var", + "grouping": "deployment", + "original_grouping": "deployment" + }, + "required": true + }, + { + "key": "cluster_resource_group_id", + "required": true + }, + { + "key": "is_vpc_cluster", + "required": true + }, + { + "key": "is_ocp_cluster", + "required": true + }, + { + "key": "cloud_logs_ingress_endpoint", + "required": true + }, + { + "key": "cloud_logs_ingress_port", + "required": true + }, + { + "key": "logs_agent_trusted_profile_id", + "required": true + }, + { + "key": "logs_agent_chart" + }, + { + "key": "logs_agent_chart_location" + }, + { + "key": "logs_agent_chart_version" + }, + { + "key": "logs_agent_image_version" + }, + { + "key": "logs_agent_resources" + }, + { + "key": "logs_agent_additional_log_source_paths" + }, + { + "key": "logs_agent_additional_metadata" + }, + { + "key": "logs_agent_exclude_log_source_paths" + }, + { + "key": "logs_agent_iam_api_key" + }, + { + "key": "logs_agent_iam_environment", + "options": [ + { + "displayname": "Production", + "value": "Production" + }, + { + "displayname": "Private Production", + "value": "PrivateProduction" + }, + { + "displayname": "Staging", + "value": "Staging" + }, + { + "displayname": "Private Staging", + "value": "PrivateStaging" + } + ] + }, + { + "key": "logs_agent_iam_mode", + "options": [ + { + "displayname": "Trusted Profile", + "value": "TrustedProfile" + }, + { + "displayname": "IAM API Key", + "value": "IAMAPIKey" + } + ] + }, + { + "key": "logs_agent_log_source_namespaces" + }, + { + "key": "logs_agent_name" + }, + { + "key": "logs_agent_namespace" + }, + { + "key": "logs_agent_selected_log_source_paths" + }, + { + "key": "logs_agent_tolerations" + }, + { + "key": "cluster_config_endpoint_type", + "options": [ + { + "displayname": "Default", + "value": "default" + }, + { + "displayname": "Private", + "value": "private" + }, + { + "displayname": "VPE", + "value": "vpe" + }, + { + "displayname": "Link", + "value": "link" + } + ] + }, + { + "key": "wait_till", + "options": [ + { + "displayname": "Master Node Ready", + "value": "MasterNodeReady" + }, + { + "displayname": "One Worker Node Ready", + "value": "OneWorkerNodeReady" + }, + { + "displayname": "Ingress Ready", + "value": "IngressReady" + }, + { + "displayname": "Normal", + "value": "Normal" + } + ] + }, + { + "key": "wait_till_timeout" + } + ], + "install_type": "fullstack" + } + ] + } + ] +} diff --git a/images/logs-icon.svg b/images/logs-icon.svg new file mode 100644 index 00000000..77079220 --- /dev/null +++ b/images/logs-icon.svg @@ -0,0 +1 @@ + diff --git a/kubeconfig/.gitignore b/kubeconfig/.gitignore new file mode 100644 index 00000000..632a28fb --- /dev/null +++ b/kubeconfig/.gitignore @@ -0,0 +1,6 @@ +# Ignore everything +* + +# But not these files... +!.gitignore +!README.md diff --git a/kubeconfig/README.md b/kubeconfig/README.md new file mode 100644 index 00000000..dff9dd4a --- /dev/null +++ b/kubeconfig/README.md @@ -0,0 +1,2 @@ +This directory must exist in source control so the `ibm_container_cluster_config` data lookup can use it to place the +config.yml used to connect to a kubernetes cluster (See https://github.ibm.com/GoldenEye/issues/issues/552). diff --git a/main.tf b/main.tf index b6b879e2..e18753ed 100644 --- a/main.tf +++ b/main.tf @@ -1,13 +1,132 @@ -# -# Developer tips: -# - Below code should be replaced with the code for the root level module -# - -resource "ibm_resource_instance" "cos_instance" { - name = var.name - resource_group_id = var.resource_group_id - service = "cloud-object-storage" - plan = var.plan - location = "global" - tags = var.resource_tags +# Lookup cluster name from ID. The is_vpc_cluster variable defines whether to use the VPC data block or the Classic data block +data "ibm_container_vpc_cluster" "cluster" { + count = var.is_vpc_cluster ? 1 : 0 + name = var.cluster_id + resource_group_id = var.cluster_resource_group_id + wait_till = var.wait_till + wait_till_timeout = var.wait_till_timeout +} + +data "ibm_container_cluster" "cluster" { + count = var.is_vpc_cluster ? 0 : 1 + name = var.cluster_id + resource_group_id = var.cluster_resource_group_id + wait_till = var.wait_till + wait_till_timeout = var.wait_till_timeout +} + +# Download cluster config which is required to connect to cluster +data "ibm_container_cluster_config" "cluster_config" { + cluster_name_id = var.is_vpc_cluster ? data.ibm_container_vpc_cluster.cluster[0].name : data.ibm_container_cluster.cluster[0].name + resource_group_id = var.cluster_resource_group_id + config_dir = "${path.module}/kubeconfig" + endpoint_type = var.cluster_config_endpoint_type != "default" ? var.cluster_config_endpoint_type : null # null value represents default +} + +locals { + logs_agent_selected_log_source_paths = distinct(concat([for namespace in var.logs_agent_log_source_namespaces : "/var/log/containers/*_${namespace}_*.log"], var.logs_agent_selected_log_source_paths)) + logs_agent_iam_api_key = var.logs_agent_iam_api_key != null ? var.logs_agent_iam_api_key : "" + logs_agent_trusted_profile_id = var.logs_agent_trusted_profile_id != null ? var.logs_agent_trusted_profile_id : "" + cloud_logs_ingress_endpoint = var.cloud_logs_ingress_endpoint != null ? var.cloud_logs_ingress_endpoint : "" + logs_agent_additional_metadata = length(var.logs_agent_additional_metadata) > 0 ? merge([ + for metadata in var.logs_agent_additional_metadata : { + (metadata.key) = metadata.value + }]...) : {} # DO NOT REMOVE "...", it is used to convert list of objects into a single object + cluster_name = var.is_vpc_cluster ? data.ibm_container_vpc_cluster.cluster[0].resource_name : data.ibm_container_cluster.cluster[0].resource_name # Not publically documented in provider. See https://github.com/IBM-Cloud/terraform-provider-ibm/issues/4485 +} + +resource "helm_release" "logs_agent" { + name = var.logs_agent_name + chart = var.logs_agent_chart + repository = var.logs_agent_chart_location + version = var.logs_agent_chart_version + namespace = var.logs_agent_namespace + create_namespace = true + timeout = 1200 + wait = true + recreate_pods = true + force_update = true + + set { + name = "metadata.name" + type = "string" + value = var.logs_agent_name + } + set { + name = "image.version" + type = "string" + value = var.logs_agent_image_version + } + set { + name = "env.ingestionHost" + type = "string" + value = local.cloud_logs_ingress_endpoint + } + set { + name = "env.ingestionPort" + value = var.cloud_logs_ingress_port + } + set_sensitive { + name = "secret.iamAPIKey" + type = "string" + value = local.logs_agent_iam_api_key + } + set { + name = "env.trustedProfileID" + type = "string" + value = local.logs_agent_trusted_profile_id + } + set { + name = "env.iamMode" + type = "string" + value = var.logs_agent_iam_mode + } + set { + name = "env.iamEnvironment" + type = "string" + value = var.logs_agent_iam_environment + } + set { + name = "additionalLogSourcePaths" + type = "string" + value = join("\\,", var.logs_agent_additional_log_source_paths) + } + set { + name = "excludeLogSourcePaths" + type = "string" + value = join("\\,", var.logs_agent_exclude_log_source_paths) + } + set { + name = "selectedLogSourcePaths" + type = "string" + value = join("\\,", local.logs_agent_selected_log_source_paths) + } + set { + name = "clusterName" + type = "string" + value = local.cluster_name + } + set { + name = "scc.create" + value = var.logs_agent_enable_scc + } + + # dummy value hack to force update https://github.com/hashicorp/terraform-provider-helm/issues/515#issuecomment-813088122 + values = [ + yamlencode({ + tolerations = var.logs_agent_tolerations + resources = var.logs_agent_resources + additionalMetadata = local.logs_agent_additional_metadata + dummy = uuid() + }) + ] + + + provisioner "local-exec" { + command = "${path.module}/scripts/confirm-rollout-status.sh ${var.logs_agent_name} ${var.logs_agent_namespace}" + interpreter = ["/bin/bash", "-c"] + environment = { + KUBECONFIG = data.ibm_container_cluster_config.cluster_config.config_file_path + } + } } diff --git a/outputs.tf b/outputs.tf index 1c0cf4cc..135186b7 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,30 +1,11 @@ -######################################################################################################################## +############################################################################## # Outputs -######################################################################################################################## +############################################################################## -# -# Developer tips: -# - Below are some good practise sample outputs -# - They should be updated for outputs applicable to the module being added -# - Use variable validation when possible -# +#output "myoutput" { +# description = "Description of my output" +# value = "value" +# depends_on = [] +#} -output "account_id" { - description = "An alpha-numeric value identifying the account ID." - value = ibm_resource_instance.cos_instance.account_id -} - -output "guid" { - description = "The GUID of the resource instance." - value = ibm_resource_instance.cos_instance.account_id -} - -output "id" { - description = "The unique identifier of the resource instance." - value = ibm_resource_instance.cos_instance.id -} - -output "crn" { - description = "The CRN of the resource instance." - value = ibm_resource_instance.cos_instance.crn -} +############################################################################## diff --git a/reference-architecture/deployable-architecture-logs-agent.svg b/reference-architecture/deployable-architecture-logs-agent.svg new file mode 100644 index 00000000..a18343ac --- /dev/null +++ b/reference-architecture/deployable-architecture-logs-agent.svg @@ -0,0 +1,4 @@ + + + +
IBM Cloud
IBM Cloud
Existing Resource Group
Existing Resource Group
Existing Cluster
Existing Cluster
Logs Agent
metrics
metrics
Existing Cloud Logs
Text is not SVG - cannot display
\ No newline at end of file diff --git a/renovate.json b/renovate.json index 8954b604..71db67b2 100644 --- a/renovate.json +++ b/renovate.json @@ -1,4 +1,15 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["github>terraform-ibm-modules/common-dev-assets:commonRenovateConfig"] + "extends": ["github>terraform-ibm-modules/common-dev-assets:commonRenovateConfig"], + "customManagers": [ + { + "customType": "regex", + "description": "Update agent version to the latest in variables.tf", + "fileMatch": ["variables.tf$"], + "datasourceTemplate": "docker", + "matchStrings": [ + "default\\s*=\\s*\"(?.*)\"\\s*# datasource: (?[^\\s]+)" + ] + } + ] } diff --git a/scripts/confirm-rollout-status.sh b/scripts/confirm-rollout-status.sh new file mode 100755 index 00000000..4197143d --- /dev/null +++ b/scripts/confirm-rollout-status.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e + +daemonset=$1 +namespace=$2 + +kubectl rollout status ds "${daemonset}" -n "${namespace}" --timeout 30m diff --git a/solutions/fully-configurable/DA-types.md b/solutions/fully-configurable/DA-types.md new file mode 100644 index 00000000..aec7a41e --- /dev/null +++ b/solutions/fully-configurable/DA-types.md @@ -0,0 +1,110 @@ +# Configuring Logs Agent Tolerations + +When you deploy the IBM Logs agent using `Cloud automation for Logs agent`, you can configure the tolerations that the agent applies to its pods by using the `logs_agent_tolerations` variable. This variable allows you to specify tolerations for scheduling the logs agent pods on nodes with specific taints. + +### Options for `logs_agent_tolerations` +- `key` (optional): The taint key that the toleration applies to. +- `operator` (optional): The operator to use for the toleration. Valid values are `Exists` and `Equal`. +- `value` (optional): The value to match for the taint key. +- `effect` (optional): The effect of the taint to tolerate. Valid values are `NoSchedule`, `PreferNoSchedule`, and `NoExecute`. +- `tolerationSeconds` (optional): The duration (in seconds) for which the toleration is valid when the `effect` is `NoExecute`. + +### Example `logs_agent_tolerations` Usage + +To configure tolerations for the logs agent, you can set the `logs_agent_tolerations` variable as follows: + +```hcl +logs_agent_tolerations = [ + { + key = "example-key" + operator = "Equal" + value = "example-value" + effect = "NoSchedule" + }, + { + operator = "Exists" + } +] +``` + +In this example: +- The first toleration applies to nodes with a taint key of `example-key` and a value of `example-value`, with the `NoSchedule` effect. +- The second toleration applies to any taint key, regardless of value, with the `Exists` operator. + +### What It Does + +The `logs_agent_tolerations` variable is used to configure the tolerations for the logs agent pods. This allows the agent to run on nodes with specific taints. The configuration is passed to the Helm chart during deployment, ensuring that the logs agent pods are scheduled according to the specified tolerations. + +=================================================================== + +# Configuring Logs Agent Additional Metadata + +When you deploy the IBM logs agent using `Cloud automation for Logs agent`, you can configure additional metadata fields to be added to the routed logs by using the `logs_agent_additional_metadata` variable. This variable allows you to specify key-value pairs of metadata that will be included in the logs. + +### Options for `logs_agent_additional_metadata` +- `key` (optional): The metadata key to add to the logs. +- `value` (optional): The metadata value to associate with the key. + +### Example `logs_agent_additional_metadata` Usage + +To configure additional metadata for the logs agent, you can set the `logs_agent_additional_metadata` variable as follows: + +```hcl +logs_agent_additional_metadata = [ + { + key = "environment" + value = "production" + }, + { + key = "team" + value = "devops" + } +] +``` + +In this example: +- The first metadata entry adds a key `environment` with the value `production` to the logs. +- The second metadata entry adds a key `team` with the value `devops` to the logs. + +### What It Does + +The `logs_agent_additional_metadata` variable is used to configure additional metadata fields that are added to the logs routed by the logs agent. This allows you to include custom metadata in the logs for better categorization or identification. The configuration is passed to the Helm chart during deployment, ensuring that the specified metadata is included in the logs. + +=================================================================== + +# Configuring Logs Agent Resources + +When you deploy the IBM Logs agent using `Cloud automation for Logs agent`, you can configure the resource requests and limits for the logs agent pods by using the `logs_agent_resources` variable. This variable allows you to specify the CPU and memory resources allocated to the logs agent. [Learn More](https://cloud.ibm.com/docs/cloud-logs?topic=cloud-logs-agent-helm-template-clusters#agent-helm-template-clusters-chart-options-resources). + +### Options for `logs_agent_resources` +- `requests` (optional): Specifies the minimum amount of resources required. Includes: + - `cpu`: The amount of CPU requested (e.g., `100m` for 0.1 CPU). + - `memory`: The amount of memory requested (e.g., `128Mi` for 128 MiB). +- `limits` (optional): Specifies the maximum amount of resources allowed. Includes: + - `cpu`: The maximum amount of CPU allowed. + - `memory`: The maximum amount of memory allowed. + +### Example `logs_agent_resources` Usage + +To configure resource requests and limits for the logs agent, you can set the `logs_agent_resources` variable as follows: + +```hcl +logs_agent_resources = { + requests = { + cpu = "100m" + memory = "128Mi" + } + limits = { + cpu = "500m" + memory = "256Mi" + } +} +``` + +In this example: +- The `requests` section specifies that the logs agent requires at least `100m` CPU and `128Mi` memory to run. +- The `limits` section specifies that the logs agent can use up to `500m` CPU and `256Mi` memory. + +### What It Does + +The `logs_agent_resources` variable is used to configure the resource requests and limits for the logs agent pods. This ensures that the logs agent has sufficient resources to operate efficiently while preventing it from consuming excessive resources on the node. The configuration is passed to the Helm chart during deployment, ensuring that the specified resource constraints are applied. diff --git a/solutions/fully-configurable/README.md b/solutions/fully-configurable/README.md new file mode 100644 index 00000000..12be964a --- /dev/null +++ b/solutions/fully-configurable/README.md @@ -0,0 +1,15 @@ +# Cloud automation for Logs agent + +This architecture deploys the following monitoring agent on a Red Hat OpenShift cluster: + +* Cloud Monitoring agent + +## Before you begin + +* Make sure that the Cluster is deployed. + +* Make sure that the Cloud Monitoring instance is deployed. + +![monitoring-agent-deployable-architecture](../../reference-architecture/deployable-architecture-logs-agent.svg) + +**NB:** This solution is not intended to be called by one or more other modules since it contains a provider configurations, meaning it is not compatible with the `for_each`, `count`, and `depends_on` arguments. For more information see [Providers Within Modules](https://developer.hashicorp.com/terraform/language/modules/develop/providers) diff --git a/solutions/fully-configurable/catalogValidationValues.json.template b/solutions/fully-configurable/catalogValidationValues.json.template new file mode 100644 index 00000000..f48a7e33 --- /dev/null +++ b/solutions/fully-configurable/catalogValidationValues.json.template @@ -0,0 +1,3 @@ +{ + "ibmcloud_api_key": $VALIDATION_APIKEY +} diff --git a/solutions/fully-configurable/kubeconfig/.gitignore b/solutions/fully-configurable/kubeconfig/.gitignore new file mode 100644 index 00000000..632a28fb --- /dev/null +++ b/solutions/fully-configurable/kubeconfig/.gitignore @@ -0,0 +1,6 @@ +# Ignore everything +* + +# But not these files... +!.gitignore +!README.md diff --git a/solutions/fully-configurable/kubeconfig/README.md b/solutions/fully-configurable/kubeconfig/README.md new file mode 100644 index 00000000..dff9dd4a --- /dev/null +++ b/solutions/fully-configurable/kubeconfig/README.md @@ -0,0 +1,2 @@ +This directory must exist in source control so the `ibm_container_cluster_config` data lookup can use it to place the +config.yml used to connect to a kubernetes cluster (See https://github.ibm.com/GoldenEye/issues/issues/552). diff --git a/solutions/fully-configurable/main.tf b/solutions/fully-configurable/main.tf new file mode 100644 index 00000000..ea0740bd --- /dev/null +++ b/solutions/fully-configurable/main.tf @@ -0,0 +1,46 @@ +############################################################################## +# Logs Agent +############################################################################## + +data "ibm_container_cluster_config" "cluster_config" { + cluster_name_id = local.is_vpc_cluster ? data.ibm_container_vpc_cluster.cluster[0].name : data.ibm_container_cluster.cluster[0].name + resource_group_id = var.cluster_resource_group_id + config_dir = "${path.module}/kubeconfig" + endpoint_type = local.cluster_config_endpoint_type != "default" ? local.cluster_config_endpoint_type : null +} + +locals { + cluster_config_endpoint_type = var.cluster_config_endpoint_type + is_vpc_cluster = var.is_vpc_cluster +} + +module "logs_agent" { + source = "../.." + cluster_id = var.cluster_id + cluster_resource_group_id = var.cluster_resource_group_id + cluster_config_endpoint_type = local.cluster_config_endpoint_type + # Logs Agent + logs_agent_chart = var.logs_agent_chart + logs_agent_chart_location = var.logs_agent_chart_location + logs_agent_chart_version = var.logs_agent_chart_version + logs_agent_image_version = var.logs_agent_image_version + logs_agent_name = var.logs_agent_name + logs_agent_namespace = var.logs_agent_namespace + logs_agent_trusted_profile_id = var.logs_agent_trusted_profile_id + logs_agent_iam_api_key = var.logs_agent_iam_api_key + logs_agent_tolerations = var.logs_agent_tolerations + logs_agent_additional_log_source_paths = var.logs_agent_additional_log_source_paths + logs_agent_exclude_log_source_paths = var.logs_agent_exclude_log_source_paths + logs_agent_selected_log_source_paths = var.logs_agent_selected_log_source_paths + logs_agent_log_source_namespaces = var.logs_agent_log_source_namespaces + logs_agent_iam_mode = var.logs_agent_iam_mode + logs_agent_iam_environment = var.logs_agent_iam_environment + logs_agent_additional_metadata = var.logs_agent_additional_metadata + logs_agent_enable_scc = var.is_ocp_cluster + logs_agent_resources = var.logs_agent_resources + cloud_logs_ingress_endpoint = var.cloud_logs_ingress_endpoint + cloud_logs_ingress_port = var.cloud_logs_ingress_port + is_vpc_cluster = var.is_vpc_cluster + wait_till = var.wait_till + wait_till_timeout = var.wait_till_timeout +} diff --git a/examples/advanced/provider.tf b/solutions/fully-configurable/outputs.tf similarity index 58% rename from examples/advanced/provider.tf rename to solutions/fully-configurable/outputs.tf index 2080946b..586e7dd2 100644 --- a/examples/advanced/provider.tf +++ b/solutions/fully-configurable/outputs.tf @@ -1,8 +1,6 @@ ############################################################################## -# Provider config +# Outputs ############################################################################## -provider "ibm" { - ibmcloud_api_key = var.ibmcloud_api_key - region = var.region -} + +############################################################################## diff --git a/solutions/fully-configurable/provider.tf b/solutions/fully-configurable/provider.tf new file mode 100644 index 00000000..40a74691 --- /dev/null +++ b/solutions/fully-configurable/provider.tf @@ -0,0 +1,39 @@ +provider "ibm" { + ibmcloud_api_key = var.ibmcloud_api_key +} + +provider "kubernetes" { + host = data.ibm_container_cluster_config.cluster_config.host + token = data.ibm_container_cluster_config.cluster_config.token + cluster_ca_certificate = data.ibm_container_cluster_config.cluster_config.ca_certificate +} + +provider "helm" { + kubernetes { + host = data.ibm_container_cluster_config.cluster_config.host + token = data.ibm_container_cluster_config.cluster_config.token + cluster_ca_certificate = data.ibm_container_cluster_config.cluster_config.ca_certificate + } + # IBM Cloud credentials are required to authenticate to the helm repo + registry { + url = "oci://icr.io/ibm/observe/logs-agent-helm" + username = "iamapikey" + password = var.ibmcloud_api_key + } +} + +# Retrieve information about an existing VPC cluster +data "ibm_container_vpc_cluster" "cluster" { + count = local.is_vpc_cluster ? 1 : 0 + name = var.cluster_id + wait_till = var.wait_till + wait_till_timeout = var.wait_till_timeout +} + +# Retrieve information about an existing Classic cluster +data "ibm_container_cluster" "cluster" { + count = local.is_vpc_cluster ? 0 : 1 + name = var.cluster_id + wait_till = var.wait_till + wait_till_timeout = var.wait_till_timeout +} diff --git a/solutions/fully-configurable/variables.tf b/solutions/fully-configurable/variables.tf new file mode 100644 index 00000000..805db54e --- /dev/null +++ b/solutions/fully-configurable/variables.tf @@ -0,0 +1,227 @@ +variable "ibmcloud_api_key" { + type = string + description = "The IBM Cloud API key." + sensitive = true +} + +############################################################################## +# Cluster variables +############################################################################## + +variable "cluster_id" { + type = string + description = "The ID of the cluster to deploy the agent in." +} + +variable "cluster_resource_group_id" { + type = string + description = "The resource group ID of the cluster." +} + +variable "cluster_config_endpoint_type" { + description = "Specify the type of endpoint to use to access the cluster configuration. Possible values: `default`, `private`, `vpe`, `link`. The `default` value uses the default endpoint of the cluster." + type = string + default = "default" + nullable = false # use default if null is passed in +} + +variable "is_vpc_cluster" { + type = bool + description = "Specify true if the target cluster for the DA is a VPC cluster, false if it is classic cluster." + default = true +} + +variable "wait_till" { + description = "Specify the stage when Terraform should mark the cluster resource creation as completed. Supported values: `MasterNodeReady`, `OneWorkerNodeReady`, `IngressReady`, `Normal`." + type = string + default = "Normal" +} + +variable "wait_till_timeout" { + description = "Timeout for wait_till in minutes." + type = number + default = 90 +} + +############################################################################## +# Logs Agent variables +############################################################################## + +variable "logs_agent_chart" { + description = "The name of the Helm chart to deploy." + type = string + default = "logs-agent-helm" # Replace with the actual chart name if different + nullable = false +} + +variable "logs_agent_chart_location" { + description = "The location of the Logs agent helm chart." + type = string + default = "oci://icr.io/ibm/observe" # Replace with the actual repository URL if different + nullable = false +} + +variable "logs_agent_chart_version" { + description = "The version of the Helm chart to deploy." + type = string + default = "1.5.1" # datasource: icr.io/ibm/observe/logs-agent-helm + nullable = false +} + +variable "logs_agent_image_version" { + description = "The version of the Logs agent image to deploy." + type = string + default = "1.5.1" # datasource: icr.io/ibm/observe/logs-agent-helm + nullable = false +} + +variable "logs_agent_name" { + description = "The name of the Logs agent. The name is used in all Kubernetes and Helm resources in the cluster." + type = string + default = "logs-agent" + nullable = false +} + +variable "logs_agent_namespace" { + type = string + description = "The namespace where the Logs agent is deployed. The default value is `ibm-observe`." + default = "ibm-observe" + nullable = false +} + +variable "logs_agent_trusted_profile_id" { + type = string + description = "The IBM Cloud trusted profile ID. Used only when `logs_agent_iam_mode` is set to `TrustedProfile`. The trusted profile must have an IBM Cloud Logs `Sender` role. Must provide a value for `logs_agent_iam_api_key` if `logs_agent_trusted_profile_id` is null." + default = null + validation { + condition = !(var.logs_agent_iam_mode == "TrustedProfile" && var.logs_agent_trusted_profile_id == null) + error_message = "The `logs_agent_trusted_profile_id` is required when `logs_agent_iam_mode` is set to `TrustedProfile`." + } +} + +variable "logs_agent_iam_api_key" { + type = string + description = "The IBM Cloud API key for the Logs agent to authenticate and communicate with the IBM Cloud Logs. It is required if `logs_agent_iam_mode` is set to `IAMAPIKey`." + sensitive = true + default = null + validation { + condition = !(var.logs_agent_iam_mode == "IAMAPIKey" && var.logs_agent_iam_api_key == null) + error_message = "The `logs_agent_iam_api_key` is required when `logs_agent_iam_mode` is set to `IAMAPIKey`." + } +} + +variable "logs_agent_tolerations" { + description = "List of tolerations to apply to Logs agent. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-logs-agent/tree/main/solutions/fully-configurable/DA-types.md#configuring-logs-agent-tolerations)." + type = list(object({ + key = optional(string) + operator = optional(string) + value = optional(string) + effect = optional(string) + tolerationSeconds = optional(number) + })) + default = [{ + operator = "Exists" + }] +} + +variable "logs_agent_resources" { + description = "The resources configuration for cpu/memory/storage. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-logs-agent/blob/main/solutions/fully-configurable/DA-types.md#configuring-logs-agent-resources)." + type = object({ + limits = object({ + cpu = string + memory = string + }) + requests = object({ + cpu = string + memory = string + }) + }) + default = { + limits = { + cpu = "500m" + memory = "3Gi" + } + requests = { + cpu = "100m" + memory = "1Gi" + } + } +} + +variable "logs_agent_additional_log_source_paths" { + type = list(string) + description = "The list of additional log sources. By default, the Logs agent collects logs from a single source at `/var/log/containers/logger-agent-ds-*.log`." + default = [] + nullable = false +} + +variable "logs_agent_exclude_log_source_paths" { + type = list(string) + description = "The list of log sources to exclude. Specify the paths that the Logs agent ignores." + default = [] + nullable = false +} + +variable "logs_agent_selected_log_source_paths" { + type = list(string) + description = "The list of specific log sources paths. Logs will only be collected from the specified log source paths." + default = [] + nullable = false +} + +variable "logs_agent_log_source_namespaces" { + type = list(string) + description = "The list of namespaces from which logs should be forwarded by agent. When specified logs from only these namespaces will be sent by the agent." + default = [] + nullable = false +} + +variable "logs_agent_iam_mode" { + type = string + default = "TrustedProfile" + description = "IAM authentication mode: `TrustedProfile` or `IAMAPIKey`." + validation { + error_message = "The IAM mode can only be `TrustedProfile` or `IAMAPIKey`." + condition = contains(["TrustedProfile", "IAMAPIKey"], var.logs_agent_iam_mode) + } +} + +variable "logs_agent_iam_environment" { + type = string + default = "PrivateProduction" + description = "IAM authentication Environment: `Production` or `PrivateProduction` or `Staging` or `PrivateStaging`." + validation { + error_message = "The IAM environment can only be `Production` or `PrivateProduction` or `Staging` or `PrivateStaging`." + condition = contains(["Production", "PrivateProduction", "Staging", "PrivateStaging"], var.logs_agent_iam_environment) + } +} + +variable "logs_agent_additional_metadata" { + description = "The list of additional metadata fields to add to the routed logs. [Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-logs-agent/blob/main/solutions/fully-configurable/DA-types.md#configuring-logs-agent-additional-metadata)." + type = list(object({ + key = optional(string) + value = optional(string) + })) + default = [] +} + +variable "is_ocp_cluster" { + description = "Whether to enable creation of Security Context Constraints in Openshift. When installing on an OpenShift cluster, this setting is mandatory to configure permissions for pods within your cluster." + type = bool + default = true +} + +variable "cloud_logs_ingress_endpoint" { + description = "The host for IBM Cloud Logs ingestion. Ensure you use the ingress endpoint. See https://cloud.ibm.com/docs/cloud-logs?topic=cloud-logs-endpoints_ingress." + type = string +} + +variable "cloud_logs_ingress_port" { + type = number + default = 3443 + description = "The target port for the IBM Cloud Logs ingestion endpoint. The port must be 443 if you connect by using a VPE gateway, or port 3443 when you connect by using CSEs." + validation { + error_message = "The Logs Routing supertenant ingestion port can only be `3443` or `443`." + condition = contains([3443, 443], var.cloud_logs_ingress_port) + } +} diff --git a/solutions/fully-configurable/version.tf b/solutions/fully-configurable/version.tf new file mode 100644 index 00000000..ef2dbfc9 --- /dev/null +++ b/solutions/fully-configurable/version.tf @@ -0,0 +1,20 @@ +terraform { + # module uses nullable feature which is only available in versions >= 1.1.0 + required_version = ">= 1.9.0" + + required_providers { + # Lock DA into an exact provider version - renovate automation will keep it updated + ibm = { + source = "ibm-cloud/ibm" + version = "1.76.1" + } + helm = { + source = "hashicorp/helm" + version = "2.16.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "2.33.0" + } + } +} diff --git a/tests/other_test.go b/tests/other_test.go index 88d360d3..4d00aa51 100644 --- a/tests/other_test.go +++ b/tests/other_test.go @@ -1,2 +1,23 @@ // Tests in this file are NOT run in the PR pipeline. They are run in the continuous testing pipeline along with the ones in pr_test.go package test + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const terraformDirLogsAgentROKS = "examples/logs-agent-ocp" + +func TestRunAgentVpcOcp(t *testing.T) { + t.Parallel() + + options := setupOptions(t, "logs-agent-roks", terraformDirLogsAgentROKS) + + // Keep costs down by using the cloud_pak entitlement + options.TerraformVars["ocp_entitlement"] = "cloud_pak" + + output, err := options.RunTestConsistency() + assert.Nil(t, err, "This should not have errored") + assert.NotNil(t, output, "Expected some output") +} diff --git a/tests/pr_test.go b/tests/pr_test.go index 8867ed00..0f1a1f74 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -1,60 +1,248 @@ -// Tests in this file are run in the PR pipeline and the continuous testing pipeline +// Tests in this file are run in the PR pipeline package test import ( + "fmt" + "os" + "strings" "testing" + "math/rand/v2" + + "github.com/gruntwork-io/terratest/modules/files" + "github.com/gruntwork-io/terratest/modules/logger" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/cloudinfo" "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testhelper" + "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testschematic" ) -// Use existing resource group -const resourceGroup = "geretain-test-resources" +const resourceGroup = "geretain-test-observability-agents" +const fullyConfigurableSolutionDir = "solutions/fully-configurable" +const fullyConfigurableSolutionKubeconfigDir = "solutions/fully-configurable/kubeconfig" +const terraformDirLogsAgentIKS = "examples/logs-agent-iks" + +var sharedInfoSvc *cloudinfo.CloudInfoService + +var validRegions = []string{ + "au-syd", + "eu-gb", + "eu-de", + "eu-es", + "us-south", +} + +// TestMain will be run before any parallel tests, used to set up a shared InfoService object to track region usage +// for multiple tests +func TestMain(m *testing.M) { + sharedInfoSvc, _ = cloudinfo.NewCloudInfoServiceFromEnv("TF_VAR_ibmcloud_api_key", cloudinfo.CloudInfoServiceOptions{}) + + os.Exit(m.Run()) +} -// Ensure every example directory has a corresponding test -const advancedExampleDir = "examples/advanced" -const basicExampleDir = "examples/basic" +func setupOptions(t *testing.T, prefix string, terraformDir string) *testhelper.TestOptions { -func setupOptions(t *testing.T, prefix string, dir string) *testhelper.TestOptions { options := testhelper.TestOptionsDefaultWithVars(&testhelper.TestOptions{ Testing: t, - TerraformDir: dir, + TerraformDir: terraformDir, Prefix: prefix, ResourceGroup: resourceGroup, + IgnoreUpdates: testhelper.Exemptions{ // Ignore for consistency check + List: []string{ + "module.logs_agent.helm_release.logs_agent", + }, + }, + CloudInfoService: sharedInfoSvc, }) + return options } -// Consistency test for the basic example -func TestRunBasicExample(t *testing.T) { +func TestFullyConfigurableSolution(t *testing.T) { t.Parallel() - options := setupOptions(t, "mod-template-basic", basicExampleDir) + var region = validRegions[rand.IntN(len(validRegions))] - output, err := options.RunTestConsistency() - assert.Nil(t, err, "This should not have errored") - assert.NotNil(t, output, "Expected some output") + // ------------------------------------------------------------------------------------------------------ + // Deploy OCP Cluster and Logs instance since it is needed to deploy Logs Agent + // ------------------------------------------------------------------------------------------------------ + + prefix := fmt.Sprintf("ocp-%s", strings.ToLower(random.UniqueId())) + realTerraformDir := "./resources" + tempTerraformDir, _ := files.CopyTerraformFolderToTemp(realTerraformDir, fmt.Sprintf(prefix+"-%s", strings.ToLower(random.UniqueId()))) + + // Verify ibmcloud_api_key variable is set + checkVariable := "TF_VAR_ibmcloud_api_key" + val, present := os.LookupEnv(checkVariable) + require.True(t, present, checkVariable+" environment variable not set") + require.NotEqual(t, "", val, checkVariable+" environment variable is empty") + + logger.Log(t, "Tempdir: ", tempTerraformDir) + existingTerraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ + TerraformDir: tempTerraformDir, + Vars: map[string]any{ + "prefix": prefix, + "region": region, + }, + // Set Upgrade to true to ensure latest version of providers and modules are used by terratest. + // This is the same as setting the -upgrade=true flag with terraform. + Upgrade: true, + }) + + terraform.WorkspaceSelectOrNew(t, existingTerraformOptions, prefix) + _, existErr := terraform.InitAndApplyE(t, existingTerraformOptions) + + if existErr != nil { + assert.True(t, existErr == nil, "Init and Apply of temp resources (OCP and Logs Instance) failed") + } else { + + options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ + Testing: t, + Prefix: "logs-agent", + TarIncludePatterns: []string{ + "*.tf", + "kubeconfig/*.*", + "scripts/*.*", + fullyConfigurableSolutionDir + "/*.*", + fullyConfigurableSolutionKubeconfigDir + "/*.*", + }, + IgnoreUpdates: testhelper.Exemptions{ // Ignore for consistency check + List: []string{ + "module.logs_agent.helm_release.logs_agent", + }, + }, + ResourceGroup: resourceGroup, + TemplateFolder: fullyConfigurableSolutionDir, + Tags: []string{"test-schematic"}, + DeleteWorkspaceOnFail: false, + WaitJobCompleteMinutes: 60, + Region: region, + }) + + options.TerraformVars = []testschematic.TestSchematicTerraformVar{ + {Name: "ibmcloud_api_key", Value: options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], DataType: "string", Secure: true}, + {Name: "cluster_id", Value: terraform.Output(t, existingTerraformOptions, "workload_cluster_id"), DataType: "string"}, + {Name: "logs_agent_trusted_profile_id", Value: terraform.Output(t, existingTerraformOptions, "trusted_profile_id"), DataType: "string"}, + {Name: "cloud_logs_ingress_endpoint", Value: terraform.Output(t, existingTerraformOptions, "cloud_logs_ingress_private_endpoint"), DataType: "string"}, + {Name: "cluster_resource_group_id", Value: terraform.Output(t, existingTerraformOptions, "cluster_resource_group_id"), DataType: "string"}, + } + + err := options.RunSchematicTest() + assert.Nil(t, err, "This should not have errored") + } + + // Check if "DO_NOT_DESTROY_ON_FAILURE" is set + envVal, _ := os.LookupEnv("DO_NOT_DESTROY_ON_FAILURE") + // Destroy the temporary existing resources if required + if t.Failed() && strings.ToLower(envVal) == "true" { + fmt.Println("Terratest failed. Debug the test and delete resources manually.") + } else { + logger.Log(t, "START: Destroy (existing resources)") + terraform.Destroy(t, existingTerraformOptions) + terraform.WorkspaceDelete(t, existingTerraformOptions, prefix) + logger.Log(t, "END: Destroy (existing resources)") + } } -func TestRunAdvancedExample(t *testing.T) { +func TestFullyConfigurableUpgradeSolution(t *testing.T) { t.Parallel() - options := setupOptions(t, "mod-template-adv", advancedExampleDir) + var region = validRegions[rand.IntN(len(validRegions))] + // ------------------------------------------------------------------------------------------------------ + // Deploy OCP Cluster and Observability instances since it is needed to deploy Logs Agent + // ------------------------------------------------------------------------------------------------------ + + prefix := fmt.Sprintf("ocp-%s", strings.ToLower(random.UniqueId())) + realTerraformDir := "./resources" + tempTerraformDir, _ := files.CopyTerraformFolderToTemp(realTerraformDir, fmt.Sprintf(prefix+"-%s", strings.ToLower(random.UniqueId()))) + + // Verify ibmcloud_api_key variable is set + checkVariable := "TF_VAR_ibmcloud_api_key" + val, present := os.LookupEnv(checkVariable) + require.True(t, present, checkVariable+" environment variable not set") + require.NotEqual(t, "", val, checkVariable+" environment variable is empty") + + logger.Log(t, "Tempdir: ", tempTerraformDir) + existingTerraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ + TerraformDir: tempTerraformDir, + Vars: map[string]any{ + "prefix": prefix, + "region": region, + }, + // Set Upgrade to true to ensure latest version of providers and modules are used by terratest. + // This is the same as setting the -upgrade=true flag with terraform. + Upgrade: true, + }) + + terraform.WorkspaceSelectOrNew(t, existingTerraformOptions, prefix) + _, existErr := terraform.InitAndApplyE(t, existingTerraformOptions) + + if existErr != nil { + assert.True(t, existErr == nil, "Init and Apply of temp resources (OCP and Logs Instances) failed") + } else { + + options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ + Testing: t, + Prefix: "logs-agent", + TarIncludePatterns: []string{ + "*.tf", + "kubeconfig/*.*", + "scripts/*.*", + fullyConfigurableSolutionDir + "/*.*", + fullyConfigurableSolutionKubeconfigDir + "/*.*", + }, + ResourceGroup: resourceGroup, + TemplateFolder: fullyConfigurableSolutionDir, + Tags: []string{"test-schematic"}, + DeleteWorkspaceOnFail: false, + WaitJobCompleteMinutes: 60, + Region: region, + }) + + options.TerraformVars = []testschematic.TestSchematicTerraformVar{ + {Name: "ibmcloud_api_key", Value: options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], DataType: "string", Secure: true}, + {Name: "cluster_id", Value: terraform.Output(t, existingTerraformOptions, "workload_cluster_id"), DataType: "string"}, + {Name: "logs_agent_trusted_profile_id", Value: terraform.Output(t, existingTerraformOptions, "trusted_profile_id"), DataType: "string"}, + {Name: "cloud_logs_ingress_endpoint", Value: terraform.Output(t, existingTerraformOptions, "cloud_logs_ingress_private_endpoint"), DataType: "string"}, + {Name: "cluster_resource_group_id", Value: terraform.Output(t, existingTerraformOptions, "cluster_resource_group_id"), DataType: "string"}, + } + + err := options.RunSchematicUpgradeTest() + assert.Nil(t, err, "This should not have errored") + } + + // Check if "DO_NOT_DESTROY_ON_FAILURE" is set + envVal, _ := os.LookupEnv("DO_NOT_DESTROY_ON_FAILURE") + // Destroy the temporary existing resources if required + if t.Failed() && strings.ToLower(envVal) == "true" { + fmt.Println("Terratest failed. Debug the test and delete resources manually.") + } else { + logger.Log(t, "START: Destroy (existing resources)") + terraform.Destroy(t, existingTerraformOptions) + terraform.WorkspaceDelete(t, existingTerraformOptions, prefix) + logger.Log(t, "END: Destroy (existing resources)") + } +} + +func TestRunAgentClassicKubernetes(t *testing.T) { + t.Parallel() + + options := setupOptions(t, "logs-agent-iks", terraformDirLogsAgentIKS) + options.TerraformVars["is_vpc_cluster"] = false output, err := options.RunTestConsistency() assert.Nil(t, err, "This should not have errored") assert.NotNil(t, output, "Expected some output") } -// Upgrade test (using advanced example) -func TestRunUpgradeExample(t *testing.T) { +func TestRunAgentVpcKubernetes(t *testing.T) { t.Parallel() - options := setupOptions(t, "mod-template-adv-upg", advancedExampleDir) - - output, err := options.RunTestUpgrade() - if !options.UpgradeTestSkipped { - assert.Nil(t, err, "This should not have errored") - assert.NotNil(t, output, "Expected some output") - } + options := setupOptions(t, "logs-agent-iks", terraformDirLogsAgentIKS) + output, err := options.RunTestConsistency() + assert.Nil(t, err, "This should not have errored") + assert.NotNil(t, output, "Expected some output") } diff --git a/tests/resources/README.md b/tests/resources/README.md new file mode 100644 index 00000000..4bb3621d --- /dev/null +++ b/tests/resources/README.md @@ -0,0 +1 @@ +The terraform code in this directory is used by the existing resource test in tests/pr_test.go diff --git a/tests/resources/main.tf b/tests/resources/main.tf new file mode 100644 index 00000000..9ea9b0ef --- /dev/null +++ b/tests/resources/main.tf @@ -0,0 +1,194 @@ +############################################################################## +# Resource Group +############################################################################## + +module "resource_group" { + source = "terraform-ibm-modules/resource-group/ibm" + version = "1.1.6" + # if an existing resource group is not set (null) create a new one using prefix + resource_group_name = var.resource_group == null ? "${var.prefix}-resource-group" : null + existing_resource_group_name = var.resource_group +} + +######################################################################################################################## +# VPC + Subnet + Public Gateway +# +# NOTE: This is a very simple VPC with single subnet in a single zone with a public gateway enabled, that will allow +# all traffic ingress/egress by default. +# For production use cases this would need to be enhanced by adding more subnets and zones for resiliency, and +# ACLs/Security Groups for network security. +######################################################################################################################## + +resource "ibm_is_vpc" "vpc" { + name = "${var.prefix}-vpc" + resource_group = module.resource_group.resource_group_id + address_prefix_management = "auto" + tags = var.resource_tags +} + +resource "ibm_is_public_gateway" "gateway" { + name = "${var.prefix}-gateway-1" + vpc = ibm_is_vpc.vpc.id + resource_group = module.resource_group.resource_group_id + zone = "${var.region}-1" +} + +resource "ibm_is_subnet" "subnet_zone_1" { + name = "${var.prefix}-subnet-1" + vpc = ibm_is_vpc.vpc.id + resource_group = module.resource_group.resource_group_id + zone = "${var.region}-1" + total_ipv4_address_count = 256 + public_gateway = ibm_is_public_gateway.gateway.id +} + +######################################################################################################################## +# OCP VPC cluster (single zone) +######################################################################################################################## + +locals { + cluster_vpc_subnets = { + default = [ + { + id = ibm_is_subnet.subnet_zone_1.id + cidr_block = ibm_is_subnet.subnet_zone_1.ipv4_cidr_block + zone = ibm_is_subnet.subnet_zone_1.zone + } + ] + } + + worker_pools = [ + { + subnet_prefix = "default" + pool_name = "default" # ibm_container_vpc_cluster automatically names default pool "default" (See https://github.com/IBM-Cloud/terraform-provider-ibm/issues/2849) + machine_type = "bx2.4x16" + workers_per_zone = 2 # minimum of 2 is allowed when using single zone + operating_system = "REDHAT_8_64" + } + ] +} + +locals { + cluster_name = "${var.prefix}-cluster" +} + +module "ocp_base" { + source = "terraform-ibm-modules/base-ocp-vpc/ibm" + version = "3.46.11" + resource_group_id = module.resource_group.resource_group_id + region = var.region + tags = var.resource_tags + cluster_name = local.cluster_name + force_delete_storage = true + vpc_id = ibm_is_vpc.vpc.id + vpc_subnets = local.cluster_vpc_subnets + worker_pools = local.worker_pools + access_tags = [] + disable_outbound_traffic_protection = false +} + +############################################################################## +# COS instance +############################################################################## + +module "cos" { + source = "terraform-ibm-modules/cos/ibm" + version = "8.16.4" + resource_group_id = module.resource_group.resource_group_id + region = var.region + cos_instance_name = "${var.prefix}-cos" + cos_tags = var.resource_tags + bucket_name = "${var.prefix}-bucket" + create_cos_bucket = false + retention_enabled = false # disable retention for test environments - enable for stage/prod + kms_encryption_enabled = false +} + + +############################################################################## +# COS buckets +############################################################################## + +locals { + logs_bucket_name = "${var.prefix}-logs-data" + metrics_bucket_name = "${var.prefix}-metrics-data" +} + +module "buckets" { + source = "terraform-ibm-modules/cos/ibm//modules/buckets" + version = "8.16.4" + bucket_configs = [ + { + bucket_name = local.logs_bucket_name + kms_encryption_enabled = false + region_location = var.region + resource_instance_id = module.cos.cos_instance_id + }, + { + bucket_name = local.metrics_bucket_name + kms_encryption_enabled = false + region_location = var.region + resource_instance_id = module.cos.cos_instance_id + } + ] +} + +############################################################################## +# Observability: +# - Cloud Logs instance +############################################################################## + +module "cloud_logs" { + source = "terraform-ibm-modules/cloud-logs/ibm" + version = "1.0.0" + resource_group_id = module.ocp_base.resource_group_id + region = var.region + instance_name = "${var.prefix}-cloud-logs" + resource_tags = var.resource_tags + data_storage = { + # logs and metrics buckets must be different + logs_data = { + enabled = true + bucket_crn = module.buckets.buckets[local.logs_bucket_name].bucket_crn + bucket_endpoint = module.buckets.buckets[local.logs_bucket_name].s3_endpoint_direct + }, + metrics_data = { + enabled = true + bucket_crn = module.buckets.buckets[local.metrics_bucket_name].bucket_crn + bucket_endpoint = module.buckets.buckets[local.metrics_bucket_name].s3_endpoint_direct + } + } +} + +############################################################################## +# Trusted Profile +############################################################################## + +locals { + logs_agent_namespace = "ibm-observe" + logs_agent_name = "logs-agent" +} + +module "trusted_profile" { + source = "terraform-ibm-modules/trusted-profile/ibm" + version = "1.0.5" + trusted_profile_name = "${var.prefix}-profile" + trusted_profile_description = "Logs agent Trusted Profile" + # As a `Sender`, you can send logs to your IBM Cloud Logs service instance - but not query or tail logs. This role is meant to be used by agent and routers sending logs. + trusted_profile_policies = [{ + roles = ["Sender"] + resources = [{ + service = "logs" + }] + }] + # Set up fine-grained authorization for `logs-agent` running in ROKS cluster in `ibm-observe` namespace. + trusted_profile_links = [{ + cr_type = "ROKS_SA" + links = [{ + crn = module.ocp_base.cluster_crn + namespace = local.logs_agent_namespace + name = local.logs_agent_name + }] + } + ] +} diff --git a/tests/resources/outputs.tf b/tests/resources/outputs.tf new file mode 100644 index 00000000..b2e907c0 --- /dev/null +++ b/tests/resources/outputs.tf @@ -0,0 +1,38 @@ +############################################################################## +# Outputs +############################################################################## + +output "region" { + value = var.region + description = "Region where OCP Cluster is deployed." +} + +output "workload_cluster_id" { + value = module.ocp_base.cluster_id + description = "ID of the workload cluster." +} + +output "workload_cluster_crn" { + value = module.ocp_base.cluster_crn + description = "CRN of the workload cluster." +} + +output "cluster_resource_group_id" { + value = module.ocp_base.resource_group_id + description = "Resource group ID of the workload cluster." +} + +output "trusted_profile_id" { + value = module.trusted_profile.trusted_profile.id + description = "The ID of the trusted profile." +} + +output "cloud_logs_instance_name" { + value = module.cloud_logs.name + description = "The name of the provisioned IBM Cloud Logs instance." +} + +output "cloud_logs_ingress_private_endpoint" { + value = module.cloud_logs.ingress_private_endpoint + description = "The private ingress endpoint of the provisioned Cloud Logs instance." +} diff --git a/tests/resources/provider.tf b/tests/resources/provider.tf new file mode 100644 index 00000000..df45ef50 --- /dev/null +++ b/tests/resources/provider.tf @@ -0,0 +1,4 @@ +provider "ibm" { + ibmcloud_api_key = var.ibmcloud_api_key + region = var.region +} diff --git a/tests/resources/variables.tf b/tests/resources/variables.tf new file mode 100644 index 00000000..07a76de2 --- /dev/null +++ b/tests/resources/variables.tf @@ -0,0 +1,29 @@ +variable "ibmcloud_api_key" { + type = string + description = "The IBM Cloud API Key." + sensitive = true +} + +variable "region" { + type = string + description = "Region to provision all resources created by this example." + default = "us-south" +} + +variable "prefix" { + type = string + description = "Prefix to append to all resources created by this example." + default = "agent-da" +} + +variable "resource_tags" { + type = list(string) + description = "Optional list of tags to be added to created resources." + default = ["logs-agent-ocp"] +} + +variable "resource_group" { + type = string + description = "The name of an existing resource group to provision resources in. If not specified, a new resource group is created with the `prefix` variable." + default = null +} diff --git a/tests/resources/version.tf b/tests/resources/version.tf new file mode 100644 index 00000000..2ac74bab --- /dev/null +++ b/tests/resources/version.tf @@ -0,0 +1,10 @@ + +terraform { + required_version = ">= 1.9.0" + required_providers { + ibm = { + source = "ibm-cloud/ibm" + version = ">= 1.76.1" + } + } +} diff --git a/tests/scripts/post-validation-destroy-ocp-and-logs-instances.sh b/tests/scripts/post-validation-destroy-ocp-and-logs-instances.sh new file mode 100755 index 00000000..6962d93d --- /dev/null +++ b/tests/scripts/post-validation-destroy-ocp-and-logs-instances.sh @@ -0,0 +1,19 @@ +#! /bin/bash + +######################################################################################################################## +## This script is used by the catalog pipeline to destroy prerequisite resource required for catalog validation ## +######################################################################################################################## + +set -e + +TERRAFORM_SOURCE_DIR="tests/resources" +TF_VARS_FILE="terraform.tfvars" + +( + cd ${TERRAFORM_SOURCE_DIR} + echo "Destroying prerequisite OCP Cluster and Logs instance.." + terraform destroy -input=false -auto-approve -var-file=${TF_VARS_FILE} || exit 1 + rm -f "${TF_VARS_FILE}" + + echo "Post-validation completed successfully" +) diff --git a/tests/scripts/pre-validation-deploy-ocp-and-logs-instances.sh b/tests/scripts/pre-validation-deploy-ocp-and-logs-instances.sh new file mode 100755 index 00000000..616d7eba --- /dev/null +++ b/tests/scripts/pre-validation-deploy-ocp-and-logs-instances.sh @@ -0,0 +1,50 @@ +#! /bin/bash + +############################################################################################################ +## This script is used by the catalog pipeline to deploy the OCP and Monitoring instances, +## which are the prerequisites for the Monitoring Agent DA. +############################################################################################################ + +set -e + +DA_DIR="solutions/fully-configurable" +TERRAFORM_SOURCE_DIR="tests/resources" +JSON_FILE="${DA_DIR}/catalogValidationValues.json" +TF_VARS_FILE="terraform.tfvars" + +( + cwd=$(pwd) + cd ${TERRAFORM_SOURCE_DIR} + echo "Provisioning prerequisite OCP Cluster and Logs Instance.." + terraform init || exit 1 + # $VALIDATION_APIKEY is available in the catalog runtime + { + echo "ibmcloud_api_key=\"${VALIDATION_APIKEY}\"" + echo "prefix=\"ocp-$(openssl rand -hex 2)\"" + } >> ${TF_VARS_FILE} + terraform apply -input=false -auto-approve -var-file=${TF_VARS_FILE} || exit 1 + + cluster_id_var_name="cluster_id" + cluster_id_value=$(terraform output -state=terraform.tfstate -raw workload_cluster_id) + cluster_resource_group_id_var_name="cluster_resource_group_id" + cluster_resource_group_id_value=$(terraform output -state=terraform.tfstate -raw cluster_resource_group_id) + logs_agent_trusted_profile_id_var_name="logs_agent_trusted_profile_id" + logs_agent_trusted_profile_id_value=$(terraform output -state=terraform.tfstate -raw trusted_profile_id) + cloud_logs_ingress_endpoint_var_name="cloud_logs_ingress_endpoint" + cloud_logs_ingress_endpoint_value=$(terraform output -state=terraform.tfstate -raw cloud_logs_ingress_private_endpoint) + + echo "Appending '${cluster_id_var_name}' '${cluster_resource_group_id_var_name}', '${logs_agent_trusted_profile_id_var_name}', and '${cloud_logs_ingress_endpoint_var_name}' input variable values to ${JSON_FILE}.." + + cd "${cwd}" + jq -r --arg cluster_id_var_name "${cluster_id_var_name}" \ + --arg cluster_id_value "${cluster_id_value}" \ + --arg cluster_resource_group_id_var_name "${cluster_resource_group_id_var_name}" \ + --arg cluster_resource_group_id_value "${cluster_resource_group_id_value}" \ + --arg logs_agent_trusted_profile_id_var_name "${logs_agent_trusted_profile_id_var_name}" \ + --arg logs_agent_trusted_profile_id_value "${logs_agent_trusted_profile_id_value}" \ + --arg cloud_logs_ingress_endpoint_var_name "${cloud_logs_ingress_endpoint_var_name}" \ + --arg cloud_logs_ingress_endpoint_value "${cloud_logs_ingress_endpoint_value}" \ + '. + {($cluster_id_var_name): $cluster_id_value, ($cluster_resource_group_id_var_name): $cluster_resource_group_id_value, ($logs_agent_trusted_profile_id_var_name): $logs_agent_trusted_profile_id_value, ($cloud_logs_ingress_endpoint_var_name): $cloud_logs_ingress_endpoint_value}' "${JSON_FILE}" > tmpfile && mv tmpfile "${JSON_FILE}" || exit 1 + + echo "Pre-validation complete successfully" +) diff --git a/variables.tf b/variables.tf index a9d9899a..656216d0 100644 --- a/variables.tf +++ b/variables.tf @@ -1,36 +1,235 @@ -######################################################################################################################## -# Input Variables -######################################################################################################################## +############################################################################## +# Cluster variables +############################################################################## -# -# Developer tips: -# - Below are some common module input variables -# - They should be updated for input variables applicable to the module being added -# - Use variable validation when possible -# +variable "cluster_id" { + type = string + description = "The ID of the cluster to deploy the agent." +} + +variable "cluster_resource_group_id" { + type = string + description = "The resource group ID of the cluster." +} + +variable "cluster_config_endpoint_type" { + description = "The type of endpoint to use for the cluster config access: `default`, `private`, `vpe`, or `link`. The `default` value uses the default endpoint of the cluster." + type = string + default = "default" + nullable = false # use default if null is passed in + validation { + error_message = "Invalid endpoint type. Valid values are `default`, `private`, `vpe`, or `link`." + condition = contains(["default", "private", "vpe", "link"], var.cluster_config_endpoint_type) + } +} + +variable "is_vpc_cluster" { + description = "Specify true if the target cluster for the agent is a VPC cluster, false if it is a classic cluster." + type = bool + default = true +} + +variable "wait_till" { + description = "To avoid long wait times when you run your Terraform code, you can specify the stage when you want Terraform to mark the cluster resource creation as completed. Depending on what stage you choose, the cluster creation might not be fully completed and continues to run in the background. However, your Terraform code can continue to run without waiting for the cluster to be fully created. Supported args are `MasterNodeReady`, `OneWorkerNodeReady`, `IngressReady` and `Normal`" + type = string + default = "Normal" + + validation { + error_message = "`wait_till` value must be one of `MasterNodeReady`, `OneWorkerNodeReady`, `IngressReady` or `Normal`." + condition = contains([ + "MasterNodeReady", + "OneWorkerNodeReady", + "IngressReady", + "Normal" + ], var.wait_till) + } +} + +variable "wait_till_timeout" { + description = "Timeout for wait_till in minutes." + type = number + default = 90 +} + +############################################################################## +# Logs Agent variables +############################################################################## + +variable "logs_agent_chart" { + description = "The name of the Helm chart to deploy." + type = string + default = "logs-agent-helm" # Replace with the actual chart name if different + nullable = false +} -variable "name" { +variable "logs_agent_chart_location" { + description = "The location of the Logs agent helm chart." type = string - description = "A descriptive name used to identify the resource instance." + default = "oci://icr.io/ibm/observe" # Replace with the actual repository URL if different + nullable = false } -variable "plan" { +variable "logs_agent_chart_version" { + description = "The version of the Helm chart to deploy." type = string - description = "The name of the plan type supported by service." - default = "standard" + default = "1.5.1" # datasource: icr.io/ibm/observe/logs-agent-helm + nullable = false +} + +variable "logs_agent_image_version" { + description = "The version of the Logs agent image to deploy." + type = string + default = "1.5.1" # datasource: icr.io/ibm/observe/logs-agent-helm + nullable = false +} + +variable "logs_agent_name" { + description = "The name of the Logs agent. The name is used in all Kubernetes and Helm resources in the cluster." + type = string + default = "logs-agent" + nullable = false +} + +variable "logs_agent_namespace" { + type = string + description = "The namespace where the Logs agent is deployed. The default value is `ibm-observe`." + default = "ibm-observe" + nullable = false +} + +variable "logs_agent_trusted_profile_id" { + type = string + description = "The IBM Cloud trusted profile ID. Used only when `logs_agent_iam_mode` is set to `TrustedProfile`. The trusted profile must have an IBM Cloud Logs `Sender` role." + default = null validation { - condition = contains(["standard", "cos-one-rate-plan"], var.plan) - error_message = "The specified pricing plan is not available. The following plans are supported: 'standard', 'cos-one-rate-plan'" + condition = !(var.logs_agent_trusted_profile_id == null && var.logs_agent_iam_mode == "TrustedProfile") + error_message = "The `logs_agent_trusted_profile_id` is required when `logs_agent_iam_mode` is set to `TrustedProfile`." } } -variable "resource_group_id" { +variable "logs_agent_iam_api_key" { type = string - description = "The ID of the resource group where you want to create the service." + description = "The IBM Cloud API key for the Logs agent to authenticate and communicate with the IBM Cloud Logs. It is required if `logs_agent_iam_mode` is set to `IAMAPIKey`." + sensitive = true + default = null + validation { + condition = !(var.logs_agent_iam_mode == "IAMAPIKey" && var.logs_agent_iam_api_key == null) + error_message = "The `logs_agent_iam_api_key` is required when `logs_agent_iam_mode` is set to `IAMAPIKey`." + } } -variable "resource_tags" { +variable "logs_agent_tolerations" { + description = "List of tolerations to apply to Logs agent. The default value means a pod will run on every node." + type = list(object({ + key = optional(string) + operator = optional(string) + value = optional(string) + effect = optional(string) + tolerationSeconds = optional(number) + })) + default = [{ + operator = "Exists" + }] +} + +variable "logs_agent_resources" { + description = "The resources configuration for cpu/memory/storage. [Learn More](https://cloud.ibm.com/docs/cloud-logs?topic=cloud-logs-agent-helm-template-clusters#agent-helm-template-clusters-chart-options-resources)" + type = object({ + limits = object({ + cpu = string + memory = string + }) + requests = object({ + cpu = string + memory = string + }) + }) + default = { + limits = { + cpu = "500m" + memory = "3Gi" + } + requests = { + cpu = "100m" + memory = "1Gi" + } + } +} + +variable "logs_agent_additional_log_source_paths" { type = list(string) - description = "List of resource tag to associate with the instance." + description = "The list of additional log sources. By default, the Logs agent collects logs from a single source at `/var/log/containers/*.log`." default = [] + nullable = false +} + +variable "logs_agent_exclude_log_source_paths" { + type = list(string) + description = "The list of log sources to exclude. Specify the paths that the Logs agent ignores." + default = [] + nullable = false +} + +variable "logs_agent_selected_log_source_paths" { + type = list(string) + description = "The list of specific log sources paths. Logs will only be collected from the specified log source paths. If no paths are specified, it will send logs from `/var/log/containers`." + default = [] + nullable = false +} + +variable "logs_agent_log_source_namespaces" { + type = list(string) + description = "The list of namespaces from which logs should be forwarded by agent. If namespaces are not listed, logs from all namespaces will be sent." + default = [] + nullable = false +} + +variable "logs_agent_iam_mode" { + type = string + default = "TrustedProfile" + description = "IAM authentication mode: `TrustedProfile` or `IAMAPIKey`." + validation { + error_message = "The IAM mode can only be `TrustedProfile` or `IAMAPIKey`." + condition = contains(["TrustedProfile", "IAMAPIKey"], var.logs_agent_iam_mode) + } +} + +variable "logs_agent_iam_environment" { + type = string + default = "PrivateProduction" + description = "IAM authentication Environment: `Production` or `PrivateProduction` or `Staging` or `PrivateStaging`. `Production` specifies the public endpoint & `PrivateProduction` specifies the private endpoint." + validation { + error_message = "The IAM environment can only be `Production` or `PrivateProduction` or `Staging` or `PrivateStaging`." + condition = contains(["Production", "PrivateProduction", "Staging", "PrivateStaging"], var.logs_agent_iam_environment) + } +} + +variable "logs_agent_additional_metadata" { + description = "The list of additional metadata fields to add to the routed logs." + type = list(object({ + key = optional(string) + value = optional(string) + })) + default = [] +} + +variable "logs_agent_enable_scc" { + description = "Whether to enable creation of Security Context Constraints in Openshift. When installing on an OpenShift cluster, this setting is mandatory to configure permissions for pods within your cluster." + type = bool + default = true +} + +variable "cloud_logs_ingress_endpoint" { + description = "The host for IBM Cloud Logs ingestion. Ensure you use the ingress endpoint. See https://cloud.ibm.com/docs/cloud-logs?topic=cloud-logs-endpoints_ingress." + type = string +} + +variable "cloud_logs_ingress_port" { + type = number + default = 3443 + description = "The target port for the IBM Cloud Logs ingestion endpoint. The port must be 443 if you connect by using a VPE gateway, or port 3443 when you connect by using CSEs." + validation { + error_message = "The Logs Routing supertenant ingestion port can only be `3443` or `443`." + condition = contains([3443, 443], var.cloud_logs_ingress_port) + } } diff --git a/version.tf b/version.tf index e51de7f6..a63e930b 100644 --- a/version.tf +++ b/version.tf @@ -1,18 +1,15 @@ terraform { - # require 1.9 or later to make use of cross-object referencing for input variable validations - # more info: https://www.hashicorp.com/blog/terraform-1-9-enhances-input-variable-validations required_version = ">= 1.9.0" - # - # Developer tips: - # - If your module requires any terraform providers, add them the "required_providers" section below. - # - Each required provider's version should be a flexible range to future proof the module's usage with upcoming minor and patch versions. - # - + # Each required provider's version should be a flexible range to future proof the module's usage with upcoming minor and patch versions. required_providers { ibm = { - source = "IBM-Cloud/ibm" - version = ">= 1.71.2, < 2.0.0" + source = "ibm-cloud/ibm" + version = ">= 1.76.1, <2.0.0" + } + helm = { + source = "hashicorp/helm" + version = ">= 2.15.0, <3.0.0" } } }