diff --git a/README.md b/README.md index 66e3bfec..6c66ab01 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ This module provisions the IBM Cloud Code Engine fully managed and serverless pl * [secret](./modules/secret) * [Examples](./examples) *
Apps example
Deploy to IBM Cloud button
+ *
Build example
Deploy to IBM Cloud button
*
Jobs example
Deploy to IBM Cloud button
* [Contributing](#contributing) @@ -157,7 +158,7 @@ No resources. |------|-------------|------|---------|:--------:| | [apps](#input\_apps) | A map of code engine apps to be created. |
map(object({
image_reference = string
image_secret = optional(string)
run_env_variables = optional(list(object({
type = optional(string)
name = optional(string)
value = optional(string)
prefix = optional(string)
key = optional(string)
reference = optional(string)
})))
run_volume_mounts = optional(list(object({
mount_path = string
reference = string
name = optional(string)
type = string
})))
image_port = optional(number)
managed_domain_mappings = optional(string)
run_arguments = optional(list(string))
run_as_user = optional(number)
run_commands = optional(list(string))
run_service_account = optional(string)
scale_concurrency = optional(number)
scale_concurrency_target = optional(number)
scale_cpu_limit = optional(string)
scale_ephemeral_storage_limit = optional(string)
scale_initial_instances = optional(number)
scale_max_instances = optional(number)
scale_memory_limit = optional(string)
scale_min_instances = optional(number)
scale_request_timeout = optional(number)
scale_down_delay = optional(number)
}))
| `{}` | no | | [bindings](#input\_bindings) | A map of code engine bindings to be created. |
map(object({
secret_name = string
components = list(object({
name = string
resource_type = string
}))
}))
| `{}` | no | -| [builds](#input\_builds) | A map of code engine builds to be created. Requires 'ibmcloud\_api\_key' to be set for authentication and execution. |
map(object({
output_image = string
output_secret = string # pragma: allowlist secret
source_url = string
strategy_type = string
source_context_dir = optional(string)
source_revision = optional(string)
source_secret = optional(string)
source_type = optional(string)
strategy_size = optional(string)
strategy_spec_file = optional(string)
timeout = optional(number)
}))
| `{}` | no | +| [builds](#input\_builds) | A map of code engine builds to be created. Requires 'ibmcloud\_api\_key' to be set for authentication and execution. |
map(object({
output_image = optional(string)
output_secret = optional(string) # pragma: allowlist secret
source_url = string
strategy_type = optional(string)
source_context_dir = optional(string)
source_revision = optional(string)
source_secret = optional(string)
source_type = optional(string)
strategy_size = optional(string)
strategy_spec_file = optional(string)
timeout = optional(number)
region = optional(string)
container_registry_namespace = optional(string)
prefix = optional(string)
}))
| `{}` | no | | [cbr\_rules](#input\_cbr\_rules) | The context-based restrictions rule to create. Only one rule is allowed. |
list(object({
description = string
account_id = string
rule_contexts = list(object({
attributes = optional(list(object({
name = string
value = string
}))) }))
enforcement_mode = string
operations = optional(list(object({
api_types = list(object({
api_type_id = string
}))
})))
}))
| `[]` | no | | [config\_maps](#input\_config\_maps) | A map of code engine config maps to be created. |
map(object({
data = map(string)
}))
| `{}` | no | | [domain\_mappings](#input\_domain\_mappings) | A map of code engine domain mappings to be created. |
map(object({
tls_secret = string # pragma: allowlist secret
components = list(object({
name = string
resource_type = string
}))
}))
| `{}` | no | diff --git a/examples/build/README.md b/examples/build/README.md new file mode 100644 index 00000000..6064020c --- /dev/null +++ b/examples/build/README.md @@ -0,0 +1,8 @@ +# Build example + +An end-to-end apps example that will provision the following: +- A new resource group if one is not passed in. +- Code Engine project +- Code Engine build +- Code Engine registry secret +- Container registry namespace diff --git a/tests/resources/main.tf b/examples/build/main.tf similarity index 62% rename from tests/resources/main.tf rename to examples/build/main.tf index 46bee3a1..1e4666e3 100644 --- a/tests/resources/main.tf +++ b/examples/build/main.tf @@ -4,28 +4,27 @@ module "resource_group" { source = "terraform-ibm-modules/resource-group/ibm" - version = "1.4.0" + version = "1.3.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 } ######################################################################################################################## -# Secrets Manager resources +# Code Engine instance ######################################################################################################################## -data "ibm_sm_public_certificate" "public_certificate" { - # depends_on = [resource.ibm_sm_public_certificate.secrets_manager_public_certificate] - instance_id = var.existing_sm_instance_guid - region = var.existing_sm_instance_region - secret_id = var.existing_cert_secret_id -} - - -module "namespace" { - source = "terraform-ibm-modules/container-registry/ibm" - version = "2.3.4" - namespace_name = "${var.prefix}-namespace" +module "code_engine" { + source = "../.." + ibmcloud_api_key = var.ibmcloud_api_key resource_group_id = module.resource_group.resource_group_id - images_per_repo = 1 + project_name = "${var.prefix}-project" + builds = { + "${var.prefix}-build1" = { + source_url = "https://github.com/IBM/CodeEngine" + container_registry_namespace = "cr-ce" + prefix = var.prefix + region = var.region + } + } } diff --git a/examples/build/outputs.tf b/examples/build/outputs.tf new file mode 100644 index 00000000..7fad9d03 --- /dev/null +++ b/examples/build/outputs.tf @@ -0,0 +1,24 @@ +######################################################################################################################## +# Outputs +######################################################################################################################## + +output "resource_group_id" { + description = "The id of created resource group." + value = module.resource_group.resource_group_id +} + +output "resource_group_name" { + description = "The name of created resource group." + value = module.resource_group.resource_group_name +} + +output "project_id" { + description = "ID of the created code engine project." + value = module.code_engine.project_id +} + +output "build" { + description = "Configuration of the created code engine domain mapping." + value = module.code_engine.build + sensitive = true +} diff --git a/tests/resources/provider.tf b/examples/build/provider.tf similarity index 85% rename from tests/resources/provider.tf rename to examples/build/provider.tf index 292d3202..84b69850 100644 --- a/tests/resources/provider.tf +++ b/examples/build/provider.tf @@ -4,5 +4,5 @@ provider "ibm" { ibmcloud_api_key = var.ibmcloud_api_key - region = var.existing_sm_instance_region + region = var.region } diff --git a/tests/resources/variables.tf b/examples/build/variables.tf similarity index 50% rename from tests/resources/variables.tf rename to examples/build/variables.tf index 5206953f..7fbea71e 100644 --- a/tests/resources/variables.tf +++ b/examples/build/variables.tf @@ -8,10 +8,16 @@ variable "ibmcloud_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 = "ce-prj" + default = "ce-build" } variable "resource_group" { @@ -19,25 +25,3 @@ variable "resource_group" { 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 } - -############################################################## -# Secret Manager -############################################################## - -variable "existing_sm_instance_guid" { - type = string - description = "An existing Secrets Manager GUID. The existing Secret Manager instance must have private certificate engine configured. If not provided an new instance will be provisioned." - default = null -} - -variable "existing_sm_instance_region" { - type = string - description = "Required if value is passed into `var.existing_sm_instance_guid`." - default = null -} - -variable "existing_cert_secret_id" { - type = string - description = "Required if value is passed into `var.existing_sm_instance_guid`." - default = null -} diff --git a/tests/resources/version.tf b/examples/build/version.tf similarity index 82% rename from tests/resources/version.tf rename to examples/build/version.tf index 893cc71e..59c11feb 100644 --- a/tests/resources/version.tf +++ b/examples/build/version.tf @@ -1,12 +1,12 @@ terraform { - required_version = ">= 1.3.0" + 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 (jobs examples). required_providers { ibm = { source = "IBM-Cloud/ibm" - version = ">= 1.70.0, < 2.0.0" + version = ">= 1.79.0, < 2.0.0" } } } diff --git a/main.tf b/main.tf index 226ed328..f5b783ac 100644 --- a/main.tf +++ b/main.tf @@ -99,24 +99,27 @@ module "secret" { # Code Engine Build ############################################################################## module "build" { - depends_on = [module.secret] - source = "./modules/build" - for_each = var.builds - ibmcloud_api_key = var.ibmcloud_api_key - existing_resource_group_id = var.resource_group_id - project_id = local.project_id - name = each.key - output_image = each.value.output_image - output_secret = each.value.output_secret - source_url = each.value.source_url - strategy_type = each.value.strategy_type - source_context_dir = each.value.source_context_dir - source_revision = each.value.source_revision - source_secret = each.value.source_secret - source_type = each.value.source_type - strategy_size = each.value.strategy_size - strategy_spec_file = each.value.strategy_spec_file - timeout = each.value.timeout + depends_on = [module.secret] + source = "./modules/build" + for_each = var.builds + ibmcloud_api_key = var.ibmcloud_api_key + existing_resource_group_id = var.resource_group_id + project_id = local.project_id + name = each.key + output_image = each.value.output_image + output_secret = each.value.output_secret + source_url = each.value.source_url + strategy_type = each.value.strategy_type + source_context_dir = each.value.source_context_dir + source_revision = each.value.source_revision + source_secret = each.value.source_secret + source_type = each.value.source_type + strategy_size = each.value.strategy_size + strategy_spec_file = each.value.strategy_spec_file + timeout = each.value.timeout + region = each.value.region + container_registry_namespace = each.value.container_registry_namespace + prefix = each.value.prefix } ############################################################################## diff --git a/modules/build/README.md b/modules/build/README.md index cd4ef77e..db81bd75 100644 --- a/modules/build/README.md +++ b/modules/build/README.md @@ -40,7 +40,11 @@ You need the following permissions to run this module. ### Modules -No modules. +| Name | Source | Version | +|------|--------|---------| +| [cr\_endpoint](#module\_cr\_endpoint) | terraform-ibm-modules/container-registry/ibm//modules/endpoint | 2.1.0 | +| [cr\_namespace](#module\_cr\_namespace) | terraform-ibm-modules/container-registry/ibm | 2.1.0 | +| [secret](#module\_secret) | ../../modules/secret | n/a | ### Resources @@ -54,21 +58,24 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [container\_registry\_api\_key](#input\_container\_registry\_api\_key) | The API key for the container registry in the target account. This is only used if 'output\_secret' is not set and a new registry secret needs to be created. If not provided, the IBM Cloud API key (ibmcloud\_api\_key) will be used instead. | `string` | `null` | no | +| [container\_registry\_namespace](#input\_container\_registry\_namespace) | The name of the namespace to create in IBM Cloud Container Registry for organizing container images. Must be set if 'output\_image' is not set. If a prefix input variable is specified, the prefix is added to the name in the `-` format. | `string` | `null` | no | | [existing\_resource\_group\_id](#input\_existing\_resource\_group\_id) | The ID of an existing resource group where build will be provisioned. This must be the same resource group in which the code engine project was created. | `string` | n/a | yes | | [ibmcloud\_api\_key](#input\_ibmcloud\_api\_key) | The IBM Cloud API key. | `string` | n/a | yes | | [name](#input\_name) | The name of the build. | `string` | n/a | yes | -| [output\_image](#input\_output\_image) | The name of the image. | `string` | n/a | yes | -| [output\_secret](#input\_output\_secret) | The secret that is required to access the image registry. | `string` | n/a | yes | +| [output\_image](#input\_output\_image) | A container image can be identified by a container image reference with the following structure: registry / namespace / repository:tag. [Learn more](https://cloud.ibm.com/docs/codeengine?topic=codeengine-getting-started).

If not provided, the value will be derived from the 'container\_registry\_namespace' input variable, which must not be null in that case. | `string` | `null` | no | +| [output\_secret](#input\_output\_secret) | The name of the Code Engine secret that contains an API key to access the IBM Cloud Container Registry.
The API key stored in this secret must have push permissions for the specified container registry namespace.
If this secret is not provided, a Code Engine secret named `-` will be created automatically. Its value will be taken from 'container\_registry\_api\_key' if set, otherwise from 'ibmcloud\_api\_key'. | `string` | `null` | no | +| [prefix](#input\_prefix) | Prefix appended to the container registry namespace and registry secret if created. | `string` | `null` | no | | [project\_id](#input\_project\_id) | The ID of the project where build will be created. | `string` | n/a | yes | | [region](#input\_region) | The region in which to provision the build. This must be the same region in which the code engine project was created. | `string` | `"us-south"` | no | | [source\_context\_dir](#input\_source\_context\_dir) | The directory in the repository that contains the buildpacks file or the Dockerfile. | `string` | `null` | no | | [source\_revision](#input\_source\_revision) | Commit, tag, or branch in the source repository to pull. | `string` | `null` | no | -| [source\_secret](#input\_source\_secret) | The name of the secret that is used access the repository source. If the var.source\_type value is `local`, this field must be omitted. | `string` | `null` | no | -| [source\_type](#input\_source\_type) | Specifies the type of source to determine if your build source is in a repository or based on local source code. | `string` | `null` | no | +| [source\_secret](#input\_source\_secret) | The name of the secret that is used access the repository source. If the var.source\_type value is `local`, this input must be omitted. | `string` | `null` | no | +| [source\_type](#input\_source\_type) | Specifies the type of source to determine if your build source is in a repository or based on local source code. If the value is `local`, then 'source\_secret' input must be omitted. | `string` | `null` | no | | [source\_url](#input\_source\_url) | The URL of the code repository. | `string` | n/a | yes | | [strategy\_size](#input\_strategy\_size) | The size for the build, which determines the amount of resources used. | `string` | `null` | no | | [strategy\_spec\_file](#input\_strategy\_spec\_file) | The path to the specification file that is used for build strategies for building an image. | `string` | `null` | no | -| [strategy\_type](#input\_strategy\_type) | The strategy to use for building the image. | `string` | n/a | yes | +| [strategy\_type](#input\_strategy\_type) | The strategy to use for building the image. | `string` | `"dockerfile"` | no | | [timeout](#input\_timeout) | The maximum amount of time, in seconds, that can pass before the build must succeed or fail. | `number` | `600` | no | ### Outputs diff --git a/modules/build/main.tf b/modules/build/main.tf index 0efb886a..59cb5338 100644 --- a/modules/build/main.tf +++ b/modules/build/main.tf @@ -4,11 +4,15 @@ # Create Code Engine build ############################################################################## +locals { + prefix = var.prefix != null ? (trimspace(var.prefix) != "" ? "${var.prefix}-" : "") : "" +} + resource "ibm_code_engine_build" "ce_build" { project_id = var.project_id name = var.name - output_image = var.output_image - output_secret = var.output_secret + output_image = local.output_image + output_secret = var.output_secret != null ? var.output_secret : module.secret[0].name source_url = var.source_url source_context_dir = var.source_context_dir source_revision = var.source_revision @@ -39,3 +43,48 @@ resource "terraform_data" "run_build" { } } } + + +############################################################################## +# Container Registry +############################################################################## + +locals { + # Determine if we need to create the container registry namespace or not + create_cr_namespace = var.output_image == null && var.container_registry_namespace != null ? true : false + # Determine the container image reference based on whether we create the namespace or not + image_container = local.create_cr_namespace ? "${module.cr_endpoint.container_registry_endpoint_private}/${module.cr_namespace[0].namespace_name}" : null + # Determine the final image reference to use: either the newly created image or the user-provided output_image + output_image = local.create_cr_namespace ? "${local.image_container}/${var.name}" : var.output_image +} + +module "cr_namespace" { + count = local.create_cr_namespace ? 1 : 0 + source = "terraform-ibm-modules/container-registry/ibm" + version = "2.1.0" + namespace_name = "${local.prefix}${var.container_registry_namespace}" + resource_group_id = var.existing_resource_group_id +} + +module "cr_endpoint" { + source = "terraform-ibm-modules/container-registry/ibm//modules/endpoint" + version = "2.1.0" + region = var.region +} + +############################################################################## +# Code Engine Secret +############################################################################## + +module "secret" { + count = var.output_secret == null ? 1 : 0 + source = "../../modules/secret" + project_id = var.project_id + name = "${local.prefix}registry-access-secret" + data = { + password = var.container_registry_api_key != null ? var.container_registry_api_key : var.ibmcloud_api_key, + username = "iamapikey", + server = module.cr_endpoint.container_registry_endpoint_private + } + format = "registry" +} diff --git a/modules/build/variables.tf b/modules/build/variables.tf index c4828f17..d215b290 100644 --- a/modules/build/variables.tf +++ b/modules/build/variables.tf @@ -8,6 +8,12 @@ variable "ibmcloud_api_key" { sensitive = true } +variable "prefix" { + type = string + description = "Prefix appended to the container registry namespace and registry secret if created." + default = null +} + variable "existing_resource_group_id" { description = "The ID of an existing resource group where build will be provisioned. This must be the same resource group in which the code engine project was created." type = string @@ -24,18 +30,19 @@ variable "name" { } variable "output_image" { - description = "The name of the image." - type = string -} + description = <-` will be created automatically. Its value will be taken from 'container_registry_api_key' if set, otherwise from 'ibmcloud_api_key'. +EOT + type = string + default = null + + validation { + condition = var.output_secret == null || var.container_registry_api_key == null + error_message = "'output_secret' and 'container_registry_api_key' cannot both be set. Provide only one." + } +} + +variable "container_registry_api_key" { + type = string + description = "The API key for the container registry in the target account. This is only used if 'output_secret' is not set and a new registry secret needs to be created. If not provided, the IBM Cloud API key (ibmcloud_api_key) will be used instead." + sensitive = true + default = null +} diff --git a/tests/pr_test.go b/tests/pr_test.go index 191c8e2a..b17a1824 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -16,6 +16,7 @@ import ( const resourceGroup = "geretain-test-resources" const appsExampleDir = "examples/apps" const jobsExampleDir = "examples/jobs" +const buildExampleDir = "examples/build" // Define a struct with fields that match the structure of the YAML data const yamlLocation = "../common-dev-assets/common-go-assets/common-permanent-resources.yaml" @@ -61,7 +62,7 @@ func setupAppsExampleOptions(t *testing.T, prefix string, terraformDir string) * options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ Testing: t, TemplateFolder: appsExampleDir, - Prefix: "ce-apps", + Prefix: prefix, TarIncludePatterns: []string{ "*.tf", appsExampleDir + "/*.tf", @@ -134,3 +135,58 @@ func TestRunJobsUpgradeExample(t *testing.T) { assert.NotNil(t, output, "Expected some output") } } + +func TestRunBuildExampleInSchematics(t *testing.T) { + t.Parallel() + + options := testschematic.TestSchematicOptionsDefault(&testschematic.TestSchematicOptions{ + Testing: t, + TemplateFolder: buildExampleDir, + Prefix: "ce-b-shem", + TarIncludePatterns: []string{ + "*.tf", + buildExampleDir + "/*.tf", + "modules/app/*.tf", + "modules/binding/*.tf", + "modules/config_map/*.tf", + "modules/project/*.tf", + "modules/secret/*.tf", + "modules/build/*.tf", + "modules/build/scripts/build-run.sh", + "modules/domain_mapping/*.tf", + "modules/job/*.tf", + }, + ResourceGroup: resourceGroup, + 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", Value: options.ResourceGroup, DataType: "string"}, + {Name: "prefix", Value: options.Prefix + "-b", DataType: "string"}, + {Name: "region", Value: options.Region, DataType: "string"}, + } + + err := options.RunSchematicTest() + assert.Nil(t, err, "This should not have errored") +} + +func TestRunBuildExample(t *testing.T) { + + options := testhelper.TestOptionsDefault(&testhelper.TestOptions{ + Testing: t, + TerraformDir: buildExampleDir, + Prefix: "ce-build", + ResourceGroup: resourceGroup, + }) + options.TerraformVars = map[string]interface{}{ + "resource_group": resourceGroup, + "prefix": options.Prefix, + "ibmcloud_api_key": options.RequiredEnvironmentVars["TF_VAR_ibmcloud_api_key"], + } + + 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 deleted file mode 100644 index 4bb3621d..00000000 --- a/tests/resources/README.md +++ /dev/null @@ -1 +0,0 @@ -The terraform code in this directory is used by the existing resource test in tests/pr_test.go diff --git a/tests/resources/outputs.tf b/tests/resources/outputs.tf deleted file mode 100644 index 88c0e756..00000000 --- a/tests/resources/outputs.tf +++ /dev/null @@ -1,19 +0,0 @@ -######################################################################################################################## -# Outputs -######################################################################################################################## -output "tls_cert" { - value = format("%s%s", data.ibm_sm_public_certificate.public_certificate.certificate, data.ibm_sm_public_certificate.public_certificate.intermediate) - sensitive = true - description = "The TLS certificate." -} - -output "tls_key" { - value = data.ibm_sm_public_certificate.public_certificate.private_key - sensitive = true - description = "The TLS private key." -} - -output "cr_name" { - value = module.namespace.namespace_name - description = "The name of the container registry namespace." -} diff --git a/variables.tf b/variables.tf index 1be6b049..928436e3 100644 --- a/variables.tf +++ b/variables.tf @@ -145,17 +145,20 @@ variable "secrets" { variable "builds" { description = "A map of code engine builds to be created. Requires 'ibmcloud_api_key' to be set for authentication and execution." type = map(object({ - output_image = string - output_secret = string # pragma: allowlist secret - source_url = string - strategy_type = string - source_context_dir = optional(string) - source_revision = optional(string) - source_secret = optional(string) - source_type = optional(string) - strategy_size = optional(string) - strategy_spec_file = optional(string) - timeout = optional(number) + output_image = optional(string) + output_secret = optional(string) # pragma: allowlist secret + source_url = string + strategy_type = optional(string) + source_context_dir = optional(string) + source_revision = optional(string) + source_secret = optional(string) + source_type = optional(string) + strategy_size = optional(string) + strategy_spec_file = optional(string) + timeout = optional(number) + region = optional(string) + container_registry_namespace = optional(string) + prefix = optional(string) })) default = {} }