From a341869d90459f2872cb7362fdffa72539ce4239 Mon Sep 17 00:00:00 2001 From: sebas_correa Date: Fri, 31 Jul 2026 14:00:19 -0300 Subject: [PATCH 1/3] feat(scope_definition): default action_spec_names from the service spec's available_actions --- nullplatform/scope_definition/locals.tf | 4 +++- nullplatform/scope_definition/variables.tf | 26 +++++++--------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/nullplatform/scope_definition/locals.tf b/nullplatform/scope_definition/locals.tf index fd3292bf5..468c71374 100644 --- a/nullplatform/scope_definition/locals.tf +++ b/nullplatform/scope_definition/locals.tf @@ -15,7 +15,9 @@ locals { scope_type_def = jsondecode(data.external.scope_type.result.json) - static_action_specs = toset(var.action_spec_names) + static_action_specs = toset( + var.action_spec_names != null ? var.action_spec_names : try(local.service_spec_parsed.available_actions, []) + ) scope_configuration_rendered = var.create_scope_configuration ? replace( data.http.scope_configuration_template[0].response_body, diff --git a/nullplatform/scope_definition/variables.tf b/nullplatform/scope_definition/variables.tf index 4d2ec94f9..3ce1eaff7 100644 --- a/nullplatform/scope_definition/variables.tf +++ b/nullplatform/scope_definition/variables.tf @@ -58,25 +58,15 @@ variable "repo_path" { } variable "action_spec_names" { - description = "List of action specification template names to fetch and create for scope operations" + description = <<-EOT + List of action specification template names to fetch and create for scope + operations. Default `null` -> use the `available_actions` array from the + scope's `service-spec.json.tpl` (fetched via `repository_service_spec` / + `service_path`). Set this explicitly only when the spec's list is wrong + for your case or the spec predates the `available_actions` field. + EOT type = list(string) - default = [ - "create-scope", - "delete-scope", - "start-initial", - "start-blue-green", - "finalize-blue-green", - "rollback-deployment", - "delete-deployment", - "switch-traffic", - "set-desired-instance-count", - "pause-autoscaling", - "resume-autoscaling", - "restart-pods", - "kill-instances", - "diagnose-deployment", - "diagnose-scope" - ] + default = null } ################################################################################ From 3f16e0ba7977a89637e0362bae075f07cfceb6db Mon Sep 17 00:00:00 2001 From: sebas_correa Date: Fri, 31 Jul 2026 14:10:09 -0300 Subject: [PATCH 2/3] fix(scope_definition): route action_spec for_each through the spec fallback --- nullplatform/scope_definition/data.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nullplatform/scope_definition/data.tf b/nullplatform/scope_definition/data.tf index d2586e5bb..d72d4fee0 100644 --- a/nullplatform/scope_definition/data.tf +++ b/nullplatform/scope_definition/data.tf @@ -10,7 +10,7 @@ data "http" "scope_type_template" { } data "http" "action_templates" { - for_each = toset(var.action_spec_names) + for_each = local.static_action_specs url = "${var.repository_action_templates}/${var.repository_action_templates_branch}/${var.service_path}/specs/actions/${each.key}.json.tpl" } @@ -63,7 +63,7 @@ data "external" "scope_type" { # it forces a phantom destroy+recreate of EVERY action_specification # instance at once (their `type` is ForceNew). data "external" "action_specs" { - for_each = toset(var.action_spec_names) + for_each = local.static_action_specs depends_on = [ data.http.action_templates ] From df701fbb9079fddef5bce12b76f889a8e7c5e6a2 Mon Sep 17 00:00:00 2001 From: Agustin Celentano <12614595+agustincelentano@users.noreply.github.com> Date: Mon, 3 Aug 2026 12:02:03 -0300 Subject: [PATCH 3/3] fix(scope_definition): fail instead of destroying when no actions resolve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dropping the hardcoded default means a caller that relied on it, and whose spec predates `available_actions`, now resolves to an empty list. That reaches the action_specification for_each as zero instances and destroys every action specification the scope has registered — with no error, since an empty list is not one. Measured on a live implementation: 44 destroys across four scopes, in a plan that reads as deliberate. The precondition sits on the service specification rather than on the action_specification resource because that resource has no instances when the for_each is empty, so its own preconditions never run. Callers passing action_spec_names explicitly are unaffected — verified against an implementation where one scope keeps its list in locals and its spec has no available_actions: the plan reports no changes. --- nullplatform/scope_definition/main.tf | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/nullplatform/scope_definition/main.tf b/nullplatform/scope_definition/main.tf index 2c41801d6..078724b72 100644 --- a/nullplatform/scope_definition/main.tf +++ b/nullplatform/scope_definition/main.tf @@ -22,6 +22,29 @@ resource "nullplatform_service_specification" "from_template" { provider = local.service_spec_parsed.selectors.provider sub_category = local.service_spec_parsed.selectors.sub_category } + + # Resolving to no actions means the caller relied on the default this variable + # no longer has, and its spec predates `available_actions`. Left alone that is + # an empty for_each below, which destroys every action specification the scope + # has registered — silently, since an empty list is not an error. + # + # The check lives here and not on the action_specification resource: with an + # empty for_each that resource has no instances, so its preconditions never run. + lifecycle { + precondition { + condition = length(local.static_action_specs) > 0 + error_message = <<-EOT + No actions resolved for scope "${var.service_spec_name}". + + Declare `available_actions` in ${var.service_path}/specs/service-spec.json.tpl, + or pass `action_spec_names` explicitly. + + Proceeding would destroy every action specification registered for this + scope: creating and deleting scopes, deploying, blue/green, rollback and + diagnostics all stop being available from the UI. + EOT + } + } } ################################################################################