Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ No resources.
|------|-------------|------|---------|:--------:|
| <a name="input_name"></a> [name](#input\_name) | Deployment name. | `string` | n/a | yes |
| <a name="input_cloud_provider"></a> [cloud\_provider](#input\_cloud\_provider) | Compatibility selector for the root GCP entrypoint. Use providers/do or providers/linode for other clouds. | `string` | `"gcp"` | no |
| <a name="input_gcp"></a> [gcp](#input\_gcp) | Google Cloud infrastructure settings. | <pre>object({<br/> project_id = optional(string, "")<br/> project_number = optional(string, "")<br/> region = optional(string, "us-east5")<br/> zone = optional(string, "us-east5-b")<br/><br/> identity = optional(object({<br/> vm_service_account_email = optional(string, "")<br/> app_service_account_email = optional(string, "")<br/> app_credentials_enabled = optional(bool, false)<br/> }), {})<br/><br/> instance = optional(object({<br/> machine_type = optional(string, "n4-standard-2")<br/> os = optional(string, "cos-125-19216-220-185")<br/> production = optional(bool, false)<br/> }), {})<br/><br/> disks = optional(object({<br/> type = optional(string, "hyperdisk-balanced")<br/> docker_volumes_size_gb = optional(number, 50)<br/> }), {})<br/><br/> network = optional(object({<br/> create = optional(bool, true)<br/> project_id = optional(string, "")<br/> name = optional(string, "")<br/> subnetwork = optional(string, "")<br/> ip_cidr_range = optional(string, "10.42.0.0/24")<br/> mtu = optional(number, 1460)<br/> power_button_allowed_ips = optional(list(string), [])<br/> power_button_ip_depth = optional(number)<br/> ssh_ipv4 = optional(list(string), [])<br/> ssh_ipv6 = optional(list(string), [])<br/> }), {})<br/><br/> snapshots = optional(object({<br/> enabled = optional(bool, false)<br/> }), {})<br/><br/> overlay = optional(object({<br/> source_instance = optional(string, "")<br/> volume_names = optional(list(string), [])<br/> }), {})<br/><br/> cloud_init = optional(object({<br/> initcmd = optional(list(string), [])<br/> runcmd = optional(list(string), [])<br/> }), {})<br/><br/> artifact_registry = optional(object({<br/> repository = optional(string, "")<br/> location = optional(string, "us")<br/> }), {})<br/><br/> power_management = optional(object({<br/> enabled = optional(bool, false)<br/> start_role = optional(string, "")<br/> suspend_role = optional(string, "")<br/> frontend = optional(object({<br/> image = string<br/> port = optional(number, 8080)<br/> cpu = optional(string, "1000m")<br/> memory = optional(string, "1Gi")<br/> }), null)<br/> }), {})<br/><br/> rollout = optional(object({<br/> enabled = optional(bool, false)<br/> release_url = optional(string, "")<br/> release_sha256 = optional(string, "")<br/> port = optional(number, 8081)<br/> jwks_uri = optional(string, "")<br/> jwt_audience = optional(string, "")<br/> custom_claims = optional(string, "")<br/> allowed_ipv4 = optional(list(string), ["10.0.0.0/8"])<br/> }), {})<br/> })</pre> | `{}` | no |
| <a name="input_gcp"></a> [gcp](#input\_gcp) | Google Cloud infrastructure settings. | <pre>object({<br/> project_id = optional(string, "")<br/> project_number = optional(string, "")<br/> region = optional(string, "us-east5")<br/> zone = optional(string, "us-east5-b")<br/><br/> identity = optional(object({<br/> vm_service_account_email = optional(string, "")<br/> app_service_account_email = optional(string, "")<br/> app_credentials_enabled = optional(bool, false)<br/> }), {})<br/><br/> instance = optional(object({<br/> machine_type = optional(string, "n4-standard-2")<br/> os = optional(string, "cos-125-19216-220-185")<br/> production = optional(bool, false)<br/> }), {})<br/><br/> disks = optional(object({<br/> type = optional(string, "hyperdisk-balanced")<br/> data_size_gb = optional(number, 20)<br/> docker_volumes_size_gb = optional(number, 50)<br/> }), {})<br/><br/> network = optional(object({<br/> create = optional(bool, true)<br/> project_id = optional(string, "")<br/> name = optional(string, "")<br/> subnetwork = optional(string, "")<br/> ip_cidr_range = optional(string, "10.42.0.0/24")<br/> mtu = optional(number, 1460)<br/> power_button_allowed_ips = optional(list(string), [])<br/> power_button_ip_depth = optional(number)<br/> ssh_ipv4 = optional(list(string), [])<br/> ssh_ipv6 = optional(list(string), [])<br/> }), {})<br/><br/> snapshots = optional(object({<br/> enabled = optional(bool, false)<br/> }), {})<br/><br/> overlay = optional(object({<br/> source_instance = optional(string, "")<br/> volume_names = optional(list(string), [])<br/> }), {})<br/><br/> cloud_init = optional(object({<br/> initcmd = optional(list(string), [])<br/> runcmd = optional(list(string), [])<br/> }), {})<br/><br/> artifact_registry = optional(object({<br/> repository = optional(string, "")<br/> location = optional(string, "us")<br/> }), {})<br/><br/> power_management = optional(object({<br/> enabled = optional(bool, false)<br/> start_role = optional(string, "")<br/> suspend_role = optional(string, "")<br/> frontend = optional(object({<br/> image = string<br/> port = optional(number, 8080)<br/> cpu = optional(string, "1000m")<br/> memory = optional(string, "1Gi")<br/> }), null)<br/> }), {})<br/><br/> rollout = optional(object({<br/> enabled = optional(bool, false)<br/> release_url = optional(string, "")<br/> release_sha256 = optional(string, "")<br/> port = optional(number, 8081)<br/> jwks_uri = optional(string, "")<br/> jwt_audience = optional(string, "")<br/> custom_claims = optional(string, "")<br/> allowed_ipv4 = optional(list(string), ["10.0.0.0/8"])<br/> }), {})<br/> })</pre> | `{}` | no |
| <a name="input_runtime"></a> [runtime](#input\_runtime) | Provider-neutral compose/runtime settings. | <pre>object({<br/> rootfs = optional(string, "")<br/> rootfs_archive_url = optional(string, "")<br/> rootfs_archive_sha256 = optional(string, "")<br/> users = optional(map(list(string)), {})<br/><br/> compose = optional(object({<br/> primary = optional(string, "")<br/> ingress_port = optional(number, 80)<br/> ingress = optional(object({<br/> letsencrypt = optional(bool, false)<br/> bot_mitigation = optional(bool, false)<br/> mode = optional(string, "")<br/> domain = optional(string, "")<br/> acme_email = optional(string, "")<br/> trusted_ips = optional(list(string), [])<br/> max_upload_size = optional(string, "")<br/> upload_timeout = optional(string, "")<br/> }), {})<br/> repo = optional(string, "")<br/> branch = optional(string, "")<br/> projects = optional(map(object({<br/> docker_compose_repo = string<br/> docker_compose_branch = optional(string)<br/> project_dir = optional(string)<br/> compose_project_name = optional(string)<br/> ingress_port = optional(number)<br/> ingress = optional(object({<br/> letsencrypt = optional(bool)<br/> bot_mitigation = optional(bool)<br/> mode = optional(string)<br/> domain = optional(string)<br/> acme_email = optional(string)<br/> trusted_ips = optional(list(string))<br/> max_upload_size = optional(string)<br/> upload_timeout = optional(string)<br/> }), {})<br/> sitectl_context_name = optional(string)<br/> sitectl_plugin = optional(string)<br/> sitectl_environment = optional(string)<br/> sitectl_packages = optional(list(string))<br/> sitectl_verify_args = optional(list(string))<br/> docker_compose_init = optional(list(string))<br/> docker_compose_up = optional(list(string))<br/> docker_compose_down = optional(list(string))<br/> docker_compose_rollout = optional(list(string))<br/> })), {})<br/> init = optional(list(string))<br/> up = optional(list(string))<br/> down = optional(list(string))<br/> rollout = optional(list(string))<br/> }), {})<br/><br/> sitectl = optional(object({<br/> packages = optional(list(string))<br/> version = optional(string, "latest")<br/> package_versions = optional(map(string), {})<br/> context_name = optional(string, "")<br/> plugin = optional(string, "core")<br/> environment = optional(string, "production")<br/> verify_args = optional(list(string), [])<br/> }), {})<br/><br/> docker = optional(object({<br/> # renovate: datasource=github-releases depName=docker-compose packageName=docker/compose versioning=semver<br/> compose_version = optional(string, "v5.3.1")<br/> # renovate: datasource=github-releases depName=docker-buildx packageName=docker/buildx versioning=semver<br/> buildx_version = optional(string, "v0.35.0")<br/> }), {})<br/><br/> managed_runtime = optional(object({<br/> enabled = optional(bool, true)<br/> internal_services_enabled = optional(bool, false)<br/> internal_services_auto_update = optional(bool, false)<br/> artifacts = optional(list(object({<br/> name = string<br/> url = string<br/> sha256 = string<br/> path = string<br/> mode = optional(string, "0755")<br/> owner = optional(string, "root")<br/> group = optional(string, "root")<br/> restart = optional(string, "")<br/> })), [])<br/> }), {})<br/><br/> vault = optional(object({<br/> addr = optional(string, "")<br/> namespace = optional(string, "")<br/> role = optional(string, "")<br/> agent_enabled = optional(bool, false)<br/> auth_method = optional(string, "auto")<br/> gcp_auth_mount_path = optional(string, "auth/gcp")<br/> agent_token_path = optional(string, "/mnt/disks/data/vault/token")<br/> agent_additional_config = optional(string, "")<br/> agent_templates = optional(list(object({<br/> destination = string<br/> contents = string<br/> perms = optional(string, "0640")<br/> command = optional(string, "")<br/> })), [])<br/> }), {})<br/><br/> extra_env = optional(map(string), {})<br/> })</pre> | `{}` | no |
| <a name="input_template"></a> [template](#input\_template) | Optional compose template preset. Supported values are archivesspace, ojs, isle, drupal, wp, omeka-s, and omeka-classic. Explicit runtime settings override preset defaults. | `string` | `""` | no |

Expand Down
50 changes: 44 additions & 6 deletions ci/gcp-upgrade-smoke-contract.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,23 @@ grep -Fq '/mnt/disks/volumes/.cloud-compose-upgrade-sentinel' "$script" ||
fail "upgrade runner must write the data-disk sentinel exactly once"
[[ "$(grep -Fc 'sudo tee /mnt/disks/volumes/.cloud-compose-upgrade-sentinel' "$script")" -eq 1 ]] ||
fail "upgrade runner must write the Docker-volume sentinel exactly once"
grep -Fq 'read_data_filesystem_size_bytes()' "$script" ||
fail "upgrade runner does not measure mounted application-data filesystem capacity"
grep -Fq 'filesystem="$(findmnt -n -o FSTYPE --target /mnt/disks/data)"' "$script" ||
fail "upgrade runner does not require the application-data mount to remain ext4"
grep -Fq 'df --block-size=1 --output=size -- /mnt/disks/data' "$script" ||
fail "upgrade runner does not measure mounted ext4 capacity in bytes"
grep -Fq '((10#$upgraded_data_filesystem_bytes > 10#$baseline_data_filesystem_bytes))' "$script" ||
fail "upgrade runner does not prove mounted ext4 grew beyond its baseline capacity"

grep -Fq 'backend "local" {}' "$fixture" ||
fail "upgrade fixture does not declare the shared local backend"
grep -Fq 'source = "../../.."' "$fixture" ||
fail "upgrade fixture does not exercise the GCP-only compatibility root"
grep -Fq 'application_data_size_gb = var.legacy_baseline ? 20 : 30' "$fixture" ||
fail "upgrade fixture does not request exact 20-to-30-GB application-data growth"
grep -Fq 'data_size_gb = local.application_data_size_gb' "$fixture" ||
fail "upgrade fixture does not pass its phase-specific application-data size"
grep -Fq 'create = false' "$fixture" ||
fail "upgrade fixture still owns an ephemeral network"
grep -Fq 'project_id = var.gcp_network_project_id' "$fixture" ||
Expand Down Expand Up @@ -208,7 +220,7 @@ grep -Fq 'docker_compose_branch = var.wordpress_compose_ref' "$fixture" ||
fail "upgrade fixture does not use its pinned WordPress commit"
grep -Fq "git_project checkout --detach \${var.wordpress_compose_ref}" "$fixture" ||
fail "legacy bootstrap does not pre-check out the exact WordPress commit"
grep -Fq 'wordpress_project_dir = "/mnt/disks/data/libops/wp.git/${var.wordpress_compose_ref}"' "$fixture" ||
grep -Eq 'wordpress_project_dir[[:space:]]*=[[:space:]]*"/mnt/disks/data/libops/wp.git/\$\{var\.wordpress_compose_ref\}"' "$fixture" ||
fail "upgrade fixture does not derive the exact legacy single-project checkout path"
grep -Fq 'project=${local.wordpress_project_dir};' "$fixture" ||
fail "upgrade fixture does not pre-check out the shared baseline/current Compose path"
Expand Down Expand Up @@ -307,6 +319,10 @@ grep -Fq 'pull-request titles beginning with `[major]`' "$docs" ||
fail "upgrade documentation does not explain when the expensive gate runs"
grep -Fq "$wordpress_ref" "$docs" ||
fail "upgrade documentation omits the pinned WordPress Compose revision"
grep -Fq 'controlled reboot after the apply' "$docs" ||
fail "GCP disk-growth documentation does not explain when ext4 capacity activates"
grep -Fq 'update-only application-data growth from 20 to 30 GB' "$docs" ||
fail "upgrade documentation omits the exact in-place data-disk growth contract"

cat >"$tmp/good-plan.json" <<'EOF'
{
Expand Down Expand Up @@ -363,7 +379,11 @@ cat >"$tmp/good-plan.json" <<'EOF'
},
{
"address": "module.app.module.gcp[0].google_compute_disk.data",
"change": {"actions": ["no-op"]}
"change": {
"actions": ["update"],
"before": {"size": 20},
"after": {"size": 30}
}
},
{
"address": "module.app.module.gcp[0].google_compute_disk.docker-volumes",
Expand Down Expand Up @@ -394,10 +414,22 @@ if bash "$script" check-plan "$tmp/bad-move.json" >/dev/null 2>&1; then
fail "plan checker accepted a missing moved-resource provenance"
fi

jq '(.resource_changes[] | select(.address | endswith("google_compute_disk.data")) | .change.actions) = ["delete", "create"]' \
"$tmp/good-plan.json" >"$tmp/bad-data-disk.json"
if bash "$script" check-plan "$tmp/bad-data-disk.json" >/dev/null 2>&1; then
fail "plan checker accepted application-data disk replacement"
fi

jq '(.resource_changes[] | select(.address | endswith("google_compute_disk.data")) | .change.after.size) = 29' \
"$tmp/good-plan.json" >"$tmp/bad-data-growth.json"
if bash "$script" check-plan "$tmp/bad-data-growth.json" >/dev/null 2>&1; then
fail "plan checker accepted the wrong application-data growth target"
fi

jq '(.resource_changes[] | select(.address | endswith("docker-volumes")) | .change.actions) = ["delete", "create"]' \
"$tmp/good-plan.json" >"$tmp/bad-disk.json"
if bash "$script" check-plan "$tmp/bad-disk.json" >/dev/null 2>&1; then
fail "plan checker accepted persistent-disk replacement"
"$tmp/good-plan.json" >"$tmp/bad-docker-disk.json"
if bash "$script" check-plan "$tmp/bad-docker-disk.json" >/dev/null 2>&1; then
fail "plan checker accepted Docker-volume disk replacement"
fi

jq '.resource_changes += [{"address":"module.app.google_compute_disk.unexpected","change":{"actions":["delete"]}}]' \
Expand Down Expand Up @@ -490,7 +522,13 @@ bash "$script" check-transition \
jq '.docker_disk = "docker-replaced"' "$tmp/new-ids.json" >"$tmp/bad-new-ids.json"
if bash "$script" check-transition \
"$tmp/old-ids.json" "$tmp/bad-new-ids.json" "$tmp/old-state.txt" "$tmp/new-state.txt" >/dev/null 2>&1; then
fail "state checker accepted a changed persistent disk id"
fail "state checker accepted a changed Docker-volume disk id"
fi

jq '.data_disk = "data-replaced"' "$tmp/new-ids.json" >"$tmp/bad-data-ids.json"
if bash "$script" check-transition \
"$tmp/old-ids.json" "$tmp/bad-data-ids.json" "$tmp/old-state.txt" "$tmp/new-state.txt" >/dev/null 2>&1; then
fail "state checker accepted a changed application-data disk id"
fi

cp "$tmp/new-state.txt" "$tmp/bad-new-state.txt"
Expand Down
Loading
Loading