diff --git a/.catalog-onboard-pipeline.yaml b/.catalog-onboard-pipeline.yaml new file mode 100644 index 0000000..d193d77 --- /dev/null +++ b/.catalog-onboard-pipeline.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +offerings: + - name: deploy-arch-ibm-container-registry + kind: solution + catalog_id: 7df1e4ca-d54c-4fd0-82ce-3d13247308cd + offering_id: 5947efd7-d52a-4905-8052-45f1142f78a0 + variations: + - name: standard + mark_ready: true + install_type: fullstack + scc: + instance_id: 1c7d5f78-9262-44c3-b779-b28fe4d88c37 + region: us-south diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dcb4ce7..890e100 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,3 @@ # Primary owner should be listed first in list of global owners, follwed by any secondary owners + * @shemau @daniel-butler-irl diff --git a/.github/settings.yml b/.github/settings.yml index 873205e..4ef7517 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -26,4 +26,4 @@ repository: # Uncomment this topics property # and add a comma-separated list of topics to set on the repo. - topics: core-team, terraform, ibm-cloud, terraform-module, supported, graduated + topics: core-team, terraform, ibm-cloud, terraform-module, supported, graduated, deployable-architecture diff --git a/.releaserc b/.releaserc index 708916f..4160e57 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/README.md b/README.md index 99ec672..6f14e1d 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,9 @@ You can use this module to provision and configure an [IBM Container Registry](h * [terraform-ibm-container-registry](#terraform-ibm-container-registry) * [Submodules](./modules) * [plan](./modules/plan) + * [quotas](./modules/quotas) * [Examples](./examples) - * [IBM Container Registry namespace example](./examples/namespace) + * [IBM Container Registry namespace example](./examples/complete) * [Contributing](#contributing) @@ -27,11 +28,25 @@ You can use this module to provision and configure an [IBM Container Registry](h ```hcl module "namespace" { source = "terraform-ibm-modules/container-registry/ibm" - version = "X.X.X" # Replace "X.X.X" with a release version to lock into a specific release - name = "my-namespace" + version = "X.Y.Z" # Replace "X.Y.Z" with a release version to lock into a specific release + namespace_name = "my-namespace" + namespace_region = "us-south" resource_group_id = "xxXXxxXXxXxXXXXxxXxxxXXXXxXXXXX" images_per_repo = 2 } + +module "upgrade-plan" { + source = "terraform-ibm-modules/container-registry/ibm//modules/plan" + version = "X.Y.Z" # Replace "X.Y.Z" with a release version to lock into a specific release +} +module "set_quota" { + source = "terraform-ibm-modules/container-registry/ibm//modules/quotas" + version = "X.Y.Z" # Replace "X.Y.Z" with a release version to lock into a specific release + update_storage_quota = true + storage_megabytes = 5 * 1024 # 5GiB + update_traffic_quota = true + traffic_megabytes = 500 # 500 MB +} ``` ### Required IAM access policies @@ -61,13 +76,15 @@ No modules. |------|------| | [ibm_cr_namespace.cr_namespace](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/cr_namespace) | resource | | [ibm_cr_retention_policy.cr_retention_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/cr_retention_policy) | resource | +| [ibm_cr_namespaces.existing_cr_namespaces](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/data-sources/cr_namespaces) | data source | ### Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [existing\_namespace\_name](#input\_existing\_namespace\_name) | The name of an existing namespace. Required if 'namespace\_name' is not provided. | `string` | `null` | no | | [images\_per\_repo](#input\_images\_per\_repo) | (Optional, Integer) Determines how many images are retained in each repository when the retention policy is processed. The value -1 denotes Unlimited (all images are retained). The value 0 denotes no retention policy will be created (default) | `number` | `0` | no | -| [name](#input\_name) | Name of the container registry namespace | `string` | n/a | yes | +| [namespace\_name](#input\_namespace\_name) | Name of the container registry namespace, if var.existing\_namespace\_name is not inputted, a new namespace will be created in a region set by provider. | `string` | n/a | yes | | [resource\_group\_id](#input\_resource\_group\_id) | The resource group ID where the IBM container namespace will be created. | `string` | n/a | yes | | [retain\_untagged](#input\_retain\_untagged) | (Optional, Bool) Determines whether untagged images are retained when the retention policy is processed. Default value is false, means untagged images can be deleted when the policy runs. | `bool` | `false` | no | | [tags](#input\_tags) | Optional list of tags to be added to the IBM container namespace. | `list(string)` | `[]` | no | @@ -77,6 +94,8 @@ No modules. | Name | Description | |------|-------------| | [namespace\_crn](#output\_namespace\_crn) | CRN representing the namespace | +| [namespace\_name](#output\_namespace\_name) | Name of ICR namespace | +| [retention\_policy\_id](#output\_retention\_policy\_id) | ID of retentation policy | diff --git a/cra-config.yaml b/cra-config.yaml index 6c22619..7803b6e 100644 --- a/cra-config.yaml +++ b/cra-config.yaml @@ -1,11 +1,11 @@ version: "v1" CRA_TARGETS: - - CRA_TARGET: "examples/namespace" + - CRA_TARGET: "solutions/standard" 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). - # 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_sample: "sample value" - # TF_VAR_other: "another value" + 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_provider_visibility: "public" + TF_VAR_namespace_region: "us-south" # 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. # PROFILE_ID: "" # The Profile ID input for CRA SCC scan. Ensure to use a US-specific ID. If not provided, a default global value will be used. diff --git a/examples/namespace/README.md b/examples/complete/README.md similarity index 99% rename from examples/namespace/README.md rename to examples/complete/README.md index 956b01c..901e42c 100644 --- a/examples/namespace/README.md +++ b/examples/complete/README.md @@ -1,6 +1,7 @@ # IBM Container Registry namespace example This example creates the following infrastructure: + - A new resource group, if one is not passed in. - A new IBM Container Registry namespace. - Optionally, a new retention policy for the namespace. diff --git a/examples/complete/main.tf b/examples/complete/main.tf new file mode 100644 index 0000000..7e9f426 --- /dev/null +++ b/examples/complete/main.tf @@ -0,0 +1,34 @@ +######################################################################################################################## +# 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 +} + +module "namespace" { + providers = { + ibm = ibm.namespace + } + source = "../.." + namespace_name = var.prefix == null ? "namespace" : "${var.prefix}-namespace" + existing_namespace_name = var.existing_namespace_name + resource_group_id = module.resource_group.resource_group_id + tags = var.resource_tags + images_per_repo = var.images_per_repo + retain_untagged = var.retain_untagged +} + +module "upgrade_plan" { + source = "../..//modules/plan" +} + +module "set_quota" { + source = "../../modules/quotas" + storage_megabytes = 5 * 1024 - 1 + traffic_megabytes = 499 +} diff --git a/examples/namespace/outputs.tf b/examples/complete/outputs.tf similarity index 100% rename from examples/namespace/outputs.tf rename to examples/complete/outputs.tf diff --git a/examples/namespace/provider.tf b/examples/complete/provider.tf similarity index 87% rename from examples/namespace/provider.tf rename to examples/complete/provider.tf index cea4308..ce91bb8 100644 --- a/examples/namespace/provider.tf +++ b/examples/complete/provider.tf @@ -4,7 +4,12 @@ provider "ibm" { ibmcloud_api_key = var.ibmcloud_api_key - region = var.region +} + +provider "ibm" { + alias = "namespace" + ibmcloud_api_key = var.ibmcloud_api_key + region = var.namespace_region } # Data source to retrieve token details diff --git a/examples/namespace/variables.tf b/examples/complete/variables.tf similarity index 73% rename from examples/namespace/variables.tf rename to examples/complete/variables.tf index aede011..ebb7e8c 100644 --- a/examples/namespace/variables.tf +++ b/examples/complete/variables.tf @@ -10,12 +10,18 @@ variable "prefix" { default = "test-icr" } -variable "region" { +variable "namespace_region" { type = string - description = "Prefix to append to all resources created by this example" + description = "The IBM Cloud region where the container registry namespace and retention policy will be created or where the existing namespace is located." default = "us-south" } +variable "existing_namespace_name" { + type = bool + description = "The name of an existing namespace. Required if `namespace_name` is not provided." + default = null +} + variable "resource_group" { type = string description = "An existing resource group name to use for this example, if unset a new resource group will be created" diff --git a/examples/namespace/versions.tf b/examples/complete/versions.tf similarity index 95% rename from examples/namespace/versions.tf rename to examples/complete/versions.tf index ebdd682..802b317 100644 --- a/examples/namespace/versions.tf +++ b/examples/complete/versions.tf @@ -11,7 +11,7 @@ terraform { } restapi = { source = "Mastercard/restapi" - version = ">= 1.18.2" + version = ">= 1.20.0" } } } diff --git a/ibm_catalog.json b/ibm_catalog.json new file mode 100644 index 0000000..147c88d --- /dev/null +++ b/ibm_catalog.json @@ -0,0 +1,200 @@ +{ + "products": [ + { + "name": "deploy-arch-ibm-container-registry", + "label": "Cloud automation for IBM Container Registry", + "product_kind": "solution", + "tags": [ + "ibm_created", + "target_terraform", + "terraform", + "containers", + "registry", + "solution" + ], + "keywords": [ + "containers", + "IaC", + "infrastructure as code", + "terraform", + "solution", + "registry" + ], + "short_description": "Creates or uses an existing IBM Container Registry namespace, configures pull traffic and storage quotas, and supports upgrading the registry plan to Standard.", + "long_description": "This architecture creates or utilizes an existing IBM Container Registry namespace, provides the ability to configure pull traffic limits and storage quotas in megabytes, and allows for upgrading the registry plan to Standard. It ensures efficient management of container image access by regulating data pull volume from the registry and setting storage capacity limits for container images within each registry.", + "offering_docs_url": "https://github.com/terraform-ibm-modules/terraform-ibm-container-registry/main/README.md", + "offering_icon_url": "https://raw.githubusercontent.com/terraform-ibm-modules/terraform-ibm-container-registry/main/images/icr_icon.svg", + "provider_name": "IBM", + "features": [ + { + "title": "Creates or Uses Existing IBM Container Registry Namespace", + "description": "Enables the creation of a new IBM Container Registry namespace or the use of an existing one, allowing users to define isolated environments for managing container images, with the ability to set a retention policy." + }, + { + "title": "Configures Pull Traffic Limits", + "description": "Allows fine-grained configuration of pull traffic limits in megabytes, controlling the amount of data that can be pulled from the registry." + }, + { + "title": "Sets Storage Quotas", + "description": "Enables the configuration of storage quotas in megabytes, defining the maximum amount of storage available for container images within each registry." + }, + { + "title": "Upgrades Registry Plan to Standard", + "description": "Provides the ability to upgrade the container registry plan to Standard, allowing for enhanced features and capabilities." + } + ], + + "flavors": [ + { + "label": "Standard", + "name": "standard", + "install_type": "fullstack", + "working_directory": "solutions/standard", + "compliance": { + "authority": "scc-v3", + "profiles": [ + { + "profile_name": "IBM Cloud Framework for Financial Services", + "profile_version": "1.7.0" + } + ]}, + "iam_permissions": [ + { + "role_crns": [ + "crn:v1:bluemix:public:iam::::role:Administrator" + ], + "service_name": "all-account-management-services" + }, + { + "role_crns": [ + "crn:v1:bluemix:public:iam::::role:Writer", + "crn:v1:bluemix:public:iam::::role:Manager" + ], + "service_name": "container-registry" + } + ], + "architecture": { + "descriptions": "Creates or uses an existing IBM Container Registry namespace, configures pull traffic and storage quotas, and supports upgrading the registry plan to Standard.", + "features": [ + { + "title": "Creates or uses an existing IBM Container Registry namespace, configures pull traffic and storage quotas, and supports upgrading the registry plan to Standard.", + "description": "This architecture creates or utilizes an existing IBM Container Registry namespace, provides the ability to configure pull traffic limits and storage quotas in megabytes, and allows for upgrading the registry plan to Standard. It ensures efficient management of container image access by regulating data pull volume from the registry and setting storage capacity limits for container images within each registry." + } + ], + "diagrams": [ + { + "diagram": { + "caption": "Creates IBM Container Registry namespace.", + "url": "https://raw.githubusercontent.com/terraform-ibm-modules/terraform-ibm-container-registry/main/reference-architecture/deployable-architecture-icr.svg", + "type": "image/svg+xml" + }, + "description": "This architecture creates or utilizes an existing IBM Container Registry namespace, provides the ability to configure pull traffic limits and storage quotas in megabytes, and allows for upgrading the registry plan to Standard. It ensures efficient management of container image access by regulating data pull volume from the registry and setting storage capacity limits for container images within each registry." + } + ] + }, + "configuration": [ + { + "key": "ibmcloud_api_key" + }, + { + "key": "provider_visibility", + "options": [ + { + "displayname": "private", + "value": "private" + }, + { + "displayname": "public", + "value": "public" + }, + { + "displayname": "public-and-private", + "value": "public-and-private" + } + ] + }, + { + "key": "prefix" + }, + { + "key": "use_existing_resource_group" + }, + { + "key": "resource_group_name" + }, + { + "key": "namespace_region", + "default_value": "us-south", + "options": [ + { + "displayname": "Global (global)", + "value": "global" + }, + { + "displayname": "Tokyo (jp-tok)", + "value": "jp-tok" + }, + { + "displayname": "Sydney (au-syd)", + "value": "au-syd" + }, + { + "displayname": "Sao Paulo (br-sao)", + "value": "br-sao" + }, + { + "displayname": "Toronto (ca-tor)", + "value": "ca-tor" + }, + { + "displayname": "Frankfurt (eu-de)", + "value": "eu-de" + }, + { + "displayname": "Madrid (eu-es)", + "value": "eu-es" + }, + { + "displayname": "London (eu-gb)", + "value": "eu-gb" + }, + { + "displayname": "Osaka (jp-osa)", + "value": "jp-osa" + }, + { + "displayname": "Dallas (us-south)", + "value": "us-south" + } + ] + }, + { + "key": "namespace_name" + }, + { + "key": "existing_namespace_name" + }, + { + "key": "tags" + }, + { + "key": "images_per_repo" + }, + { + "key": "retain_untagged" + }, + { + "key": "upgrade_to_standard_plan" + }, + { + "key": "storage_megabytes" + }, + { + "key": "traffic_megabytes" + } + ] + } + ] + } + ] +} diff --git a/images/icr_icon.svg b/images/icr_icon.svg new file mode 100644 index 0000000..4a24b1f --- /dev/null +++ b/images/icr_icon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/main.tf b/main.tf index c233ef9..81ea54b 100644 --- a/main.tf +++ b/main.tf @@ -1,14 +1,35 @@ +data "ibm_cr_namespaces" "existing_cr_namespaces" { + count = var.existing_namespace_name != null ? 1 : 0 +} + +locals { + existing_cr_namespace = var.existing_namespace_name != null ? [ + for namespace in data.ibm_cr_namespaces.existing_cr_namespaces[0].namespaces : + namespace if namespace == var.existing_namespace_name + ] : [] + # tflint-ignore: terraform_unused_declarations + validate_existing_namespace = var.existing_namespace_name != null && length(local.existing_cr_namespace) == 0 ? tobool("existing namespace ${var.existing_namespace_name} not found in a region") : false + # tflint-ignore: terraform_unused_declarations + validate_namespace_name = (var.namespace_name == null && var.existing_namespace_name == null) ? tobool("When 'namespace_name' is null, a value must be passed for var.existing_namespace_name") : true +} + resource "ibm_cr_namespace" "cr_namespace" { - name = var.name + count = var.existing_namespace_name != null ? 0 : 1 + name = var.namespace_name resource_group_id = var.resource_group_id tags = var.tags } resource "ibm_cr_retention_policy" "cr_retention_policy" { count = var.images_per_repo != 0 ? 1 : 0 - namespace = ibm_cr_namespace.cr_namespace.name + namespace = var.namespace_name images_per_repo = var.images_per_repo retain_untagged = var.retain_untagged != null ? var.retain_untagged : false # Issue 128: to ensure policy fully destroyed before namespace depends_on = [ibm_cr_namespace.cr_namespace] } + +moved { + from = ibm_cr_namespace.cr_namespace + to = ibm_cr_namespace.cr_namespace[0] +} diff --git a/modules/plan/README.md b/modules/plan/README.md index 65773ff..a66767d 100644 --- a/modules/plan/README.md +++ b/modules/plan/README.md @@ -62,7 +62,7 @@ You need the following permissions to run this module. |------|---------| | [terraform](#requirement\_terraform) | >= 1.0.0 | | [ibm](#requirement\_ibm) | >= 1.49.0, < 2.0.0 | -| [restapi](#requirement\_restapi) | >= 1.18.2 | +| [restapi](#requirement\_restapi) | >= 1.20.0, <2.0.0 | ### Modules @@ -78,7 +78,7 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [container\_registry\_endpoint](#input\_container\_registry\_endpoint) | The endpoint of the ICR region, eg. https://us.icr.io or https://de.icr.io, to change to standard plan | `string` | `"us.icr.io"` | no | +| [container\_registry\_endpoint](#input\_container\_registry\_endpoint) | The endpoint of the ICR region, eg. `us.icr.io` or `de.icr.io`, to change to standard plan | `string` | `"us.icr.io"` | no | ### Outputs diff --git a/modules/plan/variables.tf b/modules/plan/variables.tf index efee397..0a0c6a7 100644 --- a/modules/plan/variables.tf +++ b/modules/plan/variables.tf @@ -3,7 +3,7 @@ ############################################################################## variable "container_registry_endpoint" { - description = "The endpoint of the ICR region, eg. https://us.icr.io or https://de.icr.io, to change to standard plan" + description = "The endpoint of the ICR region, eg. `us.icr.io` or `de.icr.io`, to change to standard plan" type = string default = "us.icr.io" validation { diff --git a/modules/plan/version.tf b/modules/plan/version.tf index 371f9da..ebd695c 100644 --- a/modules/plan/version.tf +++ b/modules/plan/version.tf @@ -7,10 +7,9 @@ terraform { source = "ibm-cloud/ibm" version = ">= 1.49.0, < 2.0.0" } - # tflint-ignore: terraform_unused_required_providers restapi = { source = "Mastercard/restapi" - version = ">= 1.18.2" + version = ">= 1.20.0, <2.0.0" } } } diff --git a/modules/quotas/README.md b/modules/quotas/README.md new file mode 100644 index 0000000..6ccfe41 --- /dev/null +++ b/modules/quotas/README.md @@ -0,0 +1,69 @@ +# IBM Container Registry Quota + +You can use this submodule to set the [Container Registry](https://cloud.ibm.com/docs/Registry?topic=Registry-getting-started) pull traffic and storage quotas. + +The submodule can be used without the root module to set the pull traffic and storage quotas. + +### Usage + +```hcl + +# Upgrade plan: +module "upgrade-plan" { + source = "terraform-ibm-modules/container-registry/ibm//modules/plan" + version = "X.X.X" # Replace "X.X.X" with a release version to lock into a specific release + container_registry_endpoint = "us.icr.io" +} +module "set_quota" { + source = "terraform-ibm-modules/container-registry/ibm//modules/quotas" + version = "X.X.X" # Replace "X.X.X" with a release version to lock into a specific release + container_registry_endpoint = "us.icr.io" + storage_megabytes = 5 * 1024 # 5GiB + traffic_megabytes = 500 # 500 MB +} +``` + +### Required IAM access policies + +You need the following permissions to run this module. + +- Account Management + - IBM Cloud Container Registry service + - `Manager` service access + - `Writer` service access + +[Access roles for using Container Registry](https://cloud.ibm.com/docs/Registry?topic=Registry-iam&interface=ui#access_roles_using) + + + +### Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0.0 | +| [ibm](#requirement\_ibm) | >= 1.49.0, < 2.0.0 | +| [restapi](#requirement\_restapi) | >= 1.20.0, <2.0.0 | + +### Modules + +No modules. + +### Resources + +| Name | Type | +|------|------| +| [restapi_object.container_registry_storage_quota](https://registry.terraform.io/providers/Mastercard/restapi/latest/docs/resources/object) | resource | +| [restapi_object.container_registry_traffic_quota](https://registry.terraform.io/providers/Mastercard/restapi/latest/docs/resources/object) | resource | + +### Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [container\_registry\_endpoint](#input\_container\_registry\_endpoint) | The endpoint of the IBM Container Registry, eg. https://us.icr.io or https://de.icr.io, to change quotas | `string` | `"us.icr.io"` | no | +| [storage\_megabytes](#input\_storage\_megabytes) | Storage quota in megabytes. The value -1 denotes `Unlimited` | `number` | `null` | no | +| [traffic\_megabytes](#input\_traffic\_megabytes) | Traffic quota in megabytes. The value -1 denotes `Unlimited`. | `number` | `null` | no | + +### Outputs + +No outputs. + diff --git a/modules/quotas/main.tf b/modules/quotas/main.tf new file mode 100644 index 0000000..5841b5d --- /dev/null +++ b/modules/quotas/main.tf @@ -0,0 +1,45 @@ +############################################################################## +# Upgrade container registry quota +############################################################################## + +# Upgrade container registry storage quota +resource "restapi_object" "container_registry_storage_quota" { + count = var.storage_megabytes != null ? 1 : 0 + path = "//${var.container_registry_endpoint}/api/v1/quotas" + data = jsonencode({ + "storage_megabytes" : var.storage_megabytes + }) + create_method = "PATCH" + create_path = "//${var.container_registry_endpoint}/api/v1/quotas" + destroy_method = "PATCH" + destroy_path = "//${var.container_registry_endpoint}/api/v1/quotas" + destroy_data = jsonencode({ + "storage_megabytes" : 500 # set to default 500 MB + }) + read_method = "GET" + read_path = "//${var.container_registry_endpoint}/api/v1/quotas" + update_method = "PATCH" + update_path = "//${var.container_registry_endpoint}/api/v1/quotas" + object_id = "storage" +} + +# Upgrade container registry pull traffic quota +resource "restapi_object" "container_registry_traffic_quota" { + count = var.traffic_megabytes != null ? 1 : 0 + path = "//${var.container_registry_endpoint}/api/v1/quotas" + data = jsonencode({ + "traffic_megabytes" : var.traffic_megabytes + }) + create_method = "PATCH" + create_path = "//${var.container_registry_endpoint}/api/v1/quotas" + destroy_method = "PATCH" + destroy_path = "//${var.container_registry_endpoint}/api/v1/quotas" + destroy_data = jsonencode({ + "traffic_megabytes" : 5120 # set to default 5GiB + }) + read_method = "GET" + read_path = "//${var.container_registry_endpoint}/api/v1/quotas" + update_method = "PATCH" + update_path = "//${var.container_registry_endpoint}/api/v1/quotas" + object_id = "traffic" +} diff --git a/modules/quotas/outputs.tf b/modules/quotas/outputs.tf new file mode 100644 index 0000000..b16a1a3 --- /dev/null +++ b/modules/quotas/outputs.tf @@ -0,0 +1,3 @@ +############################################################################## +# Outputs +############################################################################## diff --git a/modules/quotas/variables.tf b/modules/quotas/variables.tf new file mode 100644 index 0000000..d64a97c --- /dev/null +++ b/modules/quotas/variables.tf @@ -0,0 +1,25 @@ +############################################################################## +# Common variables +############################################################################## + +variable "container_registry_endpoint" { + description = "The endpoint of the IBM Container Registry, eg. https://us.icr.io or https://de.icr.io, to change quotas" + type = string + default = "us.icr.io" + validation { + condition = can(regex("^(private.)?([a-z]{2}[2]?.)?icr.io$", var.container_registry_endpoint)) + error_message = "registry endpoint must match the regular expression \"^(private.)?([a-z]{2}[2]?.)?icr.io$\", see https://cloud.ibm.com/docs/Registry?topic=Registry-registry_overview#registry_regions_global" + } +} + +variable "storage_megabytes" { + description = "Storage quota in megabytes. The value -1 denotes `Unlimited`" + type = number + default = null +} + +variable "traffic_megabytes" { + description = "Traffic quota in megabytes. The value -1 denotes `Unlimited`." + type = number + default = null +} diff --git a/modules/quotas/version.tf b/modules/quotas/version.tf new file mode 100644 index 0000000..ebd695c --- /dev/null +++ b/modules/quotas/version.tf @@ -0,0 +1,15 @@ +terraform { + required_version = ">= 1.0.0" + required_providers { + # Use "greater than or equal to" range in modules + # tflint-ignore: terraform_unused_required_providers + ibm = { + source = "ibm-cloud/ibm" + version = ">= 1.49.0, < 2.0.0" + } + restapi = { + source = "Mastercard/restapi" + version = ">= 1.20.0, <2.0.0" + } + } +} diff --git a/outputs.tf b/outputs.tf index ed93cf6..f9e5bc8 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,8 +1,19 @@ -######################################################################################################################## +# ######################################################################################################################## # Outputs -######################################################################################################################## +# ######################################################################################################################## output "namespace_crn" { description = "CRN representing the namespace" - value = ibm_cr_namespace.cr_namespace.crn + value = var.existing_namespace_name != null ? local.existing_cr_namespace[0].crn : ibm_cr_namespace.cr_namespace[0].crn +} + +output "namespace_name" { + description = "Name of ICR namespace" + value = var.existing_namespace_name != null ? var.existing_namespace_name : var.namespace_name +} + + +output "retention_policy_id" { + description = "ID of retentation policy" + value = var.images_per_repo != 0 ? ibm_cr_retention_policy.cr_retention_policy[0].id : null } diff --git a/reference-architecture/deployable-architecture-icr.svg b/reference-architecture/deployable-architecture-icr.svg new file mode 100644 index 0000000..b2288a1 --- /dev/null +++ b/reference-architecture/deployable-architecture-icr.svg @@ -0,0 +1,4 @@ + + + +IBM Cloud Container RegistryRegionCloudNamespaces \ No newline at end of file diff --git a/solutions/standard/README.md b/solutions/standard/README.md new file mode 100644 index 0000000..a1fc9be --- /dev/null +++ b/solutions/standard/README.md @@ -0,0 +1,12 @@ +# IBM Cloud Container Registry + +This architecture creates or utilizes an existing IBM Container Registry namespace, provides the ability to configure pull traffic limits and storage quotas in megabytes, and allows for upgrading the registry plan to Standard. It ensures efficient management of container image access by regulating data pull volume from the registry and setting storage capacity limits for container images within each registry. + +- A resource group, if existing is not passed in. +- A Container Registry namespace. +- Option to upgrade to `Standard` plan. +- Option to set pull traffic and storage quotas. + +![IBM Container Registry](../../reference-architecture/deployable-architecture-icr.svg) + +:exclamation: **Important:** This solution is not intended to be called by other modules because it contains a provider configuration and 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/standard/catalogValidationValues.json.template b/solutions/standard/catalogValidationValues.json.template new file mode 100644 index 0000000..f48a7e3 --- /dev/null +++ b/solutions/standard/catalogValidationValues.json.template @@ -0,0 +1,3 @@ +{ + "ibmcloud_api_key": $VALIDATION_APIKEY +} diff --git a/solutions/standard/main.tf b/solutions/standard/main.tf new file mode 100644 index 0000000..c7c3324 --- /dev/null +++ b/solutions/standard/main.tf @@ -0,0 +1,54 @@ +locals { + endpoints = { + "ap-north" = "jp.icr.io" + "jp-tok" = "jp.icr.io" + "ap-south" = "au.icr.io" + "au-syd" = "au.icr.io" + "us-south" = "us.icr.io" + "br-sao" = "br.icr.io" + "ca-tor" = "ca.icr.io" + "eu-central" = "de.icr.io" + "eu-es" = "es.icr.io" + "jp-osa" = "jp2.icr.io" + "uk-south" = "uk.icr.io" + "global" = "icr.io" + } +} + +######################################################################################################################## +# Resource group +######################################################################################################################## + +module "resource_group" { + count = var.existing_namespace_name != null ? 0 : 1 + source = "terraform-ibm-modules/resource-group/ibm" + version = "1.1.6" + resource_group_name = var.use_existing_resource_group == false ? (var.prefix != null ? "${var.prefix}-${var.resource_group_name}" : var.resource_group_name) : null + existing_resource_group_name = var.use_existing_resource_group == true ? var.resource_group_name : null +} + +module "namespace" { + providers = { + ibm = ibm.namespace + } + count = var.existing_namespace_name != null ? 0 : 1 + source = "../.." + namespace_name = var.prefix == null ? var.namespace_name : "${var.prefix}-${var.namespace_name}" + resource_group_id = module.resource_group[0].resource_group_id + tags = var.tags + images_per_repo = var.images_per_repo + retain_untagged = var.retain_untagged +} + +module "upgrade_plan" { + count = var.upgrade_to_standard_plan ? 1 : 0 + source = "../../modules/plan" + container_registry_endpoint = var.provider_visibility == "private" ? "private.${local.endpoints[var.namespace_region]}" : local.endpoints[var.namespace_region] +} + +module "set_quota" { + source = "../../modules/quotas" + container_registry_endpoint = var.provider_visibility == "private" ? "private.${local.endpoints[var.namespace_region]}" : local.endpoints[var.namespace_region] + storage_megabytes = var.storage_megabytes + traffic_megabytes = var.traffic_megabytes +} diff --git a/solutions/standard/outputs.tf b/solutions/standard/outputs.tf new file mode 100644 index 0000000..24a7945 --- /dev/null +++ b/solutions/standard/outputs.tf @@ -0,0 +1,20 @@ +############################################################################## +# Outputs +############################################################################## + +output "namespace_crn" { + description = "CRN representing the namespace" + value = var.existing_namespace_name == null ? module.namespace[0].namespace_crn : null +} + +output "namespace_name" { + description = "Name of ICR namespace" + # var.namespace_name will be prefixed if var.prefix is not null + value = var.existing_namespace_name == null ? module.namespace[0].namespace_name : var.existing_namespace_name +} + + +output "cr_retention_policy_id" { + description = "ID of retention policy" + value = var.existing_namespace_name == null ? module.namespace[0].retention_policy_id : null +} diff --git a/solutions/standard/provider.tf b/solutions/standard/provider.tf new file mode 100644 index 0000000..58fea08 --- /dev/null +++ b/solutions/standard/provider.tf @@ -0,0 +1,36 @@ +######################################################################################################################## +# Provider config +######################################################################################################################## + +provider "ibm" { + ibmcloud_api_key = var.ibmcloud_api_key + visibility = var.provider_visibility + +} + +provider "ibm" { + alias = "namespace" + ibmcloud_api_key = var.ibmcloud_api_key + region = var.namespace_region + visibility = var.provider_visibility +} + +# Data source to retrieve token details +data "ibm_iam_auth_token" "token_data" { +} + +# Data source to account settings +data "ibm_iam_account_settings" "iam_account_settings" { +} + +provider "restapi" { + uri = "https:" + write_returns_object = false + create_returns_object = false + debug = false # set to true to show detailed logs, but use carefully as it might print sensitive values. + headers = { + Account = data.ibm_iam_account_settings.iam_account_settings.account_id + Authorization = data.ibm_iam_auth_token.token_data.iam_access_token + Content-Type = "application/json" + } +} diff --git a/solutions/standard/variables.tf b/solutions/standard/variables.tf new file mode 100644 index 0000000..6b06de7 --- /dev/null +++ b/solutions/standard/variables.tf @@ -0,0 +1,98 @@ +######################################################################################################################## +# Common variables +######################################################################################################################## + +variable "ibmcloud_api_key" { + type = string + description = "The IBM Cloud API Key" + sensitive = true +} + +variable "prefix" { + type = string + description = "The prefix to add to all resources that this solution creates." + default = null +} + +variable "provider_visibility" { + description = "Set the visibility value for the IBM terraform provider. Supported values are `public`, `private`, `public-and-private`. [Learn more](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/guides/custom-service-endpoints)" + type = string + default = "private" + + validation { + condition = contains(["public", "private", "public-and-private"], var.provider_visibility) + error_message = "Invalid visibility option. Allowed values are 'public', 'private', or 'public-and-private'." + } +} + +variable "use_existing_resource_group" { + type = bool + description = "Indicates whether to use an existing resource group. If set to 'false', a new resource group will be created." + default = false +} + +variable "resource_group_name" { + type = string + description = "The name of a new or an existing resource group to provision the container registry namespace in. If a value is passed for the prefix input variable, the prefix value is added to the name in the format of -. To use an existing group, set use_existing_resource_group to true." + default = "icr-namespace" +} + +######################################################################################################################## +# Namespace +######################################################################################################################## + +variable "namespace_region" { + type = string + description = "The IBM Cloud region where the container registry namespace and retention policy will be created or where the existing namespace is located." +} + +variable "namespace_name" { + type = string + description = "The name of the container registry namespace to create." + default = "namespace" +} + +variable "existing_namespace_name" { + type = string + description = "The name of an existing namespace." + default = null +} + +variable "tags" { + type = list(string) + description = "Optional list of tags to be added to the IBM container namespace." + default = [] +} + +variable "images_per_repo" { + type = number + default = 0 + description = "Determines how many images are retained in each repository when the retention policy is processed. The value -1 denotes Unlimited (all images are retained). The value 0 denotes no retention policy will be created (default)" +} +variable "retain_untagged" { + type = bool + description = "Determines whether untagged images are retained when the retention policy is processed. Default value is false, means untagged images can be deleted when the policy runs." + default = false +} + +######################################################################################################################## +# Settings +######################################################################################################################## + +variable "upgrade_to_standard_plan" { + description = "Set to true to upgrade container registry to the 'Standard' plan. This action cannot be undone once applied." + type = bool + default = false +} + +variable "storage_megabytes" { + type = number + description = "The storage quota in megabytes for the container registry. Use -1 for unlimited storage. If not specified, then the container registry storage quota is not set." + default = null +} + +variable "traffic_megabytes" { + type = number + description = "The traffic pull quota in megabytes for the container registry. Use -1 for unlimited traffic. If not specified, then the container registry traffic quota is not set." + default = null +} diff --git a/solutions/standard/versions.tf b/solutions/standard/versions.tf new file mode 100644 index 0000000..a2606bc --- /dev/null +++ b/solutions/standard/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 1.3.0" + required_providers { + ibm = { + source = "IBM-Cloud/ibm" + version = "1.70.0" + } + restapi = { + source = "Mastercard/restapi" + version = "1.20.0" + } + } +} diff --git a/tests/existing-resources/README.md b/tests/existing-resources/README.md new file mode 100644 index 0000000..dcb42b6 --- /dev/null +++ b/tests/existing-resources/README.md @@ -0,0 +1,6 @@ +# IBM Container Registry namespace example + +This example creates the following infrastructure: + +- A new resource group, if one is not passed in. +- A new IBM Container Registry namespace. diff --git a/examples/namespace/main.tf b/tests/existing-resources/main.tf similarity index 65% rename from examples/namespace/main.tf rename to tests/existing-resources/main.tf index e53925a..0051bfa 100644 --- a/examples/namespace/main.tf +++ b/tests/existing-resources/main.tf @@ -10,16 +10,7 @@ module "resource_group" { existing_resource_group_name = var.resource_group } -module "namespace" { - source = "../.." - name = "${var.prefix}-namespace" +resource "ibm_cr_namespace" "cr_namespace" { + name = "${var.prefix}-ns" resource_group_id = module.resource_group.resource_group_id - tags = var.resource_tags - images_per_repo = var.images_per_repo - retain_untagged = var.retain_untagged -} - -module "upgrade_plan" { - source = "../..//modules/plan" - container_registry_endpoint = "us.icr.io" } diff --git a/tests/existing-resources/outputs.tf b/tests/existing-resources/outputs.tf new file mode 100644 index 0000000..f8ca5f8 --- /dev/null +++ b/tests/existing-resources/outputs.tf @@ -0,0 +1,18 @@ +############################################################################## +# Outputs +############################################################################## + +output "namespace_crn" { + description = "CRN representing the namespace" + value = ibm_cr_namespace.cr_namespace.crn +} + +output "namespace_name" { + description = "Namespace name" + value = ibm_cr_namespace.cr_namespace.name +} + +output "resource_group_name" { + description = "Resource group name" + value = module.resource_group.resource_group_name +} diff --git a/tests/existing-resources/provider.tf b/tests/existing-resources/provider.tf new file mode 100644 index 0000000..ce91bb8 --- /dev/null +++ b/tests/existing-resources/provider.tf @@ -0,0 +1,33 @@ +######################################################################################################################## +# Provider config +######################################################################################################################## + +provider "ibm" { + ibmcloud_api_key = var.ibmcloud_api_key +} + +provider "ibm" { + alias = "namespace" + ibmcloud_api_key = var.ibmcloud_api_key + region = var.namespace_region +} + +# Data source to retrieve token details +data "ibm_iam_auth_token" "token_data" { +} + +# Data source to account settings +data "ibm_iam_account_settings" "iam_account_settings" { +} + +provider "restapi" { + uri = "https:" + write_returns_object = false + create_returns_object = false + debug = false # set to true to show detailed logs, but use carefully as it might print sensitive values. + headers = { + Account = data.ibm_iam_account_settings.iam_account_settings.account_id + Authorization = data.ibm_iam_auth_token.token_data.iam_access_token + Content-Type = "application/json" + } +} diff --git a/tests/existing-resources/variables.tf b/tests/existing-resources/variables.tf new file mode 100644 index 0000000..2bdec8c --- /dev/null +++ b/tests/existing-resources/variables.tf @@ -0,0 +1,23 @@ +variable "ibmcloud_api_key" { + type = string + description = "The IBM Cloud API Key" + sensitive = true +} + +variable "namespace_region" { + type = string + description = "The IBM Cloud region where the container registry namespace and retention policy will be created or where the existing namespace is located." + default = "us-south" +} + +variable "resource_group" { + type = string + description = "An existing resource group name to use for this example, if unset a new resource group will be created" + default = null +} + +variable "prefix" { + type = string + description = "Prefix to append to all resources created by this example" + default = "test-icr" +} diff --git a/tests/existing-resources/versions.tf b/tests/existing-resources/versions.tf new file mode 100644 index 0000000..802b317 --- /dev/null +++ b/tests/existing-resources/versions.tf @@ -0,0 +1,17 @@ +terraform { + required_version = ">= 1.3.0" + # If your module requires any terraform providers, uncomment the "required_providers" section below and add all required providers. + # 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 { + # Pin to the lowest provider version of the range defined in the main module to ensure lowest version still works + ibm = { + source = "IBM-Cloud/ibm" + version = "1.49.0" + } + restapi = { + source = "Mastercard/restapi" + version = ">= 1.20.0" + } + } +} diff --git a/tests/other_test.go b/tests/other_test.go new file mode 100644 index 0000000..f9844a8 --- /dev/null +++ b/tests/other_test.go @@ -0,0 +1,35 @@ +// 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 ( + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testhelper" +) + +const completeDir = "examples/complete" + +func TestRunCompleteExample(t *testing.T) { + t.Parallel() + + var region = validRegions[rand.Intn(len(validRegions))] + + options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ + Testing: t, + TerraformDir: completeDir, + Prefix: "complete-icr", + ResourceGroup: resourceGroup, + }) + options.TerraformVars = map[string]interface{}{ + "resource_group": resourceGroup, + "namespace_region": region, + "retain_untagged": true, + "prefix": options.Prefix, + } + + 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 fa82bb7..a89dc73 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -2,37 +2,148 @@ package test import ( + "fmt" + "io/fs" + "math/rand" + "os" + "path/filepath" + "strings" "testing" + "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/testhelper" + "github.com/terraform-ibm-modules/ibmcloud-terratest-wrapper/testschematic" ) -const namespaceDir = "examples/namespace" +const solutionStandardDir = "solutions/standard" +const resourceGroup = "geretain-test-icr" -func setupOptions(t *testing.T, prefix string, dir string) *testhelper.TestOptions { - options := testhelper.TestOptionsDefaultWithVars(&testhelper.TestOptions{ - Testing: t, - TerraformDir: dir, - Prefix: prefix, +// ICR Plan is non revertible once upgraded to standard +var validRegions = []string{ + "br-sao", + "us-south", + "ap-north", +} + +type tarIncludePatterns struct { + excludeDirs []string + + includeFiletypes []string + + includeDirs []string +} + +func getTarIncludePatternsRecursively(dir string, dirsToExclude []string, fileTypesToInclude []string) ([]string, error) { + r := tarIncludePatterns{dirsToExclude, fileTypesToInclude, nil} + err := filepath.WalkDir(dir, func(path string, entry fs.DirEntry, err error) error { + return walk(&r, path, entry, err) }) - return options + if err != nil { + fmt.Println("error") + return r.includeDirs, err + } + return r.includeDirs, nil } -func TestRunNamespaceExample(t *testing.T) { +func walk(r *tarIncludePatterns, s string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + for _, excludeDir := range r.excludeDirs { + if strings.Contains(s, excludeDir) { + return nil + } + } + if s == ".." { + r.includeDirs = append(r.includeDirs, "*.tf") + return nil + } + for _, includeFiletype := range r.includeFiletypes { + r.includeDirs = append(r.includeDirs, strings.ReplaceAll(s+"/*"+includeFiletype, "../", "")) + } + } + return nil +} + +func TestRunStandardSolutionSchematics(t *testing.T) { t.Parallel() - options := setupOptions(t, "icr", namespaceDir) + var region = validRegions[rand.Intn(len(validRegions))] + + excludeDirs := []string{ + ".terraform", + ".docs", + ".github", + ".git", + ".idea", + "common-dev-assets", + "examples", + "tests", + "reference-architectures", + } + includeFiletypes := []string{ + ".tf", + ".yaml", + ".py", + ".tpl", + ".sh", + } + + tarIncludePatterns, recurseErr := getTarIncludePatternsRecursively("..", excludeDirs, includeFiletypes) - output, err := options.RunTestConsistency() + // if error producing tar patterns (very unexpected) fail test immediately + require.NoError(t, recurseErr, "Schematic Test had unexpected error traversing directory tree") + options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ + Testing: t, + TarIncludePatterns: tarIncludePatterns, + TemplateFolder: solutionStandardDir, + Prefix: "std-icr-da", + Tags: []string{"test-schematic"}, + DeleteWorkspaceOnFail: false, + WaitJobCompleteMinutes: 60, + }) + + options.TerraformVars = []testschematic.TestSchematicTerraformVar{ + {Name: "ibmcloud_api_key", Value: options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], DataType: "string", Secure: true}, + {Name: "resource_group_name", Value: options.Prefix, DataType: "string"}, + {Name: "use_existing_resource_group", Value: false, DataType: "bool"}, + {Name: "namespace_region", Value: region, DataType: "string"}, + {Name: "prefix", Value: options.Prefix, DataType: "string"}, + {Name: "upgrade_to_standard_plan", Value: true, DataType: "bool"}, + {Name: "storage_megabytes", Value: 499, DataType: "number"}, + {Name: "traffic_megabytes", Value: 5*1024 - 1, DataType: "number"}, + } + err := options.RunSchematicTest() assert.Nil(t, err, "This should not have errored") - assert.NotNil(t, output, "Expected some output") } func TestRunUpgradeExample(t *testing.T) { t.Parallel() - options := setupOptions(t, "icr-upg", namespaceDir) + var region = validRegions[rand.Intn(len(validRegions))] + + options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ + Testing: t, + TerraformDir: solutionStandardDir, + Prefix: "upg-icr-da", + ResourceGroup: resourceGroup, + }) + options.TerraformVars = map[string]interface{}{ + "use_existing_resource_group": true, + "resource_group_name": resourceGroup, + "namespace_region": region, + "prefix": options.Prefix, + "upgrade_to_standard_plan": true, + "storage_megabytes": 499, + "traffic_megabytes": 5*1024 - 1, + "provider_visibility": "public", + } output, err := options.RunTestUpgrade() if !options.UpgradeTestSkipped { @@ -40,3 +151,75 @@ func TestRunUpgradeExample(t *testing.T) { assert.NotNil(t, output, "Expected some output") } } + +func TestRunExistingResourcesExample(t *testing.T) { + t.Parallel() + + // ------------------------------------------------------------------------------------ + // Provision icr namespace + // ------------------------------------------------------------------------------------ + + prefix := fmt.Sprintf("cr-%s", strings.ToLower(random.UniqueId())) + realTerraformDir := "./existing-resources" + tempTerraformDir, _ := files.CopyTerraformFolderToTemp(realTerraformDir, fmt.Sprintf(prefix+"-%s", strings.ToLower(random.UniqueId()))) + + var region = validRegions[rand.Intn(len(validRegions))] + + // 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]interface{}{ + "prefix": prefix, + "namespace_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 existing resource failed") + } else { + options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ + Testing: t, + TerraformDir: solutionStandardDir, + Prefix: "upg-icr-da", + ResourceGroup: resourceGroup, + }) + options.TerraformVars = map[string]interface{}{ + "existing_namespace_name": terraform.Output(t, existingTerraformOptions, "namespace_name"), + "use_existing_resource_group": true, + "resource_group_name": terraform.Output(t, existingTerraformOptions, "resource_group_name"), + "namespace_region": region, + "prefix": options.Prefix, + "upgrade_to_standard_plan": true, + "storage_megabytes": 499, + "traffic_megabytes": 5*1024 - 1, + "provider_visibility": "public", + } + + output, err := options.RunTestConsistency() + assert.Nil(t, err, "This should not have errored") + assert.NotNil(t, output, "Expected some output") + } + + // 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)") + } +} diff --git a/variables.tf b/variables.tf index b70785f..72a2bd7 100644 --- a/variables.tf +++ b/variables.tf @@ -1,10 +1,21 @@ -variable "name" { - description = "Name of the container registry namespace" +variable "namespace_name" { + description = "Name of the container registry namespace, if var.existing_namespace_name is not inputted, a new namespace will be created in a region set by provider." type = string validation { - condition = can(regex("^[a-z0-9]+[a-z0-9_-]+[a-z0-9]+$", var.name)) + condition = can(regex("^[a-z0-9]+[a-z0-9_-]+[a-z0-9]+$", var.namespace_name)) error_message = "container registry namespace name should match regex /^[a-z0-9]+[a-z0-9_-]+[a-z0-9]+$/" } + + validation { + condition = (length(var.namespace_name) >= 4 && length(var.namespace_name) <= 30) + error_message = "namespace name must contain from 4 to 30 characters " + } +} + +variable "existing_namespace_name" { + type = string + description = "The name of an existing namespace. Required if 'namespace_name' is not provided." + default = null } variable "resource_group_id" {