Skip to content

feat: add a new feature to run builds #201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ibm_catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,9 @@
{
"key": "builds"
},
{
"key": "container_registry_namespace"
},
{
"key": "domain_mappings"
},
Expand Down
2 changes: 2 additions & 0 deletions modules/build/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,6 @@ No modules.
| <a name="output_build_id"></a> [build\_id](#output\_build\_id) | The ID of the created code engine build. |
| <a name="output_id"></a> [id](#output\_id) | The unique identifier of the created code engine build. |
| <a name="output_name"></a> [name](#output\_name) | The name of the created code engine build. |
| <a name="output_output_image"></a> [output\_image](#output\_output\_image) | The container image reference of the created code engine build. |
| <a name="output_output_secret"></a> [output\_secret](#output\_output\_secret) | The registry secret of the created code engine build. |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
10 changes: 10 additions & 0 deletions modules/build/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@ output "name" {
description = "The name of the created code engine build."
value = resource.ibm_code_engine_build.ce_build.name
}

output "output_image" {
description = "The container image reference of the created code engine build."
value = resource.ibm_code_engine_build.ce_build.output_image
}

output "output_secret" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might not have caught this last time. Is this an actual secret or is it the ID of a secret? Should it be marked sensitive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is just a name of a secret, so we are good here

build = {
  "build-adn-3" = {
    "build_id" = "b522ec47-a1a8-4cf1-8c28-fa2ab28ffa5f"
    "id" = "c8e9d124-f0ab-4f36-b09d-a115273005a0/build-adn-3"
    "name" = "build-adn-3"
    "output_image" = "private.us.icr.io/andrej-test/build-adn-3"
    "output_secret" = "andrejsecret3"
  }

description = "The registry secret of the created code engine build."
value = resource.ibm_code_engine_build.ce_build.output_secret
}
22 changes: 21 additions & 1 deletion solutions/project/DA-inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The `builds` input variable allows you to provide details of the of builds which

### Options for Builds

- `output_image` (required): The name of the image.
- `output_image` (optional): The name of the image. A container image can be identified by a container image reference with the following structure: registry / namespace / repository : tag. If not provided, the name is automatically build using region registry / container_registry_namespace input / build name.
- `output_secret` (required): The secret that is required to access the image registry.
- `source_url` (required): The URL of the code repository.
- `strategy_type` (required): Specifies the type of source to determine if your build source is in a repository or based on local source code.
Expand Down Expand Up @@ -118,10 +118,30 @@ The `secrets` input variable allows you to provide a method to include sensitive
### Example for Secrets

```hcl
# generic secret
{
"your-secret-name" = {
format = "generic"
data = { "key_1" : "value_1", "key_2" : "value_2" }
}
}

# registry secret
"registry_secret_name" = {
format = "registry"
optional("data") = {
"server" = "private.us.icr.io",
"username" = "iamapikey",
"password" = iam_api_key, # pragma: allowlist secret
}
}

# private repository
"private_repo" = {
format = "generic"
"data" = {
"password" = github_token, # pragma: allowlist secret
"username" = github_user
}
}
```
83 changes: 81 additions & 2 deletions solutions/project/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,54 @@ module "project" {
##############################################################################
# Code Engine Build
##############################################################################
locals {
registry_region_result = data.external.container_registry_region.result
registry = lookup(local.registry_region_result, "registry", null)
container_registry = local.registry != null ? "private.${local.registry}" : null
registry_region_error = lookup(local.registry_region_result, "error", null)

# This will cause Terraform to fail if "error" is present in the external script output executed as a part of container_registry_region
# tflint-ignore: terraform_unused_declarations
fail_if_registry_region_error = local.registry_region_error != null ? tobool("Registry region script failed: ${local.registry_region_error}") : null

# if no build defines a container image reference (output_image), a new container registry namespace must be created using container_registry_namespace.
any_missing_output_image = anytrue([
for build in values(var.builds) :
!contains(keys(build), "output_image") || build.output_image == null
])
image_container = local.any_missing_output_image && local.container_registry != null ? "${local.container_registry}/${resource.ibm_cr_namespace.my_namespace[0].name}" : ""

# if output_image not exists then a new created container image reference
updated_builds = {
for name, build in var.builds :
name => merge(
build,
{
output_image = coalesce(build.output_image, "${local.image_container}/${name}")
}
)
}
}

resource "ibm_cr_namespace" "my_namespace" {
count = local.any_missing_output_image && var.container_registry_namespace != null ? 1 : 0
name = var.container_registry_namespace
}

data "external" "container_registry_region" {
program = ["bash", "${path.module}/scripts/get-cr-region.sh"]

query = {
RESOURCE_GROUP_ID = module.resource_group.resource_group_id
REGION = var.region
IBMCLOUD_API_KEY = var.ibmcloud_api_key
}
}

module "build" {
depends_on = [module.secret]
source = "../../modules/build"
for_each = var.builds
for_each = local.updated_builds
project_id = module.project.project_id
name = each.key
output_image = each.value.output_image
Expand All @@ -43,7 +87,21 @@ module "build" {
strategy_size = each.value.strategy_size
strategy_spec_file = each.value.strategy_spec_file
timeout = each.value.timeout
}

resource "null_resource" "run_build" {
depends_on = [module.build]
provisioner "local-exec" {
interpreter = ["/bin/bash", "-c"]
command = "${path.module}/scripts/build-run.sh"
environment = {
IBMCLOUD_API_KEY = var.ibmcloud_api_key
RESOURCE_GROUP_ID = module.resource_group.resource_group_id
CE_PROJECT_NAME = module.project.name
REGION = var.region
BUILDS = join(" ", keys(local.updated_builds))
}
}
}

##############################################################################
Expand Down Expand Up @@ -72,9 +130,30 @@ module "config_map" {
##############################################################################
# Code Engine Secret
##############################################################################
locals {
# if the secret is a registry type, inject generated credentials (username, password, server) if they're not already provided.
secrets = {
for name, secret in var.secrets :
name => merge(
secret,
{
data = (
secret.format == "registry"
? merge(secret.data, {
password = coalesce(secret.data.password, var.ibmcloud_api_key),
username = coalesce(secret.data.username, "iamapikey"),
server = coalesce(secret.data.server, local.container_registry)
})
: secret.data
)
}
)
}
}

module "secret" {
source = "../../modules/secret"
for_each = var.secrets
for_each = local.secrets
project_id = module.project.project_id
name = each.key
data = sensitive(each.value.data)
Expand Down
38 changes: 38 additions & 0 deletions solutions/project/scripts/build-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash
set -e

if [[ -z "${IBMCLOUD_API_KEY}" ]]; then
echo "IBMCLOUD_API_KEY is required" >&2
exit 1
fi

if [[ -z "${RESOURCE_GROUP_ID}" ]]; then
echo "RESOURCE_GROUP_ID is required" >&2
exit 1
fi

if [[ -z "${CE_PROJECT_NAME}" ]]; then
echo "CE_PROJECT_NAME is required" >&2
exit 1
fi

if [[ -z "${REGION}" ]]; then
echo "REGION is required" >&2
exit 1
fi

if [[ -z "${BUILDS}" ]]; then
echo "BUILDS is required" >&2
exit 1
fi

ibmcloud login -r "${REGION}" -g "${RESOURCE_GROUP_ID}" --quiet

# selecet the right code engine project
ibmcloud ce project select -n "${CE_PROJECT_NAME}"

# run a build for all builds
for build in $BUILDS; do
echo "$build"
ibmcloud ce buildrun submit --build "$build"
done
39 changes: 39 additions & 0 deletions solutions/project/scripts/get-cr-region.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash
set -euo pipefail

INPUT=$(cat)
REGION=$(echo "$INPUT" | jq -r '.REGION')
RESOURCE_GROUP_ID=$(echo "$INPUT" | jq -r '.RESOURCE_GROUP_ID')
IBMCLOUD_API_KEY=$(echo "$INPUT" | jq -r '.IBMCLOUD_API_KEY')
export IBMCLOUD_API_KEY

if [[ -z "${IBMCLOUD_API_KEY}" || "${IBMCLOUD_API_KEY}" == "null" ]]; then
echo '{"error": "IBMCLOUD_API_KEY is required"}'
exit 0
fi

if [[ -z "${RESOURCE_GROUP_ID}" || "${RESOURCE_GROUP_ID}" == "null" ]]; then
echo '{"error": "RESOURCE_GROUP_ID is required"}'
exit 0
fi

if [[ -z "${REGION}" || "${REGION}" == "null" ]]; then
echo '{"error": "REGION is required"}'
exit 0
fi

if ! ibmcloud login -r "${REGION}" -g "${RESOURCE_GROUP_ID}" --quiet > /dev/null 2>&1; then
printf '{"error": "Failed to login using: ibmcloud login -r %s -g %s"}' "$REGION" "$RESOURCE_GROUP_ID"
exit 0
fi

# extract registry value from text "You are targeting region 'us-south', the registry is 'us.icr.io'."
registry=$(ibmcloud cr region 2>/dev/null | grep registry | sed -E "s/.*registry is '([^']+)'.*/\1/")

# Validate registry value
if [[ -z "$registry" ]]; then
echo '{"error": "Failed to parse registry region from ibmcloud cr region"}'
exit 0
fi

echo "{\"registry\": \"${registry}\"}"
16 changes: 15 additions & 1 deletion solutions/project/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ variable "project_name" {
variable "builds" {
description = "A map of code engine builds to be created.[Learn more](https://github.com/terraform-ibm-modules/terraform-ibm-code-engine/blob/main/solutions/project/DA-inputs.md#builds)"
type = map(object({
output_image = string
output_image = optional(string)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should imply a change to the documentation in the link in the description. The DA now has a more complex interaction between this input and container_resgistry_namespace.

output_secret = string # pragma: allowlist secret
source_url = string
strategy_type = string
Expand All @@ -61,6 +61,20 @@ variable "builds" {
default = {}
}

variable "container_registry_namespace" {
description = "The name of the namespace to create in IBM Cloud Container Registry for organizing container images. Used only for builds that do not have output_image set."
type = string
default = null

validation {
condition = alltrue([
for build in values(var.builds) :
contains(keys(build), "output_image") && build.output_image != null
]) || var.container_registry_namespace != null
error_message = "container_registry_namespace is required because at least one build is missing an output_image"
}
}

##############################################################################
# Code Engine Domain Mapping
##############################################################################
Expand Down
8 changes: 8 additions & 0 deletions solutions/project/version.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,13 @@ terraform {
source = "IBM-Cloud/ibm"
version = "1.79.2"
}
external = {
source = "hashicorp/external"
version = "2.3.5"
}
null = {
source = "hashicorp/null"
version = "3.2.4"
}
}
}
Loading