diff --git a/docs-examples/datasources/stack_template/datasource.tf b/docs-examples/datasources/stack_template/datasource.tf new file mode 100644 index 0000000..8cca881 --- /dev/null +++ b/docs-examples/datasources/stack_template/datasource.tf @@ -0,0 +1,7 @@ +data "stackguardian_stack_template" "example" { + id = "my-stack-template" +} + +output "stack_template_output" { + value = data.stackguardian_stack_template.example.description +} diff --git a/docs-examples/datasources/stack_template_revision/datasource.tf b/docs-examples/datasources/stack_template_revision/datasource.tf new file mode 100644 index 0000000..326d234 --- /dev/null +++ b/docs-examples/datasources/stack_template_revision/datasource.tf @@ -0,0 +1,7 @@ +data "stackguardian_stack_template_revision" "example" { + id = "my-stack-template:1" +} + +output "stack_template_revision_output" { + value = data.stackguardian_stack_template_revision.example.notes +} diff --git a/docs-examples/resources/stack_template/resource.tf b/docs-examples/resources/stack_template/resource.tf new file mode 100644 index 0000000..0b701a2 --- /dev/null +++ b/docs-examples/resources/stack_template/resource.tf @@ -0,0 +1,7 @@ +# Example 1: Basic stack template +resource "stackguardian_stack_template" "basic" { + template_name = "my-stack-template" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "production"] +} diff --git a/docs-examples/resources/stack_template_revision/resource.tf b/docs-examples/resources/stack_template_revision/resource.tf new file mode 100644 index 0000000..682edf0 --- /dev/null +++ b/docs-examples/resources/stack_template_revision/resource.tf @@ -0,0 +1,67 @@ +resource "stackguardian_stack_template" "example" { + template_name = "my-stack-template" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "production"] +} + +# Example 1: Basic stack template revision +resource "stackguardian_stack_template_revision" "basic" { + parent_template_id = stackguardian_stack_template.example.id + alias = "v1" + notes = "Initial revision" + description = "First revision of the stack template" + source_config_kind = "TERRAFORM" + + workflows_config = { + workflows = [ + { + id = "d8dfaf15-2ad9-da29-8af0-c6b288b12089" + template_id = "my-workflow-template" + resource_name = "wf-1" + + terraform_config = { + managed_terraform_state = true + terraform_version = "1.5.7" + } + } + ] + } +} + +# Example 2: Stack template revision with VCS config +resource "stackguardian_stack_template_revision" "with_vcs" { + parent_template_id = stackguardian_stack_template.example.id + alias = "v2" + notes = "Revision with VCS configuration" + description = "Stack template revision with VCS and input data" + source_config_kind = "TERRAFORM" + + workflows_config = { + workflows = [ + { + id = "d8dfaf15-2ad9-da29-8af0-c6b288b12089" + template_id = "my-workflow-template" + resource_name = "wf-1" + + vcs_config = { + iac_vcs_config = { + use_marketplace_template = true + iac_template_id = "my-workflow-template" + } + iac_input_data = { + schema_type = "RAW_JSON" + data = jsonencode({ + bucket_region = "eu-central-1" + }) + } + } + + terraform_config = { + managed_terraform_state = true + terraform_version = "1.5.7" + } + } + ] + } +} diff --git a/docs-templates/data-sources/stack_template.md.tmpl b/docs-templates/data-sources/stack_template.md.tmpl new file mode 100644 index 0000000..2298e0b --- /dev/null +++ b/docs-templates/data-sources/stack_template.md.tmpl @@ -0,0 +1,19 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_stack_template Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_stack_template (Data Source) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +{{tffile "docs-examples/datasources/stack_template/datasource.tf"}} + +{{ .SchemaMarkdown }} diff --git a/docs-templates/data-sources/stack_template_revision.md.tmpl b/docs-templates/data-sources/stack_template_revision.md.tmpl new file mode 100644 index 0000000..44f55db --- /dev/null +++ b/docs-templates/data-sources/stack_template_revision.md.tmpl @@ -0,0 +1,19 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_stack_template_revision Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_stack_template_revision (Data Source) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +{{tffile "docs-examples/datasources/stack_template_revision/datasource.tf"}} + +{{ .SchemaMarkdown }} diff --git a/docs-templates/resources/stack_template.md.tmpl b/docs-templates/resources/stack_template.md.tmpl new file mode 100644 index 0000000..ea3da32 --- /dev/null +++ b/docs-templates/resources/stack_template.md.tmpl @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_stack_template Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_stack_template (Resource) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +{{tffile "docs-examples/resources/stack_template/resource.tf"}} + +{{ .SchemaMarkdown }} + +## Import + +Import existing resource + +### Using Import block (terraform v1.5.0 and later) +```terraform +import { + to = stackguardian_stack_template.example + id = "template-name" +} +``` + +### Using CLI +```bash +terraform import stackguardian_stack_template.example template-name +``` diff --git a/docs-templates/resources/stack_template_revision.md.tmpl b/docs-templates/resources/stack_template_revision.md.tmpl new file mode 100644 index 0000000..94f4a60 --- /dev/null +++ b/docs-templates/resources/stack_template_revision.md.tmpl @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_stack_template_revision Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_stack_template_revision (Resource) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +{{tffile "docs-examples/resources/stack_template_revision/resource.tf"}} + +{{ .SchemaMarkdown }} + +## Import + +Import existing resource + +### Using Import block (terraform v1.5.0 and later) +```terraform +import { + to = stackguardian_stack_template_revision.example + id = "template-name:revision-version" +} +``` + +### Using CLI +```bash +terraform import stackguardian_stack_template_revision.example template-name:revision-version +``` diff --git a/docs/data-sources/stack_template.md b/docs/data-sources/stack_template.md new file mode 100644 index 0000000..0dea1d7 --- /dev/null +++ b/docs/data-sources/stack_template.md @@ -0,0 +1,58 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_stack_template Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_stack_template (Data Source) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +```terraform +data "stackguardian_stack_template" "example" { + id = "my-stack-template" +} + +output "stack_template_output" { + value = data.stackguardian_stack_template.example.description +} +``` + + +## Schema + +### Required + +- `id` (String) ID of the resource. Should be used to import the resource. + +### Read-Only + +- `context_tags` (Map of String) Context tags for stack template +- `description` (String) A brief description of the stack template. Must be less than 256 characters. +- `is_active` (String) Whether the stack template is active. Valid values: + 0 (false), + 1 (true) +- `is_public` (String) Whether the stack template is publicly available. Valid values: + 0 (false), + 1 (true) +- `owner_org` (String) Organization that owns the stack template. +- `shared_orgs_list` (List of String) List of organization IDs with which this template is shared. +- `source_config_kind` (String) Source configuration kind for the stack template. Valid values: + TERRAFORM, + OPENTOFU, + ANSIBLE_PLAYBOOK, + HELM, + KUBECTL, + CLOUDFORMATION, + MIXED, + CUSTOM +- `tags` (List of String) A list of tags associated with the stack template. A maximum of 10 tags are allowed. +- `template_name` (String) Name of the stack template. Must be less than 100 characters. + + diff --git a/docs/data-sources/stack_template_revision.md b/docs/data-sources/stack_template_revision.md new file mode 100644 index 0000000..ab78099 --- /dev/null +++ b/docs/data-sources/stack_template_revision.md @@ -0,0 +1,642 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_stack_template_revision Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_stack_template_revision (Data Source) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +```terraform +data "stackguardian_stack_template_revision" "example" { + id = "my-stack-template:1" +} + +output "stack_template_revision_output" { + value = data.stackguardian_stack_template_revision.example.notes +} +``` + + +## Schema + +### Required + +- `id` (String) ID of the resource. Should be used to import the resource. + +### Read-Only + +- `actions` (Attributes Map) JSON-encoded map of actions for the stack template revision. (see [below for nested schema](#nestedatt--actions)) +- `alias` (String) Human-readable alias for the revision (e.g., `v1.0.0`). +- `context_tags` (Map of String) Context tags for stack template revision +- `deprecation` (Attributes) Deprecation information for this resource. (see [below for nested schema](#nestedatt--deprecation)) +- `description` (String) Long description for the stack template revision. +- `is_active` (String) Whether the stack template is active. Valid values: + 0 (false), + 1 (true) +- `is_public` (String) Whether the stack template is publicly available. Valid values: + 0 (false), + 1 (true) +- `notes` (String) Release notes or changelog for this revision. +- `source_config_kind` (String) Source configuration kind for the stack template. Valid values: + TERRAFORM, + OPENTOFU, + ANSIBLE_PLAYBOOK, + HELM, + KUBECTL, + CLOUDFORMATION, + MIXED, + CUSTOM +- `tags` (List of String) A list of tags associated with the stack template revision. A maximum of 10 tags are allowed. +- `template_id` (String) ID of the parent stack template. +- `workflows_config` (Attributes) JSON-encoded workflows configuration for the stack template revision. (see [below for nested schema](#nestedatt--workflows_config)) + + +### Nested Schema for `actions` + +Read-Only: + +- `default` (Boolean) Whether this is the default action. +- `description` (String) Description of the action. +- `name` (String) Name of the action. +- `order` (Attributes Map) Ordered map of workflow IDs to their action configurations. (see [below for nested schema](#nestedatt--actions--order)) + + +### Nested Schema for `actions.order` + +Read-Only: + +- `dependencies` (Attributes List) List of workflow dependencies that must complete before this step runs. (see [below for nested schema](#nestedatt--actions--order--dependencies)) +- `parameters` (Attributes) Run configuration parameters for the action step. (see [below for nested schema](#nestedatt--actions--order--parameters)) + + +### Nested Schema for `actions.order.dependencies` + +Read-Only: + +- `condition` (Attributes) Condition that must be met by the dependency. (see [below for nested schema](#nestedatt--actions--order--dependencies--condition)) +- `id` (String) ID of the dependent workflow. + + +### Nested Schema for `actions.order.dependencies.condition` + +Read-Only: + +- `latest_status` (String) Required latest status of the dependency (e.g., COMPLETED). + + + + +### Nested Schema for `actions.order.parameters` + +Read-Only: + +- `deployment_platform_config` (Attributes List) Deployment platform configuration. (see [below for nested schema](#nestedatt--actions--order--parameters--deployment_platform_config)) +- `environment_variables` (Attributes List) Environment variables. (see [below for nested schema](#nestedatt--actions--order--parameters--environment_variables)) +- `terraform_action` (Attributes) Terraform-specific action parameters. (see [below for nested schema](#nestedatt--actions--order--parameters--terraform_action)) +- `wf_steps_config` (Attributes List) Workflow steps configuration. (see [below for nested schema](#nestedatt--actions--order--parameters--wf_steps_config)) + + +### Nested Schema for `actions.order.parameters.deployment_platform_config` + +Read-Only: + +- `config` (String) Deployment platform configuration details. (JSON string) +- `kind` (String) Deployment platform kind. Options: AWS_STATIC, AWS_RBAC, AWS_OIDC, AZURE_STATIC, AZURE_OIDC, GCP_STATIC, GCP_OIDC + + + +### Nested Schema for `actions.order.parameters.environment_variables` + +Read-Only: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--actions--order--parameters--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `actions.order.parameters.environment_variables.config` + +Read-Only: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT +- `var_name` (String) Name of the variable. + + + + +### Nested Schema for `actions.order.parameters.terraform_action` + +Read-Only: + +- `action` (String) Terraform action to execute. E.g., "apply", "plan", "destroy". + + + +### Nested Schema for `actions.order.parameters.wf_steps_config` + +Read-Only: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--actions--order--parameters--wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--actions--order--parameters--wf_steps_config--mount_points)) +- `name` (String) Step name. +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--actions--order--parameters--wf_steps_config--wf_step_input_data)) +- `wf_step_template_id` (String) Workflow step template ID. + + +### Nested Schema for `actions.order.parameters.wf_steps_config.environment_variables` + +Read-Only: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--actions--order--parameters--wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `actions.order.parameters.wf_steps_config.environment_variables.config` + +Read-Only: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT +- `var_name` (String) Name of the variable. + + + + +### Nested Schema for `actions.order.parameters.wf_steps_config.mount_points` + +Read-Only: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `actions.order.parameters.wf_steps_config.wf_step_input_data` + +Read-Only: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + + + + +### Nested Schema for `deprecation` + +Read-Only: + +- `effective_date` (String) Effective date for after which revision will be deprecated +- `message` (String) Marking a template revision for deprecation + + + +### Nested Schema for `workflows_config` + +Read-Only: + +- `workflows` (Attributes List) List of workflows that make up the stack. (see [below for nested schema](#nestedatt--workflows_config--workflows)) + + +### Nested Schema for `workflows_config.workflows` + +Read-Only: + +- `approvers` (List of String) List of approvers. +- `deployment_platform_config` (Attributes List) Deployment platform configuration. (see [below for nested schema](#nestedatt--workflows_config--workflows--deployment_platform_config)) +- `description` (String) Description of this workflow. +- `environment_variables` (Attributes List) Environment variables for the workflow. (see [below for nested schema](#nestedatt--workflows_config--workflows--environment_variables)) +- `is_active` (String) Whether the stack template is active. Valid values: + 0 (false), + 1 (true) +- `number_of_approvals_required` (Number) Number of approvals required. +- `resource_name` (String) Name of the workflow resource within the stack. +- `runner_constraints` (Attributes) Runner constraints for the workflow. (see [below for nested schema](#nestedatt--workflows_config--workflows--runner_constraints)) +- `tags` (List of String) Tags for the workflow. +- `terraform_config` (Attributes) Terraform configuration. Valid only for terraform type template (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config)) +- `user_job_cpu` (Number) CPU limit for the user job. +- `user_job_memory` (Number) Memory limit for the user job. +- `user_schedules` (Attributes List) Scheduled run configuration. (see [below for nested schema](#nestedatt--workflows_config--workflows--user_schedules)) +- `vcs_config` (Attributes) VCS (version control) configuration for the workflow. (see [below for nested schema](#nestedatt--workflows_config--workflows--vcs_config)) +- `wf_steps_config` (Attributes List) Workflow steps configuration. (see [below for nested schema](#nestedatt--workflows_config--workflows--wf_steps_config)) +- `wf_type` (String) Workflow type. Valid values: TERRAFORM, OPENTOFU, CUSTOM + + +### Nested Schema for `workflows_config.workflows.deployment_platform_config` + +Read-Only: + +- `config` (String) Deployment platform configuration details. (JSON string) +- `kind` (String) Deployment platform kind. Options: AWS_STATIC, AWS_RBAC, AWS_OIDC, AZURE_STATIC, AZURE_OIDC, GCP_STATIC, GCP_OIDC + + + +### Nested Schema for `workflows_config.workflows.environment_variables` + +Read-Only: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.environment_variables.config` + +Read-Only: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT +- `var_name` (String) Name of the variable. + + + + +### Nested Schema for `workflows_config.workflows.runner_constraints` + +Read-Only: + +- `names` (List of String) Id of the runner group +- `type` (String) Type of runner. Valid options: shared or external + + + +### Nested Schema for `workflows_config.workflows.terraform_config` + +Read-Only: + +- `approval_pre_apply` (Boolean) Require approval before apply. +- `drift_check` (Boolean) Enable drift check. +- `drift_cron` (String) Cron expression for drift check. +- `managed_terraform_state` (Boolean) Enable stackguardian managed terraform state. +- `post_apply_hooks` (List of String) Hooks to run after apply. +- `post_apply_wf_steps_config` (Attributes List) Workflow steps configuration to run after apply. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_apply_wf_steps_config)) +- `post_plan_hooks` (List of String) Hooks to run after plan. +- `post_plan_wf_steps_config` (Attributes List) Workflow steps configuration to run after plan. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_plan_wf_steps_config)) +- `pre_apply_hooks` (List of String) Hooks to run before apply. +- `pre_apply_wf_steps_config` (Attributes List) Workflow steps configuration to run before apply. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_apply_wf_steps_config)) +- `pre_init_hooks` (List of String) Hooks to run before init. +- `pre_plan_hooks` (List of String) Hooks to run before plan. +- `pre_plan_wf_steps_config` (Attributes List) Workflow steps configuration to run before plan. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_plan_wf_steps_config)) +- `run_pre_init_hooks_on_drift` (Boolean) Run pre-init hooks on drift detection. +- `terraform_bin_path` (Attributes List) Mount points for terraform binary. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--terraform_bin_path)) +- `terraform_init_options` (String) Additional options for terraform init. +- `terraform_plan_options` (String) Additional options for terraform plan. +- `terraform_version` (String) Terraform version to use. +- `timeout` (Number) Timeout for terraform operations in seconds. + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_apply_wf_steps_config` + +Read-Only: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_apply_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_apply_wf_steps_config--mount_points)) +- `name` (String) Step name. +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_apply_wf_steps_config--wf_step_input_data)) +- `wf_step_template_id` (String) Workflow step template ID. + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_apply_wf_steps_config.environment_variables` + +Read-Only: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_apply_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_apply_wf_steps_config.environment_variables.config` + +Read-Only: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT +- `var_name` (String) Name of the variable. + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_apply_wf_steps_config.mount_points` + +Read-Only: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_apply_wf_steps_config.wf_step_input_data` + +Read-Only: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_plan_wf_steps_config` + +Read-Only: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_plan_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_plan_wf_steps_config--mount_points)) +- `name` (String) Step name. +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_plan_wf_steps_config--wf_step_input_data)) +- `wf_step_template_id` (String) Workflow step template ID. + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_plan_wf_steps_config.environment_variables` + +Read-Only: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_plan_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_plan_wf_steps_config.environment_variables.config` + +Read-Only: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT +- `var_name` (String) Name of the variable. + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_plan_wf_steps_config.mount_points` + +Read-Only: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_plan_wf_steps_config.wf_step_input_data` + +Read-Only: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_apply_wf_steps_config` + +Read-Only: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_apply_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_apply_wf_steps_config--mount_points)) +- `name` (String) Step name. +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_apply_wf_steps_config--wf_step_input_data)) +- `wf_step_template_id` (String) Workflow step template ID. + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_apply_wf_steps_config.environment_variables` + +Read-Only: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_apply_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_apply_wf_steps_config.environment_variables.config` + +Read-Only: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT +- `var_name` (String) Name of the variable. + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_apply_wf_steps_config.mount_points` + +Read-Only: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_apply_wf_steps_config.wf_step_input_data` + +Read-Only: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_plan_wf_steps_config` + +Read-Only: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_plan_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_plan_wf_steps_config--mount_points)) +- `name` (String) Step name. +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_plan_wf_steps_config--wf_step_input_data)) +- `wf_step_template_id` (String) Workflow step template ID. + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_plan_wf_steps_config.environment_variables` + +Read-Only: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_plan_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_plan_wf_steps_config.environment_variables.config` + +Read-Only: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT +- `var_name` (String) Name of the variable. + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_plan_wf_steps_config.mount_points` + +Read-Only: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_plan_wf_steps_config.wf_step_input_data` + +Read-Only: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.terraform_bin_path` + +Read-Only: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + + +### Nested Schema for `workflows_config.workflows.user_schedules` + +Read-Only: + +- `cron` (String) Cron expression defining the schedule. +- `desc` (String) +- `name` (String) +- `state` (String) State of the schedule. Options: ENABLED, DISABLED + + + +### Nested Schema for `workflows_config.workflows.vcs_config` + +Read-Only: + +- `iac_input_data` (Attributes) IaC input data for the workflow. (see [below for nested schema](#nestedatt--workflows_config--workflows--vcs_config--iac_input_data)) +- `iac_vcs_config` (Attributes) IaC VCS configuration. (see [below for nested schema](#nestedatt--workflows_config--workflows--vcs_config--iac_vcs_config)) + + +### Nested Schema for `workflows_config.workflows.vcs_config.iac_input_data` + +Read-Only: + +- `data` (String) Input data as a JSON string. +- `schema_id` (String) +- `schema_type` (String) + + + +### Nested Schema for `workflows_config.workflows.vcs_config.iac_vcs_config` + +Read-Only: + +- `custom_source` (Attributes) Custom source configuration. (see [below for nested schema](#nestedatt--workflows_config--workflows--vcs_config--iac_vcs_config--custom_source)) +- `iac_template_id` (String) ID of the IaC template from the marketplace. +- `use_marketplace_template` (Boolean) Whether to use a marketplace template. + + +### Nested Schema for `workflows_config.workflows.vcs_config.iac_vcs_config.custom_source` + +Read-Only: + +- `config` (Attributes) Source configuration details. (see [below for nested schema](#nestedatt--workflows_config--workflows--vcs_config--iac_vcs_config--custom_source--config)) +- `source_config_dest_kind` (String) Destination kind for the source configuration. Options: GITHUB_COM, GITHUB_APP_CUSTOM, GITLAB_OAUTH_SSH, GITLAB_COM, AZURE_DEVOPS + + +### Nested Schema for `workflows_config.workflows.vcs_config.iac_vcs_config.custom_source.config` + +Read-Only: + +- `auth` (String, Sensitive) +- `git_core_auto_crlf` (Boolean) +- `git_sparse_checkout_config` (String) +- `include_sub_module` (Boolean) +- `is_private` (Boolean) +- `ref` (String) +- `repo` (String) +- `working_dir` (String) + + + + + + +### Nested Schema for `workflows_config.workflows.wf_steps_config` + +Read-Only: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--workflows_config--workflows--wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--workflows_config--workflows--wf_steps_config--mount_points)) +- `name` (String) Step name. +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--workflows_config--workflows--wf_steps_config--wf_step_input_data)) +- `wf_step_template_id` (String) Workflow step template ID. + + +### Nested Schema for `workflows_config.workflows.wf_steps_config.environment_variables` + +Read-Only: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.wf_steps_config.environment_variables.config` + +Read-Only: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT +- `var_name` (String) Name of the variable. + + + + +### Nested Schema for `workflows_config.workflows.wf_steps_config.mount_points` + +Read-Only: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `workflows_config.workflows.wf_steps_config.wf_step_input_data` + +Read-Only: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + + + diff --git a/docs/resources/stack_template.md b/docs/resources/stack_template.md new file mode 100644 index 0000000..ffe7279 --- /dev/null +++ b/docs/resources/stack_template.md @@ -0,0 +1,78 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_stack_template Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_stack_template (Resource) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +```terraform +# Example 1: Basic stack template +resource "stackguardian_stack_template" "basic" { + template_name = "my-stack-template" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "production"] +} +``` + + +## Schema + +### Required + +- `source_config_kind` (String) Source configuration kind for the stack template. Valid values: + TERRAFORM, + OPENTOFU, + ANSIBLE_PLAYBOOK, + HELM, + KUBECTL, + CLOUDFORMATION, + MIXED, + CUSTOM +- `template_name` (String) Name of the stack template. Must be less than 100 characters. + +### Optional + +- `context_tags` (Map of String) Context tags for stack template +- `description` (String) A brief description of the stack template. Must be less than 256 characters. +- `id` (String) ID of the resource — Use this attribute: +- `is_active` (String) Whether the stack template is active. Valid values: + 0 (false), + 1 (true) +- `is_public` (String) Whether the stack template is publicly available. Valid values: + 0 (false), + 1 (true) +- `shared_orgs_list` (List of String) List of organization IDs with which this template is shared. +- `tags` (List of String) A list of tags associated with the stack template. A maximum of 10 tags are allowed. + +### Read-Only + +- `owner_org` (String) Organization that owns the stack template. + + + +## Import + +Import existing resource + +### Using Import block (terraform v1.5.0 and later) +```terraform +import { + to = stackguardian_stack_template.example + id = "template-name" +} +``` + +### Using CLI +```bash +terraform import stackguardian_stack_template.example template-name +``` diff --git a/docs/resources/stack_template_revision.md b/docs/resources/stack_template_revision.md new file mode 100644 index 0000000..01f9c4b --- /dev/null +++ b/docs/resources/stack_template_revision.md @@ -0,0 +1,1003 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_stack_template_revision Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_stack_template_revision (Resource) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +```terraform +resource "stackguardian_stack_template" "example" { + template_name = "my-stack-template" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "production"] +} + +# Example 1: Basic stack template revision +resource "stackguardian_stack_template_revision" "basic" { + parent_template_id = stackguardian_stack_template.example.id + alias = "v1" + notes = "Initial revision" + description = "First revision of the stack template" + source_config_kind = "TERRAFORM" + + workflows_config = { + workflows = [ + { + id = "d8dfaf15-2ad9-da29-8af0-c6b288b12089" + template_id = "my-workflow-template" + resource_name = "wf-1" + + terraform_config = { + managed_terraform_state = true + terraform_version = "1.5.7" + } + } + ] + } +} + +# Example 2: Stack template revision with VCS config +resource "stackguardian_stack_template_revision" "with_vcs" { + parent_template_id = stackguardian_stack_template.example.id + alias = "v2" + notes = "Revision with VCS configuration" + description = "Stack template revision with VCS and input data" + source_config_kind = "TERRAFORM" + + workflows_config = { + workflows = [ + { + id = "d8dfaf15-2ad9-da29-8af0-c6b288b12089" + template_id = "my-workflow-template" + resource_name = "wf-1" + + vcs_config = { + iac_vcs_config = { + use_marketplace_template = true + iac_template_id = "my-workflow-template" + } + iac_input_data = { + schema_type = "RAW_JSON" + data = jsonencode({ + bucket_region = "eu-central-1" + }) + } + } + + terraform_config = { + managed_terraform_state = true + terraform_version = "1.5.7" + } + } + ] + } +} +``` + + +## Schema + +### Required + +- `parent_template_id` (String) ID of the parent stack template to create the revision under. +- `source_config_kind` (String) Source configuration kind for the stack template. Valid values: + TERRAFORM, + OPENTOFU, + ANSIBLE_PLAYBOOK, + HELM, + KUBECTL, + CLOUDFORMATION, + MIXED, + CUSTOM + +### Optional + +- `actions` (Attributes Map) JSON-encoded map of actions for the stack template revision. (see [below for nested schema](#nestedatt--actions)) +- `alias` (String) Human-readable alias for the revision (e.g., `v1.0.0`). +- `context_tags` (Map of String) Context tags for stack template revision +- `deprecation` (Attributes) Deprecation information for this resource. (see [below for nested schema](#nestedatt--deprecation)) +- `description` (String) Long description for the stack template revision. +- `is_active` (String) Whether the stack template is active. Valid values: + 0 (false), + 1 (true) +- `is_public` (String) Whether the stack template is publicly available. Valid values: + 0 (false), + 1 (true) +- `notes` (String) Release notes or changelog for this revision. +- `tags` (List of String) A list of tags associated with the stack template revision. A maximum of 10 tags are allowed. +- `workflows_config` (Attributes) JSON-encoded workflows configuration for the stack template revision. (see [below for nested schema](#nestedatt--workflows_config)) + +### Read-Only + +- `id` (String) Unique identifier of the stack template revision. +- `template_id` (String) ID of the parent stack template. + + +### Nested Schema for `actions` + +Optional: + +- `default` (Boolean) Whether this is the default action. +- `description` (String) Description of the action. +- `name` (String) Name of the action. +- `order` (Attributes Map) Ordered map of workflow IDs to their action configurations. (see [below for nested schema](#nestedatt--actions--order)) + + +### Nested Schema for `actions.order` + +Optional: + +- `dependencies` (Attributes List) List of workflow dependencies that must complete before this step runs. (see [below for nested schema](#nestedatt--actions--order--dependencies)) +- `parameters` (Attributes) Run configuration parameters for the action step. (see [below for nested schema](#nestedatt--actions--order--parameters)) + + +### Nested Schema for `actions.order.dependencies` + +Required: + +- `id` (String) ID of the dependent workflow. + +Optional: + +- `condition` (Attributes) Condition that must be met by the dependency. (see [below for nested schema](#nestedatt--actions--order--dependencies--condition)) + + +### Nested Schema for `actions.order.dependencies.condition` + +Required: + +- `latest_status` (String) Required latest status of the dependency (e.g., COMPLETED). + + + + +### Nested Schema for `actions.order.parameters` + +Optional: + +- `deployment_platform_config` (Attributes List) Deployment platform configuration. (see [below for nested schema](#nestedatt--actions--order--parameters--deployment_platform_config)) +- `environment_variables` (Attributes List) Environment variables. (see [below for nested schema](#nestedatt--actions--order--parameters--environment_variables)) +- `terraform_action` (Attributes) Terraform-specific action parameters. (see [below for nested schema](#nestedatt--actions--order--parameters--terraform_action)) +- `wf_steps_config` (Attributes List) Workflow steps configuration. (see [below for nested schema](#nestedatt--actions--order--parameters--wf_steps_config)) + + +### Nested Schema for `actions.order.parameters.deployment_platform_config` + +Required: + +- `kind` (String) Deployment platform kind. Options: AWS_STATIC, AWS_RBAC, AWS_OIDC, AZURE_STATIC, AZURE_OIDC, GCP_STATIC, GCP_OIDC + +Optional: + +- `config` (String) Deployment platform configuration details. (JSON string) + + + +### Nested Schema for `actions.order.parameters.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--actions--order--parameters--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `actions.order.parameters.environment_variables.config` + +Required: + +- `var_name` (String) Name of the variable. + +Optional: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT + + + + +### Nested Schema for `actions.order.parameters.terraform_action` + +Read-Only: + +- `action` (String) Terraform action to execute. E.g., "apply", "plan", "destroy". + + + +### Nested Schema for `actions.order.parameters.wf_steps_config` + +Required: + +- `name` (String) Step name. +- `wf_step_template_id` (String) Workflow step template ID. + +Optional: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--actions--order--parameters--wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--actions--order--parameters--wf_steps_config--mount_points)) +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--actions--order--parameters--wf_steps_config--wf_step_input_data)) + + +### Nested Schema for `actions.order.parameters.wf_steps_config.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--actions--order--parameters--wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `actions.order.parameters.wf_steps_config.environment_variables.config` + +Required: + +- `var_name` (String) Name of the variable. + +Optional: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT + + + + +### Nested Schema for `actions.order.parameters.wf_steps_config.mount_points` + +Optional: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `actions.order.parameters.wf_steps_config.wf_step_input_data` + +Optional: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + + + + +### Nested Schema for `deprecation` + +Optional: + +- `effective_date` (String) Effective date for after which revision will be deprecated +- `message` (String) Marking a template revision for deprecation + + + +### Nested Schema for `workflows_config` + +Optional: + +- `workflows` (Attributes List) List of workflows that make up the stack. (see [below for nested schema](#nestedatt--workflows_config--workflows)) + + +### Nested Schema for `workflows_config.workflows` + +Required: + +- `id` (String) UUID identifying the workflow within the stack template. +- `template_id` (String) ID of the workflow template that this workflow is based on. + +Optional: + +- `approvers` (List of String) List of approvers. +- `deployment_platform_config` (Attributes List) Deployment platform configuration. (see [below for nested schema](#nestedatt--workflows_config--workflows--deployment_platform_config)) +- `environment_variables` (Attributes List) Environment variables for the workflow. (see [below for nested schema](#nestedatt--workflows_config--workflows--environment_variables)) +- `iac_input_data` (Attributes) Top-level IaC input data for this workflow, used when the workflow is instantiated from a workflow template (`template_id`). (see [below for nested schema](#nestedatt--workflows_config--workflows--iac_input_data)) +- `input_schemas` (Attributes List) Input schema definitions for this workflow. (see [below for nested schema](#nestedatt--workflows_config--workflows--input_schemas)) +- `mini_steps` (Attributes) Actions that are required to be performed once workflow execution is complete (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps)) +- `number_of_approvals_required` (Number) Number of approvals required. +- `resource_name` (String) Name of the workflow resource within the stack. +- `runner_constraints` (Attributes) Runner constraints for the workflow. (see [below for nested schema](#nestedatt--workflows_config--workflows--runner_constraints)) +- `terraform_config` (Attributes) Terraform configuration. Valid only for terraform type template (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config)) +- `user_job_cpu` (Number) CPU limit for the user job. +- `user_job_memory` (Number) Memory limit for the user job. +- `user_schedules` (Attributes List) Scheduled run configuration. (see [below for nested schema](#nestedatt--workflows_config--workflows--user_schedules)) +- `vcs_config` (Attributes) VCS (version control) configuration for the workflow. (see [below for nested schema](#nestedatt--workflows_config--workflows--vcs_config)) +- `wf_steps_config` (Attributes List) Workflow steps configuration. (see [below for nested schema](#nestedatt--workflows_config--workflows--wf_steps_config)) + + +### Nested Schema for `workflows_config.workflows.deployment_platform_config` + +Required: + +- `kind` (String) Deployment platform kind. Options: AWS_STATIC, AWS_RBAC, AWS_OIDC, AZURE_STATIC, AZURE_OIDC, GCP_STATIC, GCP_OIDC + +Optional: + +- `config` (String) Deployment platform configuration details. (JSON string) + + + +### Nested Schema for `workflows_config.workflows.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.environment_variables.config` + +Required: + +- `var_name` (String) Name of the variable. + +Optional: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT + + + + +### Nested Schema for `workflows_config.workflows.iac_input_data` + +Required: + +- `schema_type` (String) Schema type for the input data (e.g. RAW_JSON). + +Optional: + +- `data` (String) Input data as a JSON string. + + + +### Nested Schema for `workflows_config.workflows.input_schemas` + +Required: + +- `type` (String) Schema type (e.g. FORM_JSONSCHEMA). + +Optional: + +- `description` (String) +- `encoded_data` (String) Base64-encoded schema data. +- `id` (String) +- `is_committed` (Boolean) +- `name` (String) +- `ui_schema_data` (String) + + + +### Nested Schema for `workflows_config.workflows.mini_steps` + +Optional: + +- `notifications` (Attributes) Configuration for notifications to be sent on workflow completion (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--notifications)) +- `webhooks` (Attributes) Configuration for webhooks to be triggered on completion. Statuses on which webhooks can be sent: approval_required, cancelled, completed, drift_detected, errored (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--webhooks)) +- `wf_chaining` (Attributes) Configuration for other workflows to be triggered on completion. Statuses on which workflows can be chained: completed, errored (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--wf_chaining)) + + +### Nested Schema for `workflows_config.workflows.mini_steps.notifications` + +Optional: + +- `email` (Attributes) Configuration for email notifications to be sent on completion. Statuses on which notifications can be sent: approval_required, cancelled, completed, drift_detected, errored (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--notifications--email)) + + +### Nested Schema for `workflows_config.workflows.mini_steps.notifications.email` + +Optional: + +- `approval_required` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--notifications--email--approval_required)) +- `cancelled` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--notifications--email--cancelled)) +- `completed` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--notifications--email--completed)) +- `drift_detected` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--notifications--email--drift_detected)) +- `errored` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--notifications--email--errored)) + + +### Nested Schema for `workflows_config.workflows.mini_steps.notifications.email.approval_required` + +Optional: + +- `recipients` (List of String) List of emails + + + +### Nested Schema for `workflows_config.workflows.mini_steps.notifications.email.cancelled` + +Optional: + +- `recipients` (List of String) List of emails + + + +### Nested Schema for `workflows_config.workflows.mini_steps.notifications.email.completed` + +Optional: + +- `recipients` (List of String) List of emails + + + +### Nested Schema for `workflows_config.workflows.mini_steps.notifications.email.drift_detected` + +Optional: + +- `recipients` (List of String) List of emails + + + +### Nested Schema for `workflows_config.workflows.mini_steps.notifications.email.errored` + +Optional: + +- `recipients` (List of String) List of emails + + + + + +### Nested Schema for `workflows_config.workflows.mini_steps.webhooks` + +Optional: + +- `approval_required` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--webhooks--approval_required)) +- `cancelled` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--webhooks--cancelled)) +- `completed` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--webhooks--completed)) +- `drift_detected` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--webhooks--drift_detected)) +- `errored` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--webhooks--errored)) + + +### Nested Schema for `workflows_config.workflows.mini_steps.webhooks.approval_required` + +Required: + +- `webhook_name` (String) Webhook name +- `webhook_url` (String) Webhook URL + +Optional: + +- `webhook_secret` (String) Secret to be sent with API request to webhook url + + + +### Nested Schema for `workflows_config.workflows.mini_steps.webhooks.cancelled` + +Required: + +- `webhook_name` (String) Webhook name +- `webhook_url` (String) Webhook URL + +Optional: + +- `webhook_secret` (String) Secret to be sent with API request to webhook url + + + +### Nested Schema for `workflows_config.workflows.mini_steps.webhooks.completed` + +Required: + +- `webhook_name` (String) Webhook name +- `webhook_url` (String) Webhook URL + +Optional: + +- `webhook_secret` (String) Secret to be sent with API request to webhook url + + + +### Nested Schema for `workflows_config.workflows.mini_steps.webhooks.drift_detected` + +Required: + +- `webhook_name` (String) Webhook name +- `webhook_url` (String) Webhook URL + +Optional: + +- `webhook_secret` (String) Secret to be sent with API request to webhook url + + + +### Nested Schema for `workflows_config.workflows.mini_steps.webhooks.errored` + +Required: + +- `webhook_name` (String) Webhook name +- `webhook_url` (String) Webhook URL + +Optional: + +- `webhook_secret` (String) Secret to be sent with API request to webhook url + + + + +### Nested Schema for `workflows_config.workflows.mini_steps.wf_chaining` + +Optional: + +- `completed` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--wf_chaining--completed)) +- `errored` (Attributes List) (see [below for nested schema](#nestedatt--workflows_config--workflows--mini_steps--wf_chaining--errored)) + + +### Nested Schema for `workflows_config.workflows.mini_steps.wf_chaining.completed` + +Required: + +- `workflow_group_id` (String) Workflow group id for the workflow. + +Optional: + +- `stack_id` (String) Stack id for the stack to be triggered. +- `stack_run_payload` (String) JSON string specifying overrides for the stack to be triggered +- `workflow_id` (String) Workflow id for the workflow to be triggered +- `workflow_run_payload` (String) JSON string specifying overrides for the workflow to be triggered + + + +### Nested Schema for `workflows_config.workflows.mini_steps.wf_chaining.errored` + +Required: + +- `workflow_group_id` (String) Workflow group id for the workflow. + +Optional: + +- `stack_id` (String) Stack id for the stack to be triggered. +- `stack_run_payload` (String) JSON string specifying overrides for the stack to be triggered +- `workflow_id` (String) Workflow id for the workflow to be triggered +- `workflow_run_payload` (String) JSON string specifying overrides for the workflow to be triggered + + + + + +### Nested Schema for `workflows_config.workflows.runner_constraints` + +Required: + +- `type` (String) Type of runner. Valid options: shared or external + +Optional: + +- `names` (List of String) Id of the runner group + + + +### Nested Schema for `workflows_config.workflows.terraform_config` + +Optional: + +- `approval_pre_apply` (Boolean) Require approval before apply. +- `drift_check` (Boolean) Enable drift check. +- `drift_cron` (String) Cron expression for drift check. +- `managed_terraform_state` (Boolean) Enable stackguardian managed terraform state. +- `post_apply_hooks` (List of String) Hooks to run after apply. +- `post_apply_wf_steps_config` (Attributes List) Workflow steps configuration to run after apply. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_apply_wf_steps_config)) +- `post_plan_hooks` (List of String) Hooks to run after plan. +- `post_plan_wf_steps_config` (Attributes List) Workflow steps configuration to run after plan. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_plan_wf_steps_config)) +- `pre_apply_hooks` (List of String) Hooks to run before apply. +- `pre_apply_wf_steps_config` (Attributes List) Workflow steps configuration to run before apply. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_apply_wf_steps_config)) +- `pre_init_hooks` (List of String) Hooks to run before init. +- `pre_plan_hooks` (List of String) Hooks to run before plan. +- `pre_plan_wf_steps_config` (Attributes List) Workflow steps configuration to run before plan. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_plan_wf_steps_config)) +- `run_pre_init_hooks_on_drift` (Boolean) Run pre-init hooks on drift detection. +- `terraform_bin_path` (Attributes List) Mount points for terraform binary. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--terraform_bin_path)) +- `terraform_init_options` (String) Additional options for terraform init. +- `terraform_plan_options` (String) Additional options for terraform plan. +- `terraform_version` (String) Terraform version to use. +- `timeout` (Number) Timeout for terraform operations in seconds. + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_apply_wf_steps_config` + +Required: + +- `name` (String) Step name. +- `wf_step_template_id` (String) Workflow step template ID. + +Optional: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_apply_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_apply_wf_steps_config--mount_points)) +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_apply_wf_steps_config--wf_step_input_data)) + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_apply_wf_steps_config.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_apply_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_apply_wf_steps_config.environment_variables.config` + +Required: + +- `var_name` (String) Name of the variable. + +Optional: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_apply_wf_steps_config.mount_points` + +Optional: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_apply_wf_steps_config.wf_step_input_data` + +Optional: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_plan_wf_steps_config` + +Required: + +- `name` (String) Step name. +- `wf_step_template_id` (String) Workflow step template ID. + +Optional: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_plan_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_plan_wf_steps_config--mount_points)) +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_plan_wf_steps_config--wf_step_input_data)) + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_plan_wf_steps_config.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--post_plan_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_plan_wf_steps_config.environment_variables.config` + +Required: + +- `var_name` (String) Name of the variable. + +Optional: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_plan_wf_steps_config.mount_points` + +Optional: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `workflows_config.workflows.terraform_config.post_plan_wf_steps_config.wf_step_input_data` + +Optional: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_apply_wf_steps_config` + +Required: + +- `name` (String) Step name. +- `wf_step_template_id` (String) Workflow step template ID. + +Optional: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_apply_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_apply_wf_steps_config--mount_points)) +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_apply_wf_steps_config--wf_step_input_data)) + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_apply_wf_steps_config.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_apply_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_apply_wf_steps_config.environment_variables.config` + +Required: + +- `var_name` (String) Name of the variable. + +Optional: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_apply_wf_steps_config.mount_points` + +Optional: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_apply_wf_steps_config.wf_step_input_data` + +Optional: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_plan_wf_steps_config` + +Required: + +- `name` (String) Step name. +- `wf_step_template_id` (String) Workflow step template ID. + +Optional: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_plan_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_plan_wf_steps_config--mount_points)) +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_plan_wf_steps_config--wf_step_input_data)) + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_plan_wf_steps_config.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--terraform_config--pre_plan_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_plan_wf_steps_config.environment_variables.config` + +Required: + +- `var_name` (String) Name of the variable. + +Optional: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_plan_wf_steps_config.mount_points` + +Optional: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `workflows_config.workflows.terraform_config.pre_plan_wf_steps_config.wf_step_input_data` + +Optional: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + +### Nested Schema for `workflows_config.workflows.terraform_config.terraform_bin_path` + +Optional: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + + +### Nested Schema for `workflows_config.workflows.user_schedules` + +Required: + +- `cron` (String) Cron expression defining the schedule. +- `state` (String) State of the schedule. Options: ENABLED, DISABLED + +Optional: + +- `desc` (String) +- `name` (String) + + + +### Nested Schema for `workflows_config.workflows.vcs_config` + +Optional: + +- `iac_input_data` (Attributes) IaC input data for the workflow. (see [below for nested schema](#nestedatt--workflows_config--workflows--vcs_config--iac_input_data)) +- `iac_vcs_config` (Attributes) IaC VCS configuration. (see [below for nested schema](#nestedatt--workflows_config--workflows--vcs_config--iac_vcs_config)) + + +### Nested Schema for `workflows_config.workflows.vcs_config.iac_input_data` + +Required: + +- `schema_type` (String) + +Optional: + +- `data` (String) Input data as a JSON string. +- `schema_id` (String) + + + +### Nested Schema for `workflows_config.workflows.vcs_config.iac_vcs_config` + +Optional: + +- `custom_source` (Attributes) Custom source configuration. (see [below for nested schema](#nestedatt--workflows_config--workflows--vcs_config--iac_vcs_config--custom_source)) +- `iac_template_id` (String) ID of the IaC template from the marketplace. +- `use_marketplace_template` (Boolean) Whether to use a marketplace template. + + +### Nested Schema for `workflows_config.workflows.vcs_config.iac_vcs_config.custom_source` + +Required: + +- `source_config_dest_kind` (String) Destination kind for the source configuration. Options: GITHUB_COM, GITHUB_APP_CUSTOM, GITLAB_OAUTH_SSH, GITLAB_COM, AZURE_DEVOPS + +Optional: + +- `config` (Attributes) Source configuration details. (see [below for nested schema](#nestedatt--workflows_config--workflows--vcs_config--iac_vcs_config--custom_source--config)) + + +### Nested Schema for `workflows_config.workflows.vcs_config.iac_vcs_config.custom_source.config` + +Optional: + +- `auth` (String, Sensitive) +- `git_core_auto_crlf` (Boolean) +- `git_sparse_checkout_config` (String) +- `include_sub_module` (Boolean) +- `is_private` (Boolean) +- `ref` (String) +- `repo` (String) +- `working_dir` (String) + + + + + + +### Nested Schema for `workflows_config.workflows.wf_steps_config` + +Required: + +- `name` (String) Step name. +- `wf_step_template_id` (String) Workflow step template ID. + +Optional: + +- `approval` (Boolean) Enable approval for the workflow step. +- `cmd_override` (String) Override command for the step. +- `environment_variables` (Attributes List) Environment variables for the workflow steps. (see [below for nested schema](#nestedatt--workflows_config--workflows--wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--workflows_config--workflows--wf_steps_config--mount_points)) +- `timeout` (Number) Workflow step execution timeout in seconds. +- `wf_step_input_data` (Attributes) Workflow step input data (JSON string) (see [below for nested schema](#nestedatt--workflows_config--workflows--wf_steps_config--wf_step_input_data)) + + +### Nested Schema for `workflows_config.workflows.wf_steps_config.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--workflows_config--workflows--wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `workflows_config.workflows.wf_steps_config.environment_variables.config` + +Required: + +- `var_name` (String) Name of the variable. + +Optional: + +- `secret_id` (String) ID of the secret (if using vault secret). Only if type is SECRET_REF +- `text_value` (String) Text value (if using plain text). Only if type is TEXT + + + + +### Nested Schema for `workflows_config.workflows.wf_steps_config.mount_points` + +Optional: + +- `read_only` (Boolean) If the directory is to be mounted as read only or not +- `source` (String) Source path for mount point. +- `target` (String) Target path for mount point. + + + +### Nested Schema for `workflows_config.workflows.wf_steps_config.wf_step_input_data` + +Optional: + +- `data` (String) Input data (JSON). +- `schema_type` (String) Schema type for the input data. Options: FORM_JSONSCHEMA + + + + + + + +## Import + +Import existing resource + +### Using Import block (terraform v1.5.0 and later) +```terraform +import { + to = stackguardian_stack_template_revision.example + id = "template-name:revision-version" +} +``` + +### Using CLI +```bash +terraform import stackguardian_stack_template_revision.example template-name:revision-version +``` diff --git a/go.mod b/go.mod index 217d31b..20c3bb9 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/StackGuardian/terraform-provider-stackguardian go 1.21.4 require ( - github.com/StackGuardian/sg-sdk-go v1.3.1 + github.com/StackGuardian/sg-sdk-go v1.4.1 github.com/hashicorp/terraform-plugin-framework v1.11.0 github.com/hashicorp/terraform-plugin-go v0.23.0 github.com/hashicorp/terraform-plugin-log v0.9.0 diff --git a/go.sum b/go.sum index d290eea..fc1a028 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/StackGuardian/sg-sdk-go v1.3.1 h1:WmuLhw1S98SJBepPS1Na+Gs9JBasdvrtFabSUudM9io= -github.com/StackGuardian/sg-sdk-go v1.3.1/go.mod h1:iCrgHE0WbxEaaLx6ATsRe81Qjp82kahCwLGJ2t66mZ0= +github.com/StackGuardian/sg-sdk-go v1.4.1 h1:0RIE7nxA4HRgcACaTcOqQqccVCvoq6HE/IeHHcHIcck= +github.com/StackGuardian/sg-sdk-go v1.4.1/go.mod h1:iCrgHE0WbxEaaLx6ATsRe81Qjp82kahCwLGJ2t66mZ0= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= diff --git a/internal/constants/stack_template.go b/internal/constants/stack_template.go new file mode 100644 index 0000000..9fdce5b --- /dev/null +++ b/internal/constants/stack_template.go @@ -0,0 +1,45 @@ +package constants + +// Stack Template - Common documentation +const ( + StackTemplateSourceConfigKindCommon = `Source configuration kind for the stack template. Valid values: + TERRAFORM, + OPENTOFU, + ANSIBLE_PLAYBOOK, + HELM, + KUBECTL, + CLOUDFORMATION, + MIXED, + CUSTOM` + + StackTemplateIsActiveCommon = `Whether the stack template is active. Valid values: + 0 (false), + 1 (true)` + + StackTemplateIsPublicCommon = `Whether the stack template is publicly available. Valid values: + 0 (false), + 1 (true)` +) + +// Stack Template Resource documentation +const ( + StackTemplateName = "Name of the stack template. Must be less than 100 characters." + StackTemplateOwnerOrg = "Organization that owns the stack template." + StackTemplateSharedOrgs = "List of organization IDs with which this template is shared." + StackTemplateDescription = "A brief description of the stack template." + StackTemplateTags = "A list of tags associated with the stack template." + StackTemplateContextTags = "Contextual key-value tags that provide additional context to the main tags." +) + +// Stack Template Revision Resource documentation +const ( + StackTemplateRevisionId = "Unique identifier of the stack template revision." + StackTemplateRevisionTemplateId = "ID of the parent stack template." + StackTemplateRevisionAlias = "Human-readable alias for the revision (e.g., `v1.0.0`)." + StackTemplateRevisionNotes = "Release notes or changelog for this revision." + StackTemplateRevisionDescription = "Long description for the stack template revision." + StackTemplateRevisionTags = "A list of tags associated with the revision." + StackTemplateRevisionContextTags = "Contextual key-value tags for the revision." + StackTemplateRevisionWorkflowsConfig = "JSON-encoded workflows configuration for the stack template revision." + StackTemplateRevisionActions = "JSON-encoded map of actions for the stack template revision." +) diff --git a/internal/datasources/stack_template/datasource.go b/internal/datasources/stack_template/datasource.go new file mode 100644 index 0000000..2e33fd5 --- /dev/null +++ b/internal/datasources/stack_template/datasource.go @@ -0,0 +1,137 @@ +package stacktemplatedatasource + +import ( + "context" + "fmt" + + sgclient "github.com/StackGuardian/sg-sdk-go/client" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/constants" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/customTypes" + stacktemplate "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/stack_template" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &stackTemplateDataSource{} + _ datasource.DataSourceWithConfigure = &stackTemplateDataSource{} +) + +// NewDataSource is a helper function to simplify the provider implementation. +func NewDataSource() datasource.DataSource { + return &stackTemplateDataSource{} +} + +type stackTemplateDataSource struct { + client *sgclient.Client + orgName string +} + +func (d *stackTemplateDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_stack_template" +} + +func (d *stackTemplateDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + provInfo, ok := req.ProviderData.(*customTypes.ProviderInfo) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *customTypes.ProviderInfo, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = provInfo.Client + d.orgName = provInfo.Org_name +} + +func (d *stackTemplateDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "> **Note:** This data source is currently in **BETA**. Features and behavior may change.\n\nUse this data source to read a stack template.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: constants.DatasourceId, + Required: true, + }, + "owner_org": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateOwnerOrg, + Computed: true, + }, + "template_name": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateName, + Computed: true, + }, + "source_config_kind": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateSourceConfigKindCommon, + Computed: true, + }, + "is_active": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateIsActiveCommon, + Computed: true, + }, + "is_public": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateIsPublicCommon, + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Description, "stack template"), + Computed: true, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Tags, "stack template"), + ElementType: types.StringType, + Computed: true, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: fmt.Sprintf(constants.ContextTags, "stack template"), + ElementType: types.StringType, + Computed: true, + }, + "shared_orgs_list": schema.ListAttribute{ + MarkdownDescription: constants.StackTemplateSharedOrgs, + ElementType: types.StringType, + Computed: true, + }, + }, + } +} + +func (d *stackTemplateDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config stacktemplate.StackTemplateResourceModel + + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + templateID := config.Id.ValueString() + if templateID == "" { + resp.Diagnostics.AddError("id must be provided", "") + return + } + + readResp, err := d.client.StackTemplates.ReadStackTemplate(ctx, d.orgName, templateID) + if err != nil { + resp.Diagnostics.AddError("Unable to read stack template.", err.Error()) + return + } + + if readResp == nil { + resp.Diagnostics.AddError("Error reading stack template", "API response is empty") + return + } + + model, diags := stacktemplate.BuildAPIModelToStackTemplateModel(&readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) +} diff --git a/internal/datasources/stack_template_revision/datasource.go b/internal/datasources/stack_template_revision/datasource.go new file mode 100644 index 0000000..d3fca09 --- /dev/null +++ b/internal/datasources/stack_template_revision/datasource.go @@ -0,0 +1,610 @@ +package stacktemplaterevisiondatasource + +import ( + "context" + "fmt" + + sgclient "github.com/StackGuardian/sg-sdk-go/client" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/constants" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/customTypes" + stacktemplaterevision "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/stack_template_revision" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &stackTemplateRevisionDataSource{} + _ datasource.DataSourceWithConfigure = &stackTemplateRevisionDataSource{} +) + +// NewDataSource is a helper function to simplify the provider implementation. +func NewDataSource() datasource.DataSource { + return &stackTemplateRevisionDataSource{} +} + +type stackTemplateRevisionDataSource struct { + client *sgclient.Client + orgName string +} + +func (d *stackTemplateRevisionDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_stack_template_revision" +} + +func (d *stackTemplateRevisionDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + provInfo, ok := req.ProviderData.(*customTypes.ProviderInfo) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *customTypes.ProviderInfo, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = provInfo.Client + d.orgName = provInfo.Org_name +} + +// Datasource-scoped schema helpers (mirrors resource/stack_template_revision/schema.go using datasource/schema types). + +var dsEnvVarsAttrs = map[string]schema.Attribute{ + "config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.EnvVarConfig, + Computed: true, + Attributes: map[string]schema.Attribute{ + "var_name": schema.StringAttribute{ + MarkdownDescription: constants.EnvVarConfigVarName, + Computed: true, + }, + "secret_id": schema.StringAttribute{ + MarkdownDescription: constants.EnvVarConfigSecretId, + Computed: true, + }, + "text_value": schema.StringAttribute{ + MarkdownDescription: constants.EnvVarConfigTextValue, + Computed: true, + }, + }, + }, + "kind": schema.StringAttribute{ + MarkdownDescription: constants.EnvVarKind, + Computed: true, + }, +} + +var dsMountPointAttrs = map[string]schema.Attribute{ + "source": schema.StringAttribute{ + MarkdownDescription: constants.MountPointSource, + Computed: true, + }, + "target": schema.StringAttribute{ + MarkdownDescription: constants.MountPointTarget, + Computed: true, + }, + "read_only": schema.BoolAttribute{ + MarkdownDescription: constants.MountPointReadOnly, + Computed: true, + }, +} + +var dsWfStepsConfigNestedObj = schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: constants.WfStepName, + Computed: true, + }, + "environment_variables": schema.ListNestedAttribute{ + MarkdownDescription: constants.WfStepEnvVars, + Computed: true, + NestedObject: schema.NestedAttributeObject{Attributes: dsEnvVarsAttrs}, + }, + "approval": schema.BoolAttribute{ + MarkdownDescription: constants.WfStepApproval, + Computed: true, + }, + "timeout": schema.Int64Attribute{ + MarkdownDescription: constants.WfStepTimeout, + Computed: true, + }, + "cmd_override": schema.StringAttribute{ + MarkdownDescription: constants.WfStepCmdOverride, + Computed: true, + }, + "mount_points": schema.ListNestedAttribute{ + MarkdownDescription: constants.WfStepMountPoints, + Computed: true, + NestedObject: schema.NestedAttributeObject{Attributes: dsMountPointAttrs}, + }, + "wf_step_template_id": schema.StringAttribute{ + MarkdownDescription: constants.WfStepTemplateId, + Computed: true, + }, + "wf_step_input_data": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WfStepInputData, + Computed: true, + Attributes: map[string]schema.Attribute{ + "schema_type": schema.StringAttribute{ + MarkdownDescription: constants.WfStepInputDataSchemaType, + Computed: true, + }, + "data": schema.StringAttribute{ + MarkdownDescription: constants.WfStepInputDataData, + Computed: true, + }, + }, + }, + }, +} + +var dsDeploymentPlatformConfigAttrs = map[string]schema.Attribute{ + "kind": schema.StringAttribute{ + MarkdownDescription: constants.DeploymentPlatformKind, + Computed: true, + }, + "config": schema.StringAttribute{ + MarkdownDescription: constants.DeploymentPlatformConfigDetails + " (JSON string)", + Computed: true, + }, +} + +var dsWorkflowInStackAttrs = map[string]schema.Attribute{ + "resource_name": schema.StringAttribute{ + MarkdownDescription: "Name of the workflow resource within the stack.", + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: "Description of this workflow.", + Computed: true, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: "Tags for the workflow.", + ElementType: types.StringType, + Computed: true, + }, + "is_active": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateIsActiveCommon, + Computed: true, + }, + "wf_type": schema.StringAttribute{ + MarkdownDescription: `Workflow type. Valid values: TERRAFORM, OPENTOFU, CUSTOM`, + Computed: true, + }, + "wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: "Workflow steps configuration.", + Computed: true, + NestedObject: dsWfStepsConfigNestedObj, + }, + "terraform_config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.TerraformConfig, + Computed: true, + Attributes: map[string]schema.Attribute{ + "terraform_version": schema.StringAttribute{ + MarkdownDescription: constants.TerraformVersion, + Computed: true, + }, + "drift_check": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformDriftCheck, + Computed: true, + }, + "drift_cron": schema.StringAttribute{ + MarkdownDescription: constants.TerraformDriftCron, + Computed: true, + }, + "managed_terraform_state": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformManagedState, + Computed: true, + }, + "approval_pre_apply": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformApprovalPreApply, + Computed: true, + }, + "terraform_plan_options": schema.StringAttribute{ + MarkdownDescription: constants.TerraformPlanOptions, + Computed: true, + }, + "terraform_init_options": schema.StringAttribute{ + MarkdownDescription: constants.TerraformInitOptions, + Computed: true, + }, + "terraform_bin_path": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformBinPath, + Computed: true, + NestedObject: schema.NestedAttributeObject{Attributes: dsMountPointAttrs}, + }, + "timeout": schema.Int64Attribute{ + MarkdownDescription: constants.TerraformTimeout, + Computed: true, + }, + "post_apply_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPostApplyWfSteps, + Computed: true, + NestedObject: dsWfStepsConfigNestedObj, + }, + "pre_apply_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPreApplyWfSteps, + Computed: true, + NestedObject: dsWfStepsConfigNestedObj, + }, + "pre_plan_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPrePlanWfSteps, + Computed: true, + NestedObject: dsWfStepsConfigNestedObj, + }, + "post_plan_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPostPlanWfSteps, + Computed: true, + NestedObject: dsWfStepsConfigNestedObj, + }, + "pre_init_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPreInitHooks, + Computed: true, + ElementType: types.StringType, + }, + "pre_plan_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPrePlanHooks, + Computed: true, + ElementType: types.StringType, + }, + "post_plan_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPostPlanHooks, + Computed: true, + ElementType: types.StringType, + }, + "pre_apply_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPreApplyHooks, + Computed: true, + ElementType: types.StringType, + }, + "post_apply_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPostApplyHooks, + Computed: true, + ElementType: types.StringType, + }, + "run_pre_init_hooks_on_drift": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformRunPreInitHooksOnDrift, + Computed: true, + }, + }, + }, + "environment_variables": schema.ListNestedAttribute{ + MarkdownDescription: "Environment variables for the workflow.", + Computed: true, + NestedObject: schema.NestedAttributeObject{Attributes: dsEnvVarsAttrs}, + }, + "deployment_platform_config": schema.ListNestedAttribute{ + MarkdownDescription: "Deployment platform configuration.", + Computed: true, + NestedObject: schema.NestedAttributeObject{Attributes: dsDeploymentPlatformConfigAttrs}, + }, + "vcs_config": schema.SingleNestedAttribute{ + MarkdownDescription: "VCS (version control) configuration for the workflow.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "iac_vcs_config": schema.SingleNestedAttribute{ + MarkdownDescription: "IaC VCS configuration.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "use_marketplace_template": schema.BoolAttribute{ + MarkdownDescription: "Whether to use a marketplace template.", + Computed: true, + }, + "iac_template_id": schema.StringAttribute{ + MarkdownDescription: "ID of the IaC template from the marketplace.", + Computed: true, + }, + "custom_source": schema.SingleNestedAttribute{ + MarkdownDescription: "Custom source configuration.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "source_config_dest_kind": schema.StringAttribute{ + MarkdownDescription: constants.RuntimeSourceDestKind, + Computed: true, + }, + "config": schema.SingleNestedAttribute{ + MarkdownDescription: "Source configuration details.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "is_private": schema.BoolAttribute{ + Computed: true, + }, + "auth": schema.StringAttribute{ + Computed: true, + Sensitive: true, + }, + "working_dir": schema.StringAttribute{ + Computed: true, + }, + "git_sparse_checkout_config": schema.StringAttribute{ + Computed: true, + }, + "git_core_auto_crlf": schema.BoolAttribute{ + Computed: true, + }, + "ref": schema.StringAttribute{ + Computed: true, + }, + "repo": schema.StringAttribute{ + Computed: true, + }, + "include_sub_module": schema.BoolAttribute{ + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "iac_input_data": schema.SingleNestedAttribute{ + MarkdownDescription: "IaC input data for the workflow.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "schema_id": schema.StringAttribute{ + Computed: true, + }, + "schema_type": schema.StringAttribute{ + Computed: true, + }, + "data": schema.StringAttribute{ + MarkdownDescription: "Input data as a JSON string.", + Computed: true, + }, + }, + }, + }, + }, + "user_schedules": schema.ListNestedAttribute{ + MarkdownDescription: "Scheduled run configuration.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Computed: true, + }, + "desc": schema.StringAttribute{ + Computed: true, + }, + "cron": schema.StringAttribute{ + MarkdownDescription: constants.UserScheduleCron, + Computed: true, + }, + "state": schema.StringAttribute{ + MarkdownDescription: constants.UserScheduleState, + Computed: true, + }, + }, + }, + }, + "approvers": schema.ListAttribute{ + MarkdownDescription: "List of approvers.", + ElementType: types.StringType, + Computed: true, + }, + "number_of_approvals_required": schema.Int64Attribute{ + MarkdownDescription: "Number of approvals required.", + Computed: true, + }, + "runner_constraints": schema.SingleNestedAttribute{ + MarkdownDescription: "Runner constraints for the workflow.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + MarkdownDescription: constants.RunnerConstraintsType, + Computed: true, + }, + "names": schema.ListAttribute{ + MarkdownDescription: constants.RunnerConstraintsNames, + ElementType: types.StringType, + Computed: true, + }, + }, + }, + "user_job_cpu": schema.Int64Attribute{ + MarkdownDescription: "CPU limit for the user job.", + Computed: true, + }, + "user_job_memory": schema.Int64Attribute{ + MarkdownDescription: "Memory limit for the user job.", + Computed: true, + }, +} + +var dsActionOrderAttrs = map[string]schema.Attribute{ + "parameters": schema.SingleNestedAttribute{ + MarkdownDescription: "Run configuration parameters for the action step.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "terraform_action": schema.SingleNestedAttribute{ + MarkdownDescription: "Terraform-specific action parameters.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + MarkdownDescription: `Terraform action to execute. E.g., "apply", "plan", "destroy".`, + Computed: true, + }, + }, + }, + "deployment_platform_config": schema.ListNestedAttribute{ + MarkdownDescription: "Deployment platform configuration.", + Computed: true, + NestedObject: schema.NestedAttributeObject{Attributes: dsDeploymentPlatformConfigAttrs}, + }, + "wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: "Workflow steps configuration.", + Computed: true, + NestedObject: dsWfStepsConfigNestedObj, + }, + "environment_variables": schema.ListNestedAttribute{ + MarkdownDescription: "Environment variables.", + Computed: true, + NestedObject: schema.NestedAttributeObject{Attributes: dsEnvVarsAttrs}, + }, + }, + }, + "dependencies": schema.ListNestedAttribute{ + MarkdownDescription: "List of workflow dependencies that must complete before this step runs.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "ID of the dependent workflow.", + Computed: true, + }, + "condition": schema.SingleNestedAttribute{ + MarkdownDescription: "Condition that must be met by the dependency.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "latest_status": schema.StringAttribute{ + MarkdownDescription: "Required latest status of the dependency (e.g., COMPLETED).", + Computed: true, + }, + }, + }, + }, + }, + }, +} + +func (d *stackTemplateRevisionDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "> **Note:** This data source is currently in **BETA**. Features and behavior may change.\n\nUse this data source to read a stack template revision.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: constants.DatasourceId, + Required: true, + }, + "template_id": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateRevisionTemplateId, + Computed: true, + }, + "alias": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateRevisionAlias, + Computed: true, + }, + "notes": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateRevisionNotes, + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateRevisionDescription, + Computed: true, + }, + "source_config_kind": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateSourceConfigKindCommon, + Computed: true, + }, + "is_active": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateIsActiveCommon, + Computed: true, + }, + "is_public": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateIsPublicCommon, + Computed: true, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Tags, "stack template revision"), + ElementType: types.StringType, + Computed: true, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: fmt.Sprintf(constants.ContextTags, "stack template revision"), + ElementType: types.StringType, + Computed: true, + }, + "deprecation": schema.SingleNestedAttribute{ + MarkdownDescription: constants.Deprecation, + Computed: true, + Attributes: map[string]schema.Attribute{ + "effective_date": schema.StringAttribute{ + MarkdownDescription: constants.TemplateRevisionDeprecationEffectiveDate, + Computed: true, + }, + "message": schema.StringAttribute{ + MarkdownDescription: constants.TemplateRevisionDeprecation, + Computed: true, + }, + }, + }, + "workflows_config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.StackTemplateRevisionWorkflowsConfig, + Computed: true, + Attributes: map[string]schema.Attribute{ + "workflows": schema.ListNestedAttribute{ + MarkdownDescription: "List of workflows that make up the stack.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: dsWorkflowInStackAttrs, + }, + }, + }, + }, + "actions": schema.MapNestedAttribute{ + MarkdownDescription: constants.StackTemplateRevisionActions, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the action.", + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: "Description of the action.", + Computed: true, + }, + "default": schema.BoolAttribute{ + MarkdownDescription: "Whether this is the default action.", + Computed: true, + }, + "order": schema.MapNestedAttribute{ + MarkdownDescription: "Ordered map of workflow IDs to their action configurations.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: dsActionOrderAttrs, + }, + }, + }, + }, + }, + }, + } +} + +func (d *stackTemplateRevisionDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config stacktemplaterevision.StackTemplateRevisionResourceModel + + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + revisionID := config.Id.ValueString() + if revisionID == "" { + resp.Diagnostics.AddError("id must be provided", "") + return + } + + readResp, err := d.client.StackTemplateRevisions.ReadStackTemplateRevision(ctx, d.orgName, revisionID) + if err != nil { + resp.Diagnostics.AddError("Unable to read stack template revision.", err.Error()) + return + } + + if readResp == nil { + resp.Diagnostics.AddError("Error reading stack template revision", "API response is empty") + return + } + + model, diags := stacktemplaterevision.BuildAPIModelToStackTemplateRevisionModel(ctx, &readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1377588..1875f69 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -16,6 +16,8 @@ import ( runnergroupdatasource "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/runner_group" runnergrouptoken "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/runner_group_token" stackoutputs "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/stack_outputs" + stacktemplatedatasource "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/stack_template" + stacktemplaterevisiondatasource "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/stack_template_revision" stackworkflowoutputs "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/stack_workflow_outputs" workflowgroupdatasource "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/workflow_group" workflowoutputs "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/workflow_outputs" @@ -29,6 +31,8 @@ import ( roleassignment "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/role_assignment" rolev4 "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/role_v4" runnergroup "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/runner_group" + stacktemplate "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/stack_template" + stacktemplaterevision "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/stack_template_revision" workflowgroup "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_group" workflowsteptemplate "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_step_template" workflowsteptemplaterevision "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_step_template_revision" @@ -228,6 +232,8 @@ func (p *stackguardianProvider) DataSources(_ context.Context) []func() datasour workflowsteptemplaterevisiondatasource.NewDataSource, workflowtemplatedatasource.NewDataSource, workflowtemplaterevisiondatasource.NewDataSource, + stacktemplatedatasource.NewDataSource, + stacktemplaterevisiondatasource.NewDataSource, } } @@ -245,5 +251,7 @@ func (p *stackguardianProvider) Resources(_ context.Context) []func() resource.R rolev4.NewResource, workflowtemplate.NewResource, workflowtemplaterevision.NewResource, + stacktemplate.NewResource, + stacktemplaterevision.NewResource, } } diff --git a/internal/resource/stack_template/model.go b/internal/resource/stack_template/model.go new file mode 100644 index 0000000..fa0e6f5 --- /dev/null +++ b/internal/resource/stack_template/model.go @@ -0,0 +1,203 @@ +package stacktemplate + +import ( + "context" + + sgsdkgo "github.com/StackGuardian/sg-sdk-go" + "github.com/StackGuardian/sg-sdk-go/stacktemplates" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/expanders" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/flatteners" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type StackTemplateResourceModel struct { + Id types.String `tfsdk:"id"` + TemplateName types.String `tfsdk:"template_name"` + OwnerOrg types.String `tfsdk:"owner_org"` + SourceConfigKind types.String `tfsdk:"source_config_kind"` + IsActive types.String `tfsdk:"is_active"` + IsPublic types.String `tfsdk:"is_public"` + ShortDescription types.String `tfsdk:"description"` + Tags types.List `tfsdk:"tags"` + ContextTags types.Map `tfsdk:"context_tags"` + SharedOrgsList types.List `tfsdk:"shared_orgs_list"` +} + +func (m *StackTemplateResourceModel) ToAPIModel(ctx context.Context) (*stacktemplates.CreateStackTemplateRequest, diag.Diagnostics) { + diags := diag.Diagnostics{} + + apiModel := &stacktemplates.CreateStackTemplateRequest{ + TemplateName: m.TemplateName.ValueString(), + ShortDescription: m.ShortDescription.ValueStringPointer(), + } + + if !m.SourceConfigKind.IsNull() && !m.SourceConfigKind.IsUnknown() { + apiModel.SourceConfigKind = (*stacktemplates.StackTemplateSourceConfigKindEnum)(m.SourceConfigKind.ValueStringPointer()) + } + + if !m.IsActive.IsNull() && !m.IsActive.IsUnknown() { + apiModel.IsActive = (*sgsdkgo.IsPublicEnum)(m.IsActive.ValueStringPointer()) + } + + if !m.IsPublic.IsNull() && !m.IsPublic.IsUnknown() { + apiModel.IsPublic = (*sgsdkgo.IsPublicEnum)(m.IsPublic.ValueStringPointer()) + } + + // Convert Tags + if !m.Tags.IsNull() && !m.Tags.IsUnknown() { + tags, diagsTags := expanders.StringList(ctx, m.Tags) + diags.Append(diagsTags...) + if !diags.HasError() { + apiModel.Tags = tags + } + } + + // Convert SharedOrgsList + if !m.SharedOrgsList.IsNull() && !m.SharedOrgsList.IsUnknown() { + sharedOrgs, diagsShared := expanders.StringList(ctx, m.SharedOrgsList) + diags.Append(diagsShared...) + if !diags.HasError() { + apiModel.SharedOrgsList = sharedOrgs + } + } + + // Convert ContextTags + if !m.ContextTags.IsNull() && !m.ContextTags.IsUnknown() { + contextTags := make(map[string]string) + diagsCT := m.ContextTags.ElementsAs(ctx, &contextTags, false) + diags.Append(diagsCT...) + if !diags.HasError() && len(contextTags) > 0 { + apiModel.ContextTags = contextTags + } + } + + return apiModel, diags +} + +func (m *StackTemplateResourceModel) ToUpdateAPIModel(ctx context.Context) (*stacktemplates.UpdateStackTemplateRequest, diag.Diagnostics) { + diags := diag.Diagnostics{} + + apiModel := &stacktemplates.UpdateStackTemplateRequest{ + TemplateName: sgsdkgo.Optional(m.TemplateName.ValueString()), + } + + if !m.SourceConfigKind.IsNull() && !m.SourceConfigKind.IsUnknown() { + apiModel.SourceConfigKind = sgsdkgo.Optional(stacktemplates.StackTemplateSourceConfigKindEnum(m.SourceConfigKind.ValueString())) + } else { + apiModel.SourceConfigKind = sgsdkgo.Null[stacktemplates.StackTemplateSourceConfigKindEnum]() + } + + if !m.ShortDescription.IsNull() && !m.ShortDescription.IsUnknown() { + apiModel.ShortDescription = sgsdkgo.Optional(m.ShortDescription.ValueString()) + } else { + apiModel.ShortDescription = sgsdkgo.Null[string]() + } + + if !m.IsActive.IsNull() && !m.IsActive.IsUnknown() { + apiModel.IsActive = sgsdkgo.Optional(sgsdkgo.IsPublicEnum(m.IsActive.ValueString())) + } + + if !m.IsPublic.IsNull() && !m.IsPublic.IsUnknown() { + apiModel.IsPublic = sgsdkgo.Optional(sgsdkgo.IsPublicEnum(m.IsPublic.ValueString())) + } + + // Convert Tags + tags, diagsTags := expanders.StringList(ctx, m.Tags) + diags.Append(diagsTags...) + if tags != nil { + apiModel.Tags = sgsdkgo.Optional(tags) + } else { + apiModel.Tags = sgsdkgo.Null[[]string]() + } + + // Convert ContextTags + contextTags, diagsCT := expanders.MapStringString(ctx, m.ContextTags) + diags.Append(diagsCT...) + if contextTags != nil { + apiModel.ContextTags = sgsdkgo.Optional(contextTags) + } else { + apiModel.ContextTags = sgsdkgo.Null[map[string]string]() + } + + // Convert SharedOrgsList + sharedOrgsList, diagsShared := expanders.StringList(ctx, m.SharedOrgsList) + diags.Append(diagsShared...) + if sharedOrgsList != nil { + apiModel.SharedOrgsList = sgsdkgo.Optional(sharedOrgsList) + } else { + apiModel.SharedOrgsList = sgsdkgo.Null[[]string]() + } + + return apiModel, diags +} + +func BuildAPIModelToStackTemplateModel(apiResponse *stacktemplates.ReadStackTemplateResponse) (*StackTemplateResourceModel, diag.Diagnostics) { + diags := diag.Diagnostics{} + + model := &StackTemplateResourceModel{ + Id: flatteners.StringPtr(apiResponse.Id), + TemplateName: flatteners.StringPtr(apiResponse.TemplateName), + OwnerOrg: flatteners.StringPtr(apiResponse.OwnerOrg), + ShortDescription: flatteners.StringPtr(apiResponse.ShortDescription), + } + + if apiResponse.SourceConfigKind != nil { + model.SourceConfigKind = flatteners.String(string(*apiResponse.SourceConfigKind)) + } else { + model.SourceConfigKind = types.StringNull() + } + + if apiResponse.IsActive != nil { + model.IsActive = flatteners.String(string(*apiResponse.IsActive)) + } else { + model.IsActive = types.StringNull() + } + + if apiResponse.IsPublic != nil { + model.IsPublic = flatteners.String(string(*apiResponse.IsPublic)) + } else { + model.IsPublic = types.StringNull() + } + + // Convert Tags + if apiResponse.Tags != nil { + var tags []types.String + for _, tag := range apiResponse.Tags { + tags = append(tags, flatteners.String(tag)) + } + tagsList, diagsTags := types.ListValueFrom(context.Background(), types.StringType, tags) + diags.Append(diagsTags...) + model.Tags = tagsList + } else { + model.Tags = types.ListNull(types.StringType) + } + + // Convert SharedOrgsList + if apiResponse.SharedOrgsList != nil { + var sharedOrgs []types.String + for _, org := range apiResponse.SharedOrgsList { + sharedOrgs = append(sharedOrgs, flatteners.String(org)) + } + sharedOrgsList, diagsShared := types.ListValueFrom(context.Background(), types.StringType, sharedOrgs) + diags.Append(diagsShared...) + model.SharedOrgsList = sharedOrgsList + } else { + model.SharedOrgsList = types.ListNull(types.StringType) + } + + // Convert ContextTags + if apiResponse.ContextTags != nil { + contextTags := make(map[string]types.String) + for k, v := range apiResponse.ContextTags { + contextTags[k] = flatteners.String(v) + } + contextTagsMap, diagsCT := types.MapValueFrom(context.Background(), types.StringType, contextTags) + diags.Append(diagsCT...) + model.ContextTags = contextTagsMap + } else { + model.ContextTags = types.MapNull(types.StringType) + } + + return model, diags +} diff --git a/internal/resource/stack_template/resource.go b/internal/resource/stack_template/resource.go new file mode 100644 index 0000000..e1f9644 --- /dev/null +++ b/internal/resource/stack_template/resource.go @@ -0,0 +1,196 @@ +package stacktemplate + +import ( + "context" + "fmt" + + sgclient "github.com/StackGuardian/sg-sdk-go/client" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/customTypes" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +var ( + _ resource.Resource = &stackTemplateResource{} + _ resource.ResourceWithConfigure = &stackTemplateResource{} + _ resource.ResourceWithImportState = &stackTemplateResource{} +) + +type stackTemplateResource struct { + client *sgclient.Client + org_name string +} + +// NewResource is a helper function to simplify the provider implementation. +func NewResource() resource.Resource { + return &stackTemplateResource{} +} + +// Metadata returns the resource type name. +func (r *stackTemplateResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_stack_template" +} + +// Configure adds the provider configured client to the resource. +func (r *stackTemplateResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + provider, ok := req.ProviderData.(*customTypes.ProviderInfo) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *customTypes.ProviderInfo, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = provider.Client + r.org_name = provider.Org_name +} + +// ImportState imports a stack template using its ID. +func (r *stackTemplateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...) +} + +// Create creates the resource and sets the initial Terraform state. +func (r *stackTemplateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan StackTemplateResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + payload, diags := plan.ToAPIModel(ctx) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + payload.OwnerOrg = fmt.Sprintf("/orgs/%v", r.org_name) + + createResp, err := r.client.StackTemplates.CreateStackTemplate(ctx, r.org_name, false, payload) + if err != nil { + resp.Diagnostics.AddError("Error creating stack template", "Error in creating stack template API call: "+err.Error()) + return + } + + templateID := *createResp.Data.Parent.Id + + // Call read to get the full state since create response doesn't return all values + readResp, err := r.client.StackTemplates.ReadStackTemplate(ctx, r.org_name, templateID) + if err != nil { + resp.Diagnostics.AddError("Error reading created stack template", "Could not read the created stack template: "+err.Error()) + return + } + + if readResp == nil { + resp.Diagnostics.AddError("Error reading stack template", "API response is empty") + return + } + + templateModel, diags := BuildAPIModelToStackTemplateModel(&readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &templateModel)...) +} + +// Read refreshes the Terraform state with the latest data. +func (r *stackTemplateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state StackTemplateResourceModel + + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + templateID := state.Id.ValueString() + + readResp, err := r.client.StackTemplates.ReadStackTemplate(ctx, r.org_name, templateID) + if err != nil { + resp.Diagnostics.AddError("Error reading stack template", "Error in reading stack template API call: "+err.Error()) + return + } + + templateModel, diags := BuildAPIModelToStackTemplateModel(&readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &templateModel)...) +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *stackTemplateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan StackTemplateResourceModel + var state StackTemplateResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + templateID := state.Id.ValueString() + + payload, diags := plan.ToUpdateAPIModel(ctx) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + _, err := r.client.StackTemplates.UpdateStackTemplate(ctx, r.org_name, templateID, payload) + if err != nil { + resp.Diagnostics.AddError("Error updating stack template", "Error in updating stack template API call: "+err.Error()) + return + } + + // Call read to get the updated state since update response doesn't return all values + readResp, err := r.client.StackTemplates.ReadStackTemplate(ctx, r.org_name, templateID) + if err != nil { + resp.Diagnostics.AddError("Error reading updated stack template", "Could not read the updated stack template: "+err.Error()) + return + } + + templateModel, diags := BuildAPIModelToStackTemplateModel(&readResp.Msg) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &templateModel)...) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *stackTemplateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state StackTemplateResourceModel + + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.StackTemplates.DeleteStackTemplate(ctx, r.org_name, state.Id.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error deleting stack template", "Error in deleting stack template API call: "+err.Error()) + return + } +} diff --git a/internal/resource/stack_template/resource_test.go b/internal/resource/stack_template/resource_test.go new file mode 100644 index 0000000..76e2cc2 --- /dev/null +++ b/internal/resource/stack_template/resource_test.go @@ -0,0 +1,71 @@ +package stacktemplate_test + +import ( + "fmt" + "net/http" + "testing" + + "github.com/StackGuardian/terraform-provider-stackguardian/internal/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +var sourceConfigKind = "TERRAFORM" + +func TestAccStackTemplate_Basic(t *testing.T) { + templateName := "tf-provider-stack-template-1" + + customHeader := http.Header{} + customHeader.Set("x-sg-internal-auth-orgid", "sg-provider-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), + }, + ProtoV6ProviderFactories: acctest.ProviderFactories(customHeader), + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccStackTemplateConfig(templateName, sourceConfigKind), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_stack_template.test", "template_name", templateName), + resource.TestCheckResourceAttr("stackguardian_stack_template.test", "is_public", "0"), + resource.TestCheckResourceAttrSet("stackguardian_stack_template.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccStackTemplateConfigUpdated(templateName, sourceConfigKind), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_stack_template.test", "template_name", templateName), + resource.TestCheckResourceAttr("stackguardian_stack_template.test", "description", "Updated description"), + ), + }, + // Delete testing automatically occurs + }, + }) +} + +func testAccStackTemplateConfig(name, sourceConfigKind string) string { + return fmt.Sprintf(` +resource "stackguardian_stack_template" "test" { + template_name = "%s" + source_config_kind = "%s" + is_public = "0" + tags = ["test", "terraform"] +} +`, name, sourceConfigKind) +} + +func testAccStackTemplateConfigUpdated(name, sourceConfigKind string) string { + return fmt.Sprintf(` +resource "stackguardian_stack_template" "test" { + template_name = "%s" + source_config_kind = "%s" + is_public = "1" + description = "Updated description" + tags = ["test", "terraform", "updated"] +} +`, name, sourceConfigKind) +} diff --git a/internal/resource/stack_template/schema.go b/internal/resource/stack_template/schema.go new file mode 100644 index 0000000..8988330 --- /dev/null +++ b/internal/resource/stack_template/schema.go @@ -0,0 +1,76 @@ +package stacktemplate + +import ( + "context" + "fmt" + + "github.com/StackGuardian/terraform-provider-stackguardian/internal/constants" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Schema defines the schema for the resource. +func (r *stackTemplateResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "> **Note:** This resource is currently in **BETA**. Features and behavior may change.\n\nManages a stack template resource.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: constants.Id, + Computed: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + stringplanmodifier.RequiresReplace(), + }, + }, + "owner_org": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateOwnerOrg, + Computed: true, + }, + "template_name": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateName, + Required: true, + }, + "source_config_kind": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateSourceConfigKindCommon, + Required: true, + }, + "is_active": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateIsActiveCommon, + Optional: true, + Computed: true, + }, + "is_public": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateIsPublicCommon, + Optional: true, + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Description, "stack template"), + Optional: true, + Computed: true, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Tags, "stack template"), + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: fmt.Sprintf(constants.ContextTags, "stack template"), + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "shared_orgs_list": schema.ListAttribute{ + MarkdownDescription: constants.StackTemplateSharedOrgs, + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + }, + } +} diff --git a/internal/resource/stack_template_revision/model.go b/internal/resource/stack_template_revision/model.go new file mode 100644 index 0000000..8ab783d --- /dev/null +++ b/internal/resource/stack_template_revision/model.go @@ -0,0 +1,2349 @@ +package stacktemplaterevision + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + sgsdkgo "github.com/StackGuardian/sg-sdk-go" + "github.com/StackGuardian/sg-sdk-go/stacktemplaterevisions" + "github.com/StackGuardian/sg-sdk-go/stacktemplates" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/expanders" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/flatteners" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// --------------------------------------------------------------------------- +// Root resource model +// --------------------------------------------------------------------------- + +type StackTemplateRevisionResourceModel struct { + Id types.String `tfsdk:"id"` + ParentTemplateId types.String `tfsdk:"parent_template_id"` + TemplateId types.String `tfsdk:"template_id"` + Alias types.String `tfsdk:"alias"` + Notes types.String `tfsdk:"notes"` + LongDescription types.String `tfsdk:"description"` + SourceConfigKind types.String `tfsdk:"source_config_kind"` + IsActive types.String `tfsdk:"is_active"` + IsPublic types.String `tfsdk:"is_public"` + Tags types.List `tfsdk:"tags"` + ContextTags types.Map `tfsdk:"context_tags"` + Deprecation types.Object `tfsdk:"deprecation"` + WorkflowsConfig types.Object `tfsdk:"workflows_config"` + Actions types.Map `tfsdk:"actions"` +} + +// --------------------------------------------------------------------------- +// Deprecation +// --------------------------------------------------------------------------- + +type DeprecationModel struct { + EffectiveDate types.String `tfsdk:"effective_date"` + Message types.String `tfsdk:"message"` +} + +func (DeprecationModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "effective_date": types.StringType, + "message": types.StringType, + } +} + +// --------------------------------------------------------------------------- +// Environment variables +// --------------------------------------------------------------------------- + +type EnvVarConfigModel struct { + VarName types.String `tfsdk:"var_name"` + SecretId types.String `tfsdk:"secret_id"` + TextValue types.String `tfsdk:"text_value"` +} + +func (EnvVarConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "var_name": types.StringType, + "secret_id": types.StringType, + "text_value": types.StringType, + } +} + +type EnvVarModel struct { + Config types.Object `tfsdk:"config"` + Kind types.String `tfsdk:"kind"` +} + +func (EnvVarModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "config": types.ObjectType{AttrTypes: EnvVarConfigModel{}.AttributeTypes()}, + "kind": types.StringType, + } +} + +// --------------------------------------------------------------------------- +// Mount points +// --------------------------------------------------------------------------- + +type MountPointModel struct { + Source types.String `tfsdk:"source"` + Target types.String `tfsdk:"target"` + ReadOnly types.Bool `tfsdk:"read_only"` +} + +func (MountPointModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "source": types.StringType, + "target": types.StringType, + "read_only": types.BoolType, + } +} + +// --------------------------------------------------------------------------- +// Wf step input data +// --------------------------------------------------------------------------- + +type WfStepInputDataModel struct { + SchemaType types.String `tfsdk:"schema_type"` + Data types.String `tfsdk:"data"` +} + +func (WfStepInputDataModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "schema_type": types.StringType, + "data": types.StringType, + } +} + +// --------------------------------------------------------------------------- +// Input schemas (for WorkflowsConfig workflows) +// --------------------------------------------------------------------------- + +type StackInputSchemaModel struct { + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Type types.String `tfsdk:"type"` + EncodedData types.String `tfsdk:"encoded_data"` + UiSchemaData types.String `tfsdk:"ui_schema_data"` + IsCommitted types.Bool `tfsdk:"is_committed"` +} + +func (StackInputSchemaModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "id": types.StringType, + "name": types.StringType, + "description": types.StringType, + "type": types.StringType, + "encoded_data": types.StringType, + "ui_schema_data": types.StringType, + "is_committed": types.BoolType, + } +} + +// --------------------------------------------------------------------------- +// MiniSteps +// --------------------------------------------------------------------------- + +type MinistepsNotificationRecipientsModel struct { + Recipients types.List `tfsdk:"recipients"` +} + +func (MinistepsNotificationRecipientsModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "recipients": types.ListType{ElemType: types.StringType}, + } +} + +type MinistepsWebhooksModel struct { + WebhookName types.String `tfsdk:"webhook_name"` + WebhookUrl types.String `tfsdk:"webhook_url"` + WebhookSecret types.String `tfsdk:"webhook_secret"` +} + +func (MinistepsWebhooksModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "webhook_name": types.StringType, + "webhook_url": types.StringType, + "webhook_secret": types.StringType, + } +} + +type MinistepsWorkflowChainingModel struct { + WorkflowGroupId types.String `tfsdk:"workflow_group_id"` + StackId types.String `tfsdk:"stack_id"` + StackRunPayload types.String `tfsdk:"stack_run_payload"` + WorkflowId types.String `tfsdk:"workflow_id"` + WorkflowRunPayload types.String `tfsdk:"workflow_run_payload"` +} + +func (MinistepsWorkflowChainingModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "workflow_group_id": types.StringType, + "stack_id": types.StringType, + "stack_run_payload": types.StringType, + "workflow_id": types.StringType, + "workflow_run_payload": types.StringType, + } +} + +type MinistepsEmailModel struct { + ApprovalRequired types.List `tfsdk:"approval_required"` + Cancelled types.List `tfsdk:"cancelled"` + Completed types.List `tfsdk:"completed"` + DriftDetected types.List `tfsdk:"drift_detected"` + Errored types.List `tfsdk:"errored"` +} + +func (MinistepsEmailModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "approval_required": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsNotificationRecipientsModel{}.AttributeTypes()}}, + "cancelled": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsNotificationRecipientsModel{}.AttributeTypes()}}, + "completed": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsNotificationRecipientsModel{}.AttributeTypes()}}, + "drift_detected": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsNotificationRecipientsModel{}.AttributeTypes()}}, + "errored": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsNotificationRecipientsModel{}.AttributeTypes()}}, + } +} + +type MinistepsNotificationsModel struct { + Email types.Object `tfsdk:"email"` +} + +func (MinistepsNotificationsModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "email": types.ObjectType{AttrTypes: MinistepsEmailModel{}.AttributeTypes()}, + } +} + +type MinistepsWebhooksContainerModel struct { + ApprovalRequired types.List `tfsdk:"approval_required"` + Cancelled types.List `tfsdk:"cancelled"` + Completed types.List `tfsdk:"completed"` + DriftDetected types.List `tfsdk:"drift_detected"` + Errored types.List `tfsdk:"errored"` +} + +func (MinistepsWebhooksContainerModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "approval_required": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsWebhooksModel{}.AttributeTypes()}}, + "cancelled": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsWebhooksModel{}.AttributeTypes()}}, + "completed": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsWebhooksModel{}.AttributeTypes()}}, + "drift_detected": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsWebhooksModel{}.AttributeTypes()}}, + "errored": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsWebhooksModel{}.AttributeTypes()}}, + } +} + +type MinistepsWfChainingContainerModel struct { + Completed types.List `tfsdk:"completed"` + Errored types.List `tfsdk:"errored"` +} + +func (MinistepsWfChainingContainerModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "completed": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsWorkflowChainingModel{}.AttributeTypes()}}, + "errored": types.ListType{ElemType: types.ObjectType{AttrTypes: MinistepsWorkflowChainingModel{}.AttributeTypes()}}, + } +} + +type MinistepsModel struct { + Notifications types.Object `tfsdk:"notifications"` + Webhooks types.Object `tfsdk:"webhooks"` + WfChaining types.Object `tfsdk:"wf_chaining"` +} + +func (MinistepsModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "notifications": types.ObjectType{AttrTypes: MinistepsNotificationsModel{}.AttributeTypes()}, + "webhooks": types.ObjectType{AttrTypes: MinistepsWebhooksContainerModel{}.AttributeTypes()}, + "wf_chaining": types.ObjectType{AttrTypes: MinistepsWfChainingContainerModel{}.AttributeTypes()}, + } +} + +// --------------------------------------------------------------------------- +// WfStepsConfig +// --------------------------------------------------------------------------- + +type WfStepsConfigModel struct { + Name types.String `tfsdk:"name"` + EnvironmentVariables types.List `tfsdk:"environment_variables"` + Approval types.Bool `tfsdk:"approval"` + Timeout types.Int64 `tfsdk:"timeout"` + CmdOverride types.String `tfsdk:"cmd_override"` + MountPoints types.List `tfsdk:"mount_points"` + WfStepTemplateId types.String `tfsdk:"wf_step_template_id"` + WfStepInputData types.Object `tfsdk:"wf_step_input_data"` +} + +func (WfStepsConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "name": types.StringType, + "environment_variables": types.ListType{ElemType: types.ObjectType{AttrTypes: EnvVarModel{}.AttributeTypes()}}, + "approval": types.BoolType, + "timeout": types.Int64Type, + "cmd_override": types.StringType, + "mount_points": types.ListType{ElemType: types.ObjectType{AttrTypes: MountPointModel{}.AttributeTypes()}}, + "wf_step_template_id": types.StringType, + "wf_step_input_data": types.ObjectType{AttrTypes: WfStepInputDataModel{}.AttributeTypes()}, + } +} + +// --------------------------------------------------------------------------- +// Terraform config +// --------------------------------------------------------------------------- + +type TerraformConfigModel struct { + TerraformVersion types.String `tfsdk:"terraform_version"` + DriftCheck types.Bool `tfsdk:"drift_check"` + DriftCron types.String `tfsdk:"drift_cron"` + ManagedTerraformState types.Bool `tfsdk:"managed_terraform_state"` + ApprovalPreApply types.Bool `tfsdk:"approval_pre_apply"` + TerraformPlanOptions types.String `tfsdk:"terraform_plan_options"` + TerraformInitOptions types.String `tfsdk:"terraform_init_options"` + TerraformBinPath types.List `tfsdk:"terraform_bin_path"` + Timeout types.Int64 `tfsdk:"timeout"` + PostApplyWfStepsConfig types.List `tfsdk:"post_apply_wf_steps_config"` + PreApplyWfStepsConfig types.List `tfsdk:"pre_apply_wf_steps_config"` + PrePlanWfStepsConfig types.List `tfsdk:"pre_plan_wf_steps_config"` + PostPlanWfStepsConfig types.List `tfsdk:"post_plan_wf_steps_config"` + PreInitHooks types.List `tfsdk:"pre_init_hooks"` + PrePlanHooks types.List `tfsdk:"pre_plan_hooks"` + PostPlanHooks types.List `tfsdk:"post_plan_hooks"` + PreApplyHooks types.List `tfsdk:"pre_apply_hooks"` + PostApplyHooks types.List `tfsdk:"post_apply_hooks"` + RunPreInitHooksOnDrift types.Bool `tfsdk:"run_pre_init_hooks_on_drift"` +} + +func (TerraformConfigModel) AttributeTypes() map[string]attr.Type { + wfStepsListType := types.ListType{ElemType: types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}} + return map[string]attr.Type{ + "terraform_version": types.StringType, + "drift_check": types.BoolType, + "drift_cron": types.StringType, + "managed_terraform_state": types.BoolType, + "approval_pre_apply": types.BoolType, + "terraform_plan_options": types.StringType, + "terraform_init_options": types.StringType, + "terraform_bin_path": types.ListType{ElemType: types.ObjectType{AttrTypes: MountPointModel{}.AttributeTypes()}}, + "timeout": types.Int64Type, + "post_apply_wf_steps_config": wfStepsListType, + "pre_apply_wf_steps_config": wfStepsListType, + "pre_plan_wf_steps_config": wfStepsListType, + "post_plan_wf_steps_config": wfStepsListType, + "pre_init_hooks": types.ListType{ElemType: types.StringType}, + "pre_plan_hooks": types.ListType{ElemType: types.StringType}, + "post_plan_hooks": types.ListType{ElemType: types.StringType}, + "pre_apply_hooks": types.ListType{ElemType: types.StringType}, + "post_apply_hooks": types.ListType{ElemType: types.StringType}, + "run_pre_init_hooks_on_drift": types.BoolType, + } +} + +// --------------------------------------------------------------------------- +// Deployment platform config +// --------------------------------------------------------------------------- + +type DeploymentPlatformConfigModel struct { + Kind types.String `tfsdk:"kind"` + Config types.String `tfsdk:"config"` // JSON string (map[string]interface{}) +} + +func (DeploymentPlatformConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "kind": types.StringType, + "config": types.StringType, + } +} + +// --------------------------------------------------------------------------- +// Runner constraints +// --------------------------------------------------------------------------- + +type RunnerConstraintsModel struct { + Type types.String `tfsdk:"type"` + Names types.List `tfsdk:"names"` +} + +func (RunnerConstraintsModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "type": types.StringType, + "names": types.ListType{ElemType: types.StringType}, + } +} + +// --------------------------------------------------------------------------- +// User schedules +// --------------------------------------------------------------------------- + +type UserSchedulesModel struct { + Name types.String `tfsdk:"name"` + Desc types.String `tfsdk:"desc"` + Cron types.String `tfsdk:"cron"` + State types.String `tfsdk:"state"` +} + +func (UserSchedulesModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "name": types.StringType, + "desc": types.StringType, + "cron": types.StringType, + "state": types.StringType, + } +} + +// --------------------------------------------------------------------------- +// VCS config (for WorkflowsConfigWorkflow) +// --------------------------------------------------------------------------- + +type CustomSourceConfigModel struct { + IsPrivate types.Bool `tfsdk:"is_private"` + Auth types.String `tfsdk:"auth"` + WorkingDir types.String `tfsdk:"working_dir"` + GitSparseCheckoutConfig types.String `tfsdk:"git_sparse_checkout_config"` + GitCoreAutoCrlf types.Bool `tfsdk:"git_core_auto_crlf"` + Ref types.String `tfsdk:"ref"` + Repo types.String `tfsdk:"repo"` + IncludeSubModule types.Bool `tfsdk:"include_sub_module"` +} + +func (CustomSourceConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "is_private": types.BoolType, + "auth": types.StringType, + "working_dir": types.StringType, + "git_sparse_checkout_config": types.StringType, + "git_core_auto_crlf": types.BoolType, + "ref": types.StringType, + "repo": types.StringType, + "include_sub_module": types.BoolType, + } +} + +type CustomSourceModel struct { + SourceConfigDestKind types.String `tfsdk:"source_config_dest_kind"` + Config types.Object `tfsdk:"config"` +} + +func (CustomSourceModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "source_config_dest_kind": types.StringType, + "config": types.ObjectType{AttrTypes: CustomSourceConfigModel{}.AttributeTypes()}, + } +} + +type IacVcsConfigModel struct { + UseMarketplaceTemplate types.Bool `tfsdk:"use_marketplace_template"` + IacTemplateId types.String `tfsdk:"iac_template_id"` + CustomSource types.Object `tfsdk:"custom_source"` +} + +func (IacVcsConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "use_marketplace_template": types.BoolType, + "iac_template_id": types.StringType, + "custom_source": types.ObjectType{AttrTypes: CustomSourceModel{}.AttributeTypes()}, + } +} + +type IacInputDataModel struct { + SchemaId types.String `tfsdk:"schema_id"` + SchemaType types.String `tfsdk:"schema_type"` + Data types.String `tfsdk:"data"` // JSON string +} + +func (IacInputDataModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "schema_id": types.StringType, + "schema_type": types.StringType, + "data": types.StringType, + } +} + +type VcsConfigModel struct { + IacVcsConfig types.Object `tfsdk:"iac_vcs_config"` + IacInputData types.Object `tfsdk:"iac_input_data"` +} + +func (VcsConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "iac_vcs_config": types.ObjectType{AttrTypes: IacVcsConfigModel{}.AttributeTypes()}, + "iac_input_data": types.ObjectType{AttrTypes: IacInputDataModel{}.AttributeTypes()}, + } +} + +// --------------------------------------------------------------------------- +// WorkflowsConfig +// --------------------------------------------------------------------------- + +type WorkflowInStackModel struct { + Id types.String `tfsdk:"id"` + TemplateId types.String `tfsdk:"template_id"` + ResourceName types.String `tfsdk:"resource_name"` + WfStepsConfig types.List `tfsdk:"wf_steps_config"` + TerraformConfig types.Object `tfsdk:"terraform_config"` + EnvironmentVariables types.List `tfsdk:"environment_variables"` + DeploymentPlatformConfig types.List `tfsdk:"deployment_platform_config"` + VcsConfig types.Object `tfsdk:"vcs_config"` + IacInputData types.Object `tfsdk:"iac_input_data"` + UserSchedules types.List `tfsdk:"user_schedules"` + Approvers types.List `tfsdk:"approvers"` + NumberOfApprovalsRequired types.Int64 `tfsdk:"number_of_approvals_required"` + RunnerConstraints types.Object `tfsdk:"runner_constraints"` + UserJobCpu types.Int64 `tfsdk:"user_job_cpu"` + UserJobMemory types.Int64 `tfsdk:"user_job_memory"` + InputSchemas types.List `tfsdk:"input_schemas"` + MiniSteps types.Object `tfsdk:"mini_steps"` +} + +func (WorkflowInStackModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "id": types.StringType, + "template_id": types.StringType, + "resource_name": types.StringType, + "wf_steps_config": types.ListType{ElemType: types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}}, + "terraform_config": types.ObjectType{AttrTypes: TerraformConfigModel{}.AttributeTypes()}, + "environment_variables": types.ListType{ElemType: types.ObjectType{AttrTypes: EnvVarModel{}.AttributeTypes()}}, + "deployment_platform_config": types.ListType{ElemType: types.ObjectType{AttrTypes: DeploymentPlatformConfigModel{}.AttributeTypes()}}, + "vcs_config": types.ObjectType{AttrTypes: VcsConfigModel{}.AttributeTypes()}, + "iac_input_data": types.ObjectType{AttrTypes: WfStepInputDataModel{}.AttributeTypes()}, + "user_schedules": types.ListType{ElemType: types.ObjectType{AttrTypes: UserSchedulesModel{}.AttributeTypes()}}, + "approvers": types.ListType{ElemType: types.StringType}, + "number_of_approvals_required": types.Int64Type, + "runner_constraints": types.ObjectType{AttrTypes: RunnerConstraintsModel{}.AttributeTypes()}, + "user_job_cpu": types.Int64Type, + "user_job_memory": types.Int64Type, + "input_schemas": types.ListType{ElemType: types.ObjectType{AttrTypes: StackInputSchemaModel{}.AttributeTypes()}}, + "mini_steps": types.ObjectType{AttrTypes: MinistepsModel{}.AttributeTypes()}, + } +} + +type WorkflowsConfigModel struct { + Workflows types.List `tfsdk:"workflows"` +} + +func (WorkflowsConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "workflows": types.ListType{ElemType: types.ObjectType{AttrTypes: WorkflowInStackModel{}.AttributeTypes()}}, + } +} + +// --------------------------------------------------------------------------- +// Actions +// --------------------------------------------------------------------------- + +type TerraformActionModel struct { + Action types.String `tfsdk:"action"` +} + +func (TerraformActionModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "action": types.StringType, + } +} + +type StackActionParametersModel struct { + TerraformAction types.Object `tfsdk:"terraform_action"` + DeploymentPlatformConfig types.List `tfsdk:"deployment_platform_config"` + WfStepsConfig types.List `tfsdk:"wf_steps_config"` + EnvironmentVariables types.List `tfsdk:"environment_variables"` +} + +func (StackActionParametersModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "terraform_action": types.ObjectType{AttrTypes: TerraformActionModel{}.AttributeTypes()}, + "deployment_platform_config": types.ListType{ElemType: types.ObjectType{AttrTypes: DeploymentPlatformConfigModel{}.AttributeTypes()}}, + "wf_steps_config": types.ListType{ElemType: types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}}, + "environment_variables": types.ListType{ElemType: types.ObjectType{AttrTypes: EnvVarModel{}.AttributeTypes()}}, + } +} + +type ActionDependencyConditionModel struct { + LatestStatus types.String `tfsdk:"latest_status"` +} + +func (ActionDependencyConditionModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "latest_status": types.StringType, + } +} + +type ActionDependencyModel struct { + Id types.String `tfsdk:"id"` + Condition types.Object `tfsdk:"condition"` +} + +func (ActionDependencyModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "id": types.StringType, + "condition": types.ObjectType{AttrTypes: ActionDependencyConditionModel{}.AttributeTypes()}, + } +} + +type ActionOrderModel struct { + Parameters types.Object `tfsdk:"parameters"` + Dependencies types.List `tfsdk:"dependencies"` +} + +func (ActionOrderModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "parameters": types.ObjectType{AttrTypes: StackActionParametersModel{}.AttributeTypes()}, + "dependencies": types.ListType{ElemType: types.ObjectType{AttrTypes: ActionDependencyModel{}.AttributeTypes()}}, + } +} + +// ActionsModel represents a single action value in the actions map. +type ActionsModel struct { + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Default types.Bool `tfsdk:"default"` + Order types.Map `tfsdk:"order"` // map[string]ActionOrderModel +} + +func (ActionsModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "name": types.StringType, + "description": types.StringType, + "default": types.BoolType, + "order": types.MapType{ElemType: types.ObjectType{AttrTypes: ActionOrderModel{}.AttributeTypes()}}, + } +} + +// --------------------------------------------------------------------------- +// Helper: parse JSON string to map[string]interface{} +// --------------------------------------------------------------------------- + +func parseJSONToMap(s string) map[string]interface{} { + if s == "" { + return nil + } + var result map[string]interface{} + if err := json.Unmarshal([]byte(s), &result); err != nil { + return nil + } + return result +} + +func marshalToJSONString(v interface{}) types.String { + if v == nil { + return types.StringNull() + } + b, err := json.Marshal(v) + if err != nil { + return types.StringNull() + } + return flatteners.String(string(b)) +} + +// --------------------------------------------------------------------------- +// Converters: Terraform model → SDK API types +// --------------------------------------------------------------------------- + +func convertEnvVarsToAPI(ctx context.Context, list types.List) ([]sgsdkgo.EnvVars, diag.Diagnostics) { + if list.IsNull() || list.IsUnknown() { + return nil, nil + } + var models []EnvVarModel + if diags := list.ElementsAs(ctx, &models, false); diags.HasError() { + return nil, diags + } + result := make([]sgsdkgo.EnvVars, len(models)) + for i, m := range models { + var cfgModel EnvVarConfigModel + if diags := m.Config.As(ctx, &cfgModel, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + result[i] = sgsdkgo.EnvVars{ + Kind: sgsdkgo.EnvVarsKindEnum(m.Kind.ValueString()), + Config: &sgsdkgo.EnvVarConfig{ + VarName: cfgModel.VarName.ValueString(), + SecretId: cfgModel.SecretId.ValueStringPointer(), + TextValue: cfgModel.TextValue.ValueStringPointer(), + }, + } + } + return result, nil +} + +func convertEnvVarPointersToAPI(ctx context.Context, list types.List) ([]*sgsdkgo.EnvVars, diag.Diagnostics) { + vals, diags := convertEnvVarsToAPI(ctx, list) + if diags.HasError() { + return nil, diags + } + if vals == nil { + return nil, nil + } + result := make([]*sgsdkgo.EnvVars, len(vals)) + for i := range vals { + result[i] = &vals[i] + } + return result, nil +} + +func convertMountPointsToAPI(ctx context.Context, list types.List) ([]sgsdkgo.MountPoint, diag.Diagnostics) { + if list.IsNull() || list.IsUnknown() { + return nil, nil + } + var models []MountPointModel + if diags := list.ElementsAs(ctx, &models, false); diags.HasError() { + return nil, diags + } + result := make([]sgsdkgo.MountPoint, len(models)) + for i, m := range models { + result[i] = sgsdkgo.MountPoint{ + Source: m.Source.ValueString(), + Target: m.Target.ValueString(), + ReadOnly: m.ReadOnly.ValueBoolPointer(), + } + } + return result, nil +} + +func convertWfStepsConfigToAPI(ctx context.Context, list types.List) ([]sgsdkgo.WfStepsConfig, diag.Diagnostics) { + if list.IsNull() || list.IsUnknown() { + return nil, nil + } + var models []WfStepsConfigModel + if diags := list.ElementsAs(ctx, &models, false); diags.HasError() { + return nil, diags + } + result := make([]sgsdkgo.WfStepsConfig, len(models)) + for i, m := range models { + step := sgsdkgo.WfStepsConfig{ + Name: m.Name.ValueString(), + Approval: m.Approval.ValueBoolPointer(), + Timeout: expanders.IntPtr(m.Timeout.ValueInt64Pointer()), + WfStepTemplateId: m.WfStepTemplateId.ValueStringPointer(), + CmdOverride: m.CmdOverride.ValueStringPointer(), + } + if !m.EnvironmentVariables.IsNull() && !m.EnvironmentVariables.IsUnknown() { + envVars, diags := convertEnvVarsToAPI(ctx, m.EnvironmentVariables) + if diags.HasError() { + return nil, diags + } + step.EnvironmentVariables = envVars + } + if !m.MountPoints.IsNull() && !m.MountPoints.IsUnknown() { + mps, diags := convertMountPointsToAPI(ctx, m.MountPoints) + if diags.HasError() { + return nil, diags + } + step.MountPoints = mps + } + if !m.WfStepInputData.IsNull() && !m.WfStepInputData.IsUnknown() { + var idm WfStepInputDataModel + if diags := m.WfStepInputData.As(ctx, &idm, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + step.WfStepInputData = &sgsdkgo.WfStepInputData{ + SchemaType: sgsdkgo.WfStepInputDataSchemaTypeEnum(idm.SchemaType.ValueString()), + Data: parseJSONToMap(idm.Data.ValueString()), + } + } + result[i] = step + } + return result, nil +} + +func convertWfStepsConfigPointersToAPI(ctx context.Context, list types.List) ([]*sgsdkgo.WfStepsConfig, diag.Diagnostics) { + vals, diags := convertWfStepsConfigToAPI(ctx, list) + if diags.HasError() { + return nil, diags + } + if vals == nil { + return nil, nil + } + result := make([]*sgsdkgo.WfStepsConfig, len(vals)) + for i := range vals { + result[i] = &vals[i] + } + return result, nil +} + +func convertTerraformConfigToAPI(ctx context.Context, obj types.Object) (*sgsdkgo.TerraformConfig, diag.Diagnostics) { + if obj.IsNull() || obj.IsUnknown() { + return nil, nil + } + var m TerraformConfigModel + if diags := obj.As(ctx, &m, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + tc := &sgsdkgo.TerraformConfig{ + TerraformVersion: m.TerraformVersion.ValueStringPointer(), + DriftCheck: m.DriftCheck.ValueBoolPointer(), + DriftCron: m.DriftCron.ValueStringPointer(), + ManagedTerraformState: m.ManagedTerraformState.ValueBoolPointer(), + ApprovalPreApply: m.ApprovalPreApply.ValueBoolPointer(), + TerraformPlanOptions: m.TerraformPlanOptions.ValueStringPointer(), + TerraformInitOptions: m.TerraformInitOptions.ValueStringPointer(), + Timeout: expanders.IntPtr(m.Timeout.ValueInt64Pointer()), + RunPreInitHooksOnDrift: m.RunPreInitHooksOnDrift.ValueBoolPointer(), + } + if !m.TerraformBinPath.IsNull() && !m.TerraformBinPath.IsUnknown() { + mps, diags := convertMountPointsToAPI(ctx, m.TerraformBinPath) + if diags.HasError() { + return nil, diags + } + tc.TerraformBinPath = mps + } + for _, pair := range []struct { + list *types.List + dest *[]sgsdkgo.WfStepsConfig + }{ + {&m.PostApplyWfStepsConfig, &tc.PostApplyWfStepsConfig}, + {&m.PreApplyWfStepsConfig, &tc.PreApplyWfStepsConfig}, + {&m.PrePlanWfStepsConfig, &tc.PrePlanWfStepsConfig}, + {&m.PostPlanWfStepsConfig, &tc.PostPlanWfStepsConfig}, + } { + if !pair.list.IsNull() && !pair.list.IsUnknown() { + steps, diags := convertWfStepsConfigToAPI(ctx, *pair.list) + if diags.HasError() { + return nil, diags + } + *pair.dest = steps + } + } + for _, pair := range []struct { + list *types.List + dest *[]string + }{ + {&m.PreInitHooks, &tc.PreInitHooks}, + {&m.PrePlanHooks, &tc.PrePlanHooks}, + {&m.PostPlanHooks, &tc.PostPlanHooks}, + {&m.PreApplyHooks, &tc.PreApplyHooks}, + {&m.PostApplyHooks, &tc.PostApplyHooks}, + } { + if !pair.list.IsNull() && !pair.list.IsUnknown() { + hooks, diags := expanders.StringList(ctx, *pair.list) + if diags.HasError() { + return nil, diags + } + *pair.dest = hooks + } + } + return tc, nil +} + +func convertDeploymentPlatformConfigToAPI(ctx context.Context, list types.List) ([]*sgsdkgo.DeploymentPlatformConfig, diag.Diagnostics) { + if list.IsNull() || list.IsUnknown() { + return nil, nil + } + var models []DeploymentPlatformConfigModel + if diags := list.ElementsAs(ctx, &models, false); diags.HasError() { + return nil, diags + } + result := make([]*sgsdkgo.DeploymentPlatformConfig, len(models)) + for i, m := range models { + dpc := &sgsdkgo.DeploymentPlatformConfig{ + Kind: sgsdkgo.DeploymentPlatformConfigKindEnum(m.Kind.ValueString()), + Config: parseJSONToMap(m.Config.ValueString()), + } + result[i] = dpc + } + return result, nil +} + +func convertRunnerConstraintsToAPI(ctx context.Context, obj types.Object) (*sgsdkgo.RunnerConstraints, diag.Diagnostics) { + if obj.IsNull() || obj.IsUnknown() { + return nil, nil + } + var m RunnerConstraintsModel + if diags := obj.As(ctx, &m, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + names, diags := expanders.StringList(ctx, m.Names) + if diags.HasError() { + return nil, diags + } + return &sgsdkgo.RunnerConstraints{ + Type: sgsdkgo.RunnerConstraintsTypeEnum(m.Type.ValueString()), + Names: names, + }, nil +} + +func convertUserSchedulesToAPI(ctx context.Context, list types.List) ([]*sgsdkgo.UserSchedules, diag.Diagnostics) { + if list.IsNull() || list.IsUnknown() { + return nil, nil + } + var models []UserSchedulesModel + if diags := list.ElementsAs(ctx, &models, false); diags.HasError() { + return nil, diags + } + result := make([]*sgsdkgo.UserSchedules, len(models)) + for i, m := range models { + us := &sgsdkgo.UserSchedules{ + Name: m.Name.ValueStringPointer(), + Desc: m.Desc.ValueStringPointer(), + Cron: m.Cron.ValueString(), + State: sgsdkgo.StateEnum(m.State.ValueString()), + } + result[i] = us + } + return result, nil +} + +func convertVcsConfigToAPI(ctx context.Context, obj types.Object, orgName string) (*sgsdkgo.VcsConfig, diag.Diagnostics) { + if obj.IsNull() || obj.IsUnknown() { + return nil, nil + } + var m VcsConfigModel + if diags := obj.As(ctx, &m, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + vcsConfig := &sgsdkgo.VcsConfig{} + + if !m.IacVcsConfig.IsNull() && !m.IacVcsConfig.IsUnknown() { + var iacModel IacVcsConfigModel + if diags := m.IacVcsConfig.As(ctx, &iacModel, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + var prefixedIacTemplateId *string + if tid := iacModel.IacTemplateId.ValueStringPointer(); tid != nil { + v := fmt.Sprintf("/%s/%s", orgName, *tid) + prefixedIacTemplateId = &v + } + iacVcs := &sgsdkgo.IacvcsConfig{ + UseMarketplaceTemplate: iacModel.UseMarketplaceTemplate.ValueBool(), + IacTemplateId: prefixedIacTemplateId, + } + if !iacModel.CustomSource.IsNull() && !iacModel.CustomSource.IsUnknown() { + var csModel CustomSourceModel + if diags := iacModel.CustomSource.As(ctx, &csModel, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + cs := &sgsdkgo.CustomSource{ + SourceConfigDestKind: sgsdkgo.CustomSourceSourceConfigDestKindEnum(csModel.SourceConfigDestKind.ValueString()), + } + if !csModel.Config.IsNull() && !csModel.Config.IsUnknown() { + var csCfgModel CustomSourceConfigModel + if diags := csModel.Config.As(ctx, &csCfgModel, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + cs.Config = &sgsdkgo.CustomSourceConfig{ + IsPrivate: csCfgModel.IsPrivate.ValueBoolPointer(), + Auth: csCfgModel.Auth.ValueStringPointer(), + WorkingDir: csCfgModel.WorkingDir.ValueStringPointer(), + GitSparseCheckoutConfig: csCfgModel.GitSparseCheckoutConfig.ValueStringPointer(), + GitCoreAutoCrlf: csCfgModel.GitCoreAutoCrlf.ValueBoolPointer(), + Ref: csCfgModel.Ref.ValueStringPointer(), + Repo: csCfgModel.Repo.ValueStringPointer(), + IncludeSubModule: csCfgModel.IncludeSubModule.ValueBoolPointer(), + } + } + iacVcs.CustomSource = cs + } + vcsConfig.IacVcsConfig = iacVcs + } + + if !m.IacInputData.IsNull() && !m.IacInputData.IsUnknown() { + var idModel IacInputDataModel + if diags := m.IacInputData.As(ctx, &idModel, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + vcsConfig.IacInputData = &sgsdkgo.IacInputData{ + SchemaId: idModel.SchemaId.ValueStringPointer(), + SchemaType: sgsdkgo.IacInputDataSchemaTypeEnum(idModel.SchemaType.ValueString()), + Data: parseJSONToMap(idModel.Data.ValueString()), + } + } + + return vcsConfig, nil +} + +// --------------------------------------------------------------------------- +// MiniSteps converters (Terraform → SDK) +// --------------------------------------------------------------------------- + +func convertNotificationRecipientsToAPI(ctx context.Context, list types.List) ([]*sgsdkgo.Notifications, diag.Diagnostics) { + if list.IsNull() || list.IsUnknown() { + return nil, nil + } + var models []MinistepsNotificationRecipientsModel + if diags := list.ElementsAs(ctx, &models, false); diags.HasError() { + return nil, diags + } + result := make([]*sgsdkgo.Notifications, len(models)) + for i, m := range models { + recipients, diags := expanders.StringList(ctx, m.Recipients) + if diags.HasError() { + return nil, diags + } + result[i] = &sgsdkgo.Notifications{Recipients: recipients} + } + return result, nil +} + +func convertWebhooksToAPI(ctx context.Context, list types.List) ([]map[string]interface{}, diag.Diagnostics) { + if list.IsNull() || list.IsUnknown() { + return nil, nil + } + var models []MinistepsWebhooksModel + if diags := list.ElementsAs(ctx, &models, false); diags.HasError() { + return nil, diags + } + result := make([]map[string]interface{}, len(models)) + for i, m := range models { + wh := map[string]interface{}{ + "webhookName": m.WebhookName.ValueString(), + "webhookUrl": m.WebhookUrl.ValueString(), + } + if !m.WebhookSecret.IsNull() && !m.WebhookSecret.IsUnknown() { + wh["webhookSecret"] = m.WebhookSecret.ValueString() + } + result[i] = wh + } + return result, nil +} + +func convertWfChainingToAPI(ctx context.Context, list types.List) ([]*sgsdkgo.MiniSteps, diag.Diagnostics) { + if list.IsNull() || list.IsUnknown() { + return nil, nil + } + var models []MinistepsWorkflowChainingModel + if diags := list.ElementsAs(ctx, &models, false); diags.HasError() { + return nil, diags + } + result := make([]*sgsdkgo.MiniSteps, len(models)) + for i, m := range models { + ms := &sgsdkgo.MiniSteps{ + WorkflowGroupId: m.WorkflowGroupId.ValueString(), + WorkflowId: m.WorkflowId.ValueStringPointer(), + StackId: m.StackId.ValueStringPointer(), + } + if !m.WorkflowRunPayload.IsNull() && !m.WorkflowRunPayload.IsUnknown() { + ms.WorkflowRunPayload = parseJSONToMap(m.WorkflowRunPayload.ValueString()) + } + if !m.StackRunPayload.IsNull() && !m.StackRunPayload.IsUnknown() { + ms.StackRunPayload = parseJSONToMap(m.StackRunPayload.ValueString()) + } + result[i] = ms + } + return result, nil +} + +func convertMiniStepsToAPI(ctx context.Context, obj types.Object) (*sgsdkgo.MiniStepsSchema, diag.Diagnostics) { + if obj.IsNull() || obj.IsUnknown() { + return nil, nil + } + var m MinistepsModel + if diags := obj.As(ctx, &m, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + result := &sgsdkgo.MiniStepsSchema{} + + if !m.Notifications.IsNull() && !m.Notifications.IsUnknown() { + var notifModel MinistepsNotificationsModel + if diags := m.Notifications.As(ctx, ¬ifModel, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + notif := &sgsdkgo.NotificationTypes{} + if !notifModel.Email.IsNull() && !notifModel.Email.IsUnknown() { + var emailModel MinistepsEmailModel + if diags := notifModel.Email.As(ctx, &emailModel, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + email := &sgsdkgo.NotificationEmailType{} + for _, pair := range []struct { + list *types.List + dest *[]*sgsdkgo.Notifications + }{ + {&emailModel.ApprovalRequired, &email.ApprovalRequired}, + {&emailModel.Cancelled, &email.Cancelled}, + {&emailModel.Completed, &email.Completed}, + {&emailModel.DriftDetected, &email.DriftDetected}, + {&emailModel.Errored, &email.Errored}, + } { + if !pair.list.IsNull() && !pair.list.IsUnknown() { + vals, diags := convertNotificationRecipientsToAPI(ctx, *pair.list) + if diags.HasError() { + return nil, diags + } + *pair.dest = vals + } + } + notif.Email = email + } + result.Notifications = notif + } + + if !m.Webhooks.IsNull() && !m.Webhooks.IsUnknown() { + var whModel MinistepsWebhooksContainerModel + if diags := m.Webhooks.As(ctx, &whModel, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + wh := &sgsdkgo.WebhookTypes{} + for _, pair := range []struct { + list *types.List + dest *[]map[string]interface{} + }{ + {&whModel.ApprovalRequired, &wh.ApprovalRequired}, + {&whModel.Cancelled, &wh.Cancelled}, + {&whModel.Completed, &wh.Completed}, + {&whModel.DriftDetected, &wh.DriftDetected}, + {&whModel.Errored, &wh.Errored}, + } { + if !pair.list.IsNull() && !pair.list.IsUnknown() { + vals, diags := convertWebhooksToAPI(ctx, *pair.list) + if diags.HasError() { + return nil, diags + } + *pair.dest = vals + } + } + result.Webhooks = wh + } + + if !m.WfChaining.IsNull() && !m.WfChaining.IsUnknown() { + var wcModel MinistepsWfChainingContainerModel + if diags := m.WfChaining.As(ctx, &wcModel, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + wc := &sgsdkgo.WfChainingPayloadPayload{} + for _, pair := range []struct { + list *types.List + dest *[]*sgsdkgo.MiniSteps + }{ + {&wcModel.Completed, &wc.Completed}, + {&wcModel.Errored, &wc.Errored}, + } { + if !pair.list.IsNull() && !pair.list.IsUnknown() { + vals, diags := convertWfChainingToAPI(ctx, *pair.list) + if diags.HasError() { + return nil, diags + } + *pair.dest = vals + } + } + result.WfChaining = wc + } + + return result, nil +} + +func convertNotificationRecipientsFromAPI(ctx context.Context, recipients []*sgsdkgo.Notifications) (types.List, diag.Diagnostics) { + nullObj := types.ListNull(types.ObjectType{AttrTypes: MinistepsNotificationRecipientsModel{}.AttributeTypes()}) + if recipients == nil { + return nullObj, nil + } + elems := make([]MinistepsNotificationRecipientsModel, 0, len(recipients)) + for _, r := range recipients { + if r == nil { + continue + } + recList, diags := types.ListValueFrom(ctx, types.StringType, r.Recipients) + if diags.HasError() { + return nullObj, diags + } + elems = append(elems, MinistepsNotificationRecipientsModel{Recipients: recList}) + } + obj, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: MinistepsNotificationRecipientsModel{}.AttributeTypes()}, elems) + if diags.HasError() { + return nullObj, diags + } + return obj, nil +} + +func convertWebhooksFromAPI(ctx context.Context, webhooks []map[string]interface{}) (types.List, diag.Diagnostics) { + nullObj := types.ListNull(types.ObjectType{AttrTypes: MinistepsWebhooksModel{}.AttributeTypes()}) + if webhooks == nil { + return nullObj, nil + } + elems := make([]MinistepsWebhooksModel, 0, len(webhooks)) + for _, wh := range webhooks { + name, _ := wh["webhookName"].(string) + url, _ := wh["webhookUrl"].(string) + secret, _ := wh["webhookSecret"].(string) + m := MinistepsWebhooksModel{ + WebhookName: flatteners.String(name), + WebhookUrl: flatteners.String(url), + WebhookSecret: flatteners.String(secret), + } + elems = append(elems, m) + } + obj, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: MinistepsWebhooksModel{}.AttributeTypes()}, elems) + if diags.HasError() { + return nullObj, diags + } + return obj, nil +} + +func convertWfChainingFromAPI(ctx context.Context, items []*sgsdkgo.MiniSteps) (types.List, diag.Diagnostics) { + nullObj := types.ListNull(types.ObjectType{AttrTypes: MinistepsWorkflowChainingModel{}.AttributeTypes()}) + if items == nil { + return nullObj, nil + } + elems := make([]MinistepsWorkflowChainingModel, 0, len(items)) + for _, item := range items { + if item == nil { + continue + } + m := MinistepsWorkflowChainingModel{ + WorkflowGroupId: flatteners.String(item.WorkflowGroupId), + StackId: flatteners.StringPtr(item.StackId), + StackRunPayload: marshalToJSONString(item.StackRunPayload), + WorkflowId: flatteners.StringPtr(item.WorkflowId), + WorkflowRunPayload: marshalToJSONString(item.WorkflowRunPayload), + } + elems = append(elems, m) + } + obj, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: MinistepsWorkflowChainingModel{}.AttributeTypes()}, elems) + if diags.HasError() { + return nullObj, diags + } + return obj, nil +} + +func miniStepsFromAPI(ctx context.Context, miniSteps *sgsdkgo.MiniStepsSchema) (types.Object, diag.Diagnostics) { + nullObj := types.ObjectNull(MinistepsModel{}.AttributeTypes()) + if miniSteps == nil { + return nullObj, nil + } + + var diags diag.Diagnostics + msModel := MinistepsModel{} + + // Notifications + if miniSteps.Notifications != nil { + notifModel := MinistepsNotificationsModel{} + if miniSteps.Notifications.Email != nil { + emailModel := MinistepsEmailModel{} + emailModel.ApprovalRequired, diags = convertNotificationRecipientsFromAPI(ctx, miniSteps.Notifications.Email.ApprovalRequired) + if diags.HasError() { + return nullObj, diags + } + emailModel.Cancelled, diags = convertNotificationRecipientsFromAPI(ctx, miniSteps.Notifications.Email.Cancelled) + if diags.HasError() { + return nullObj, diags + } + emailModel.Completed, diags = convertNotificationRecipientsFromAPI(ctx, miniSteps.Notifications.Email.Completed) + if diags.HasError() { + return nullObj, diags + } + emailModel.DriftDetected, diags = convertNotificationRecipientsFromAPI(ctx, miniSteps.Notifications.Email.DriftDetected) + if diags.HasError() { + return nullObj, diags + } + emailModel.Errored, diags = convertNotificationRecipientsFromAPI(ctx, miniSteps.Notifications.Email.Errored) + if diags.HasError() { + return nullObj, diags + } + emailObj, d := types.ObjectValueFrom(ctx, MinistepsEmailModel{}.AttributeTypes(), emailModel) + if d.HasError() { + return nullObj, d + } + notifModel.Email = emailObj + } else { + notifModel.Email = types.ObjectNull(MinistepsEmailModel{}.AttributeTypes()) + } + notifObj, d := types.ObjectValueFrom(ctx, MinistepsNotificationsModel{}.AttributeTypes(), notifModel) + if d.HasError() { + return nullObj, d + } + msModel.Notifications = notifObj + } else { + msModel.Notifications = types.ObjectNull(MinistepsNotificationsModel{}.AttributeTypes()) + } + + // Webhooks + if miniSteps.Webhooks != nil { + whModel := MinistepsWebhooksContainerModel{} + whModel.ApprovalRequired, diags = convertWebhooksFromAPI(ctx, miniSteps.Webhooks.ApprovalRequired) + if diags.HasError() { + return nullObj, diags + } + whModel.Cancelled, diags = convertWebhooksFromAPI(ctx, miniSteps.Webhooks.Cancelled) + if diags.HasError() { + return nullObj, diags + } + whModel.Completed, diags = convertWebhooksFromAPI(ctx, miniSteps.Webhooks.Completed) + if diags.HasError() { + return nullObj, diags + } + whModel.DriftDetected, diags = convertWebhooksFromAPI(ctx, miniSteps.Webhooks.DriftDetected) + if diags.HasError() { + return nullObj, diags + } + whModel.Errored, diags = convertWebhooksFromAPI(ctx, miniSteps.Webhooks.Errored) + if diags.HasError() { + return nullObj, diags + } + whObj, d := types.ObjectValueFrom(ctx, MinistepsWebhooksContainerModel{}.AttributeTypes(), whModel) + if d.HasError() { + return nullObj, d + } + msModel.Webhooks = whObj + } else { + msModel.Webhooks = types.ObjectNull(MinistepsWebhooksContainerModel{}.AttributeTypes()) + } + + // WfChaining + if miniSteps.WfChaining != nil { + wcModel := MinistepsWfChainingContainerModel{} + wcModel.Completed, diags = convertWfChainingFromAPI(ctx, miniSteps.WfChaining.Completed) + if diags.HasError() { + return nullObj, diags + } + wcModel.Errored, diags = convertWfChainingFromAPI(ctx, miniSteps.WfChaining.Errored) + if diags.HasError() { + return nullObj, diags + } + wcObj, d := types.ObjectValueFrom(ctx, MinistepsWfChainingContainerModel{}.AttributeTypes(), wcModel) + if d.HasError() { + return nullObj, d + } + msModel.WfChaining = wcObj + } else { + msModel.WfChaining = types.ObjectNull(MinistepsWfChainingContainerModel{}.AttributeTypes()) + } + + return types.ObjectValueFrom(ctx, MinistepsModel{}.AttributeTypes(), msModel) +} + +func convertWorkflowsConfigToAPI(ctx context.Context, obj types.Object, orgName string) (*stacktemplaterevisions.StackTemplateRevisionWorkflowsConfig, diag.Diagnostics) { + if obj.IsNull() || obj.IsUnknown() { + return nil, nil + } + var m WorkflowsConfigModel + if diags := obj.As(ctx, &m, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + if m.Workflows.IsNull() || m.Workflows.IsUnknown() { + return &stacktemplaterevisions.StackTemplateRevisionWorkflowsConfig{}, nil + } + var wfModels []WorkflowInStackModel + if diags := m.Workflows.ElementsAs(ctx, &wfModels, false); diags.HasError() { + return nil, diags + } + + workflows := make([]*stacktemplaterevisions.StackTemplateRevisionWorkflow, len(wfModels)) + for i, wm := range wfModels { + // Prefix template_id with // + var prefixedTemplateId *string + if tid := wm.TemplateId.ValueStringPointer(); tid != nil { + v := fmt.Sprintf("/%s/%s", orgName, *tid) + prefixedTemplateId = &v + } + + wf := &stacktemplaterevisions.StackTemplateRevisionWorkflow{ + Id: wm.Id.ValueStringPointer(), + TemplateId: prefixedTemplateId, + ResourceName: wm.ResourceName.ValueStringPointer(), + NumberOfApprovalsRequired: expanders.IntPtr(wm.NumberOfApprovalsRequired.ValueInt64Pointer()), + UserJobCpu: expanders.IntPtr(wm.UserJobCpu.ValueInt64Pointer()), + UserJobMemory: expanders.IntPtr(wm.UserJobMemory.ValueInt64Pointer()), + } + if !wm.WfStepsConfig.IsNull() && !wm.WfStepsConfig.IsUnknown() { + steps, diags := convertWfStepsConfigPointersToAPI(ctx, wm.WfStepsConfig) + if diags.HasError() { + return nil, diags + } + wf.WfStepsConfig = steps + } + if !wm.TerraformConfig.IsNull() && !wm.TerraformConfig.IsUnknown() { + tc, diags := convertTerraformConfigToAPI(ctx, wm.TerraformConfig) + if diags.HasError() { + return nil, diags + } + wf.TerraformConfig = tc + } + if !wm.EnvironmentVariables.IsNull() && !wm.EnvironmentVariables.IsUnknown() { + envVars, diags := convertEnvVarPointersToAPI(ctx, wm.EnvironmentVariables) + if diags.HasError() { + return nil, diags + } + wf.EnvironmentVariables = envVars + } + if !wm.DeploymentPlatformConfig.IsNull() && !wm.DeploymentPlatformConfig.IsUnknown() { + dpcs, diags := convertDeploymentPlatformConfigToAPI(ctx, wm.DeploymentPlatformConfig) + if diags.HasError() { + return nil, diags + } + wf.DeploymentPlatformConfig = dpcs + } + if !wm.VcsConfig.IsNull() && !wm.VcsConfig.IsUnknown() { + vcs, diags := convertVcsConfigToAPI(ctx, wm.VcsConfig, orgName) + if diags.HasError() { + return nil, diags + } + wf.VcsConfig = vcs + } + if !wm.IacInputData.IsNull() && !wm.IacInputData.IsUnknown() { + var idm WfStepInputDataModel + if diags := wm.IacInputData.As(ctx, &idm, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + wf.IacInputData = &sgsdkgo.TemplatesIacInputData{ + SchemaType: idm.SchemaType.ValueString(), + Data: parseJSONToMap(idm.Data.ValueString()), + } + } + if !wm.UserSchedules.IsNull() && !wm.UserSchedules.IsUnknown() { + us, diags := convertUserSchedulesToAPI(ctx, wm.UserSchedules) + if diags.HasError() { + return nil, diags + } + wf.UserSchedules = us + } + if !wm.Approvers.IsNull() && !wm.Approvers.IsUnknown() { + approvers, diags := expanders.StringList(ctx, wm.Approvers) + if diags.HasError() { + return nil, diags + } + wf.Approvers = approvers + } + if !wm.RunnerConstraints.IsNull() && !wm.RunnerConstraints.IsUnknown() { + rc, diags := convertRunnerConstraintsToAPI(ctx, wm.RunnerConstraints) + if diags.HasError() { + return nil, diags + } + wf.RunnerConstraints = rc + } + if !wm.InputSchemas.IsNull() && !wm.InputSchemas.IsUnknown() { + var isModels []StackInputSchemaModel + if diags := wm.InputSchemas.ElementsAs(ctx, &isModels, false); diags.HasError() { + return nil, diags + } + schemas := make([]*sgsdkgo.InputSchemas, len(isModels)) + for j, ism := range isModels { + schemas[j] = &sgsdkgo.InputSchemas{ + Id: ism.Id.ValueStringPointer(), + Name: ism.Name.ValueStringPointer(), + Description: ism.Description.ValueStringPointer(), + Type: sgsdkgo.InputSchemasTypeEnum(ism.Type.ValueString()), + EncodedData: ism.EncodedData.ValueStringPointer(), + UiSchemaData: ism.UiSchemaData.ValueStringPointer(), + IsCommitted: ism.IsCommitted.ValueBoolPointer(), + } + } + wf.InputSchemas = schemas + } + if !wm.MiniSteps.IsNull() && !wm.MiniSteps.IsUnknown() { + ms, diags := convertMiniStepsToAPI(ctx, wm.MiniSteps) + if diags.HasError() { + return nil, diags + } + wf.MiniSteps = ms + } + workflows[i] = wf + } + return &stacktemplaterevisions.StackTemplateRevisionWorkflowsConfig{Workflows: workflows}, nil +} + +func convertActionsToAPI(ctx context.Context, actionsMap types.Map) (map[string]*sgsdkgo.Actions, diag.Diagnostics) { + if actionsMap.IsNull() || actionsMap.IsUnknown() { + return nil, nil + } + var models map[string]ActionsModel + if diags := actionsMap.ElementsAs(ctx, &models, false); diags.HasError() { + return nil, diags + } + result := make(map[string]*sgsdkgo.Actions, len(models)) + for k, am := range models { + action := &sgsdkgo.Actions{ + Name: am.Name.ValueString(), + Description: am.Description.ValueStringPointer(), + Default: am.Default.ValueBoolPointer(), + } + if !am.Order.IsNull() && !am.Order.IsUnknown() { + var orderModels map[string]ActionOrderModel + if diags := am.Order.ElementsAs(ctx, &orderModels, false); diags.HasError() { + return nil, diags + } + order := make(map[string]*sgsdkgo.ActionOrder, len(orderModels)) + for wfId, om := range orderModels { + ao := &sgsdkgo.ActionOrder{} + if !om.Parameters.IsNull() && !om.Parameters.IsUnknown() { + var pm StackActionParametersModel + if diags := om.Parameters.As(ctx, &pm, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + params := &sgsdkgo.StackActionParameters{} + if !pm.TerraformAction.IsNull() && !pm.TerraformAction.IsUnknown() { + var tam TerraformActionModel + if diags := pm.TerraformAction.As(ctx, &tam, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + actionEnum := sgsdkgo.ActionEnum(tam.Action.ValueString()) + params.TerraformAction = &sgsdkgo.TerraformAction{Action: &actionEnum} + } + if !pm.DeploymentPlatformConfig.IsNull() && !pm.DeploymentPlatformConfig.IsUnknown() { + dpcs, diags := convertDeploymentPlatformConfigToAPI(ctx, pm.DeploymentPlatformConfig) + if diags.HasError() { + return nil, diags + } + params.DeploymentPlatformConfig = dpcs + } + if !pm.WfStepsConfig.IsNull() && !pm.WfStepsConfig.IsUnknown() { + steps, diags := convertWfStepsConfigPointersToAPI(ctx, pm.WfStepsConfig) + if diags.HasError() { + return nil, diags + } + params.WfStepsConfig = steps + } + if !pm.EnvironmentVariables.IsNull() && !pm.EnvironmentVariables.IsUnknown() { + envVars, diags := convertEnvVarPointersToAPI(ctx, pm.EnvironmentVariables) + if diags.HasError() { + return nil, diags + } + params.EnvironmentVariables = envVars + } + ao.Parameters = params + } + if !om.Dependencies.IsNull() && !om.Dependencies.IsUnknown() { + var depModels []ActionDependencyModel + if diags := om.Dependencies.ElementsAs(ctx, &depModels, false); diags.HasError() { + return nil, diags + } + deps := make([]*sgsdkgo.ActionDependency, len(depModels)) + for j, dm := range depModels { + dep := &sgsdkgo.ActionDependency{Id: dm.Id.ValueString()} + if !dm.Condition.IsNull() && !dm.Condition.IsUnknown() { + var cond ActionDependencyConditionModel + if diags := dm.Condition.As(ctx, &cond, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}); diags.HasError() { + return nil, diags + } + dep.Condition = &sgsdkgo.ActionDependencyCondition{LatestStatus: cond.LatestStatus.ValueString()} + } + deps[j] = dep + } + ao.Dependencies = deps + } + order[wfId] = ao + } + action.Order = order + } + result[k] = action + } + return result, nil +} + +// --------------------------------------------------------------------------- +// Converters: SDK API types → Terraform model +// --------------------------------------------------------------------------- + +func envVarsFromAPI(envVars []sgsdkgo.EnvVars) (types.List, diag.Diagnostics) { + if envVars == nil { + return types.ListNull(types.ObjectType{AttrTypes: EnvVarModel{}.AttributeTypes()}), nil + } + elements := make([]attr.Value, len(envVars)) + for i, ev := range envVars { + cfgModel := EnvVarConfigModel{} + if ev.Config != nil { + cfgModel.VarName = flatteners.String(ev.Config.VarName) + cfgModel.SecretId = flatteners.StringPtr(ev.Config.SecretId) + cfgModel.TextValue = flatteners.StringPtr(ev.Config.TextValue) + } + cfgObj, diags := types.ObjectValueFrom(context.Background(), EnvVarConfigModel{}.AttributeTypes(), cfgModel) + if diags.HasError() { + return types.ListNull(types.ObjectType{AttrTypes: EnvVarModel{}.AttributeTypes()}), diags + } + evModel := EnvVarModel{ + Config: cfgObj, + Kind: flatteners.String(string(ev.Kind)), + } + obj, diags := types.ObjectValueFrom(context.Background(), EnvVarModel{}.AttributeTypes(), evModel) + if diags.HasError() { + return types.ListNull(types.ObjectType{AttrTypes: EnvVarModel{}.AttributeTypes()}), diags + } + elements[i] = obj + } + return types.ListValue(types.ObjectType{AttrTypes: EnvVarModel{}.AttributeTypes()}, elements) +} + +func envVarPointersFromAPI(envVars []*sgsdkgo.EnvVars) (types.List, diag.Diagnostics) { + vals := make([]sgsdkgo.EnvVars, 0, len(envVars)) + for _, p := range envVars { + if p != nil { + vals = append(vals, *p) + } + } + return envVarsFromAPI(vals) +} + +func mountPointsFromAPI(mps []sgsdkgo.MountPoint) (types.List, diag.Diagnostics) { + if mps == nil { + return types.ListNull(types.ObjectType{AttrTypes: MountPointModel{}.AttributeTypes()}), nil + } + elements := make([]attr.Value, len(mps)) + for i, mp := range mps { + m := MountPointModel{ + Source: flatteners.String(mp.Source), + Target: flatteners.String(mp.Target), + ReadOnly: flatteners.BoolPtr(mp.ReadOnly), + } + obj, diags := types.ObjectValueFrom(context.Background(), MountPointModel{}.AttributeTypes(), m) + if diags.HasError() { + return types.ListNull(types.ObjectType{AttrTypes: MountPointModel{}.AttributeTypes()}), diags + } + elements[i] = obj + } + return types.ListValue(types.ObjectType{AttrTypes: MountPointModel{}.AttributeTypes()}, elements) +} + +func wfStepsConfigFromAPI(steps []sgsdkgo.WfStepsConfig) (types.List, diag.Diagnostics) { + listNull := types.ListNull(types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}) + if steps == nil { + return listNull, nil + } + elements := make([]attr.Value, len(steps)) + for i, s := range steps { + envList, diags := envVarsFromAPI(s.EnvironmentVariables) + if diags.HasError() { + return listNull, diags + } + mpList, diags := mountPointsFromAPI(s.MountPoints) + if diags.HasError() { + return listNull, diags + } + inputDataObj := types.ObjectNull(WfStepInputDataModel{}.AttributeTypes()) + if s.WfStepInputData != nil { + dataStr := marshalToJSONString(s.WfStepInputData.Data) + idm := WfStepInputDataModel{ + SchemaType: flatteners.String(string(s.WfStepInputData.SchemaType)), + Data: dataStr, + } + var diags2 diag.Diagnostics + inputDataObj, diags2 = types.ObjectValueFrom(context.Background(), WfStepInputDataModel{}.AttributeTypes(), idm) + if diags2.HasError() { + return listNull, diags2 + } + } + m := WfStepsConfigModel{ + Name: flatteners.String(s.Name), + EnvironmentVariables: envList, + Approval: flatteners.BoolPtr(s.Approval), + Timeout: flatteners.Int64Ptr(s.Timeout), + CmdOverride: flatteners.StringPtr(s.CmdOverride), + MountPoints: mpList, + WfStepTemplateId: flatteners.StringPtr(s.WfStepTemplateId), + WfStepInputData: inputDataObj, + } + obj, diags2 := types.ObjectValueFrom(context.Background(), WfStepsConfigModel{}.AttributeTypes(), m) + if diags2.HasError() { + return listNull, diags2 + } + elements[i] = obj + } + return types.ListValue(types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}, elements) +} + +func wfStepsConfigPointersFromAPI(steps []*sgsdkgo.WfStepsConfig) (types.List, diag.Diagnostics) { + vals := make([]sgsdkgo.WfStepsConfig, 0, len(steps)) + for _, p := range steps { + if p != nil { + vals = append(vals, *p) + } + } + return wfStepsConfigFromAPI(vals) +} + +func terraformConfigFromAPI(tc *sgsdkgo.TerraformConfig) (types.Object, diag.Diagnostics) { + nullObj := types.ObjectNull(TerraformConfigModel{}.AttributeTypes()) + if tc == nil { + return nullObj, nil + } + strListNull := types.ListNull(types.StringType) + + binPath, diags := mountPointsFromAPI(tc.TerraformBinPath) + if diags.HasError() { + return nullObj, diags + } + + makeWfStepsList := func(steps []sgsdkgo.WfStepsConfig) (types.List, diag.Diagnostics) { + return wfStepsConfigFromAPI(steps) + } + + postApply, diags := makeWfStepsList(tc.PostApplyWfStepsConfig) + if diags.HasError() { + return nullObj, diags + } + preApply, diags := makeWfStepsList(tc.PreApplyWfStepsConfig) + if diags.HasError() { + return nullObj, diags + } + prePlan, diags := makeWfStepsList(tc.PrePlanWfStepsConfig) + if diags.HasError() { + return nullObj, diags + } + postPlan, diags := makeWfStepsList(tc.PostPlanWfStepsConfig) + if diags.HasError() { + return nullObj, diags + } + + makeStringList := func(hooks []string) (types.List, diag.Diagnostics) { + if hooks == nil { + return strListNull, nil + } + elems := make([]attr.Value, len(hooks)) + for i, h := range hooks { + elems[i] = flatteners.String(h) + } + return types.ListValue(types.StringType, elems) + } + + preInit, diags := makeStringList(tc.PreInitHooks) + if diags.HasError() { + return nullObj, diags + } + prePlan2, diags := makeStringList(tc.PrePlanHooks) + if diags.HasError() { + return nullObj, diags + } + postPlan2, diags := makeStringList(tc.PostPlanHooks) + if diags.HasError() { + return nullObj, diags + } + preApply2, diags := makeStringList(tc.PreApplyHooks) + if diags.HasError() { + return nullObj, diags + } + postApply2, diags := makeStringList(tc.PostApplyHooks) + if diags.HasError() { + return nullObj, diags + } + + m := TerraformConfigModel{ + TerraformVersion: flatteners.StringPtr(tc.TerraformVersion), + DriftCheck: flatteners.BoolPtr(tc.DriftCheck), + DriftCron: flatteners.StringPtr(tc.DriftCron), + ManagedTerraformState: flatteners.BoolPtr(tc.ManagedTerraformState), + ApprovalPreApply: flatteners.BoolPtr(tc.ApprovalPreApply), + TerraformPlanOptions: flatteners.StringPtr(tc.TerraformPlanOptions), + TerraformInitOptions: flatteners.StringPtr(tc.TerraformInitOptions), + TerraformBinPath: binPath, + Timeout: flatteners.Int64Ptr(tc.Timeout), + PostApplyWfStepsConfig: postApply, + PreApplyWfStepsConfig: preApply, + PrePlanWfStepsConfig: prePlan, + PostPlanWfStepsConfig: postPlan, + PreInitHooks: preInit, + PrePlanHooks: prePlan2, + PostPlanHooks: postPlan2, + PreApplyHooks: preApply2, + PostApplyHooks: postApply2, + RunPreInitHooksOnDrift: flatteners.BoolPtr(tc.RunPreInitHooksOnDrift), + } + return types.ObjectValueFrom(context.Background(), TerraformConfigModel{}.AttributeTypes(), m) +} + +func deploymentPlatformConfigFromAPI(dpcs []*sgsdkgo.DeploymentPlatformConfig) (types.List, diag.Diagnostics) { + listNull := types.ListNull(types.ObjectType{AttrTypes: DeploymentPlatformConfigModel{}.AttributeTypes()}) + if dpcs == nil { + return listNull, nil + } + elements := make([]attr.Value, 0, len(dpcs)) + for _, dpc := range dpcs { + if dpc == nil { + continue + } + m := DeploymentPlatformConfigModel{ + Kind: flatteners.String(string(dpc.Kind)), + Config: marshalToJSONString(dpc.Config), + } + obj, diags := types.ObjectValueFrom(context.Background(), DeploymentPlatformConfigModel{}.AttributeTypes(), m) + if diags.HasError() { + return listNull, diags + } + elements = append(elements, obj) + } + return types.ListValue(types.ObjectType{AttrTypes: DeploymentPlatformConfigModel{}.AttributeTypes()}, elements) +} + +func runnerConstraintsFromAPI(rc *sgsdkgo.RunnerConstraints) (types.Object, diag.Diagnostics) { + nullObj := types.ObjectNull(RunnerConstraintsModel{}.AttributeTypes()) + if rc == nil { + return nullObj, nil + } + namesList, diags := types.ListValueFrom(context.Background(), types.StringType, rc.Names) + if diags.HasError() { + return nullObj, diags + } + m := RunnerConstraintsModel{ + Type: flatteners.String(string(rc.Type)), + Names: namesList, + } + return types.ObjectValueFrom(context.Background(), RunnerConstraintsModel{}.AttributeTypes(), m) +} + +func userSchedulesFromAPI(uss []*sgsdkgo.UserSchedules) (types.List, diag.Diagnostics) { + listNull := types.ListNull(types.ObjectType{AttrTypes: UserSchedulesModel{}.AttributeTypes()}) + if uss == nil { + return listNull, nil + } + elements := make([]attr.Value, 0, len(uss)) + for _, us := range uss { + if us == nil { + continue + } + m := UserSchedulesModel{ + Name: flatteners.StringPtr(us.Name), + Desc: flatteners.StringPtr(us.Desc), + Cron: flatteners.String(us.Cron), + State: flatteners.String(string(us.State)), + } + obj, diags := types.ObjectValueFrom(context.Background(), UserSchedulesModel{}.AttributeTypes(), m) + if diags.HasError() { + return listNull, diags + } + elements = append(elements, obj) + } + return types.ListValue(types.ObjectType{AttrTypes: UserSchedulesModel{}.AttributeTypes()}, elements) +} + +func vcsConfigFromAPI(vc *sgsdkgo.VcsConfig) (types.Object, diag.Diagnostics) { + nullObj := types.ObjectNull(VcsConfigModel{}.AttributeTypes()) + if vc == nil { + return nullObj, nil + } + m := VcsConfigModel{} + + iacVcsNull := types.ObjectNull(IacVcsConfigModel{}.AttributeTypes()) + if vc.IacVcsConfig != nil { + var strippedIacTemplateId *string + if vc.IacVcsConfig.IacTemplateId != nil { + parts := strings.Split(*vc.IacVcsConfig.IacTemplateId, "/") + base := parts[len(parts)-1] + strippedIacTemplateId = &base + } + iacM := IacVcsConfigModel{ + UseMarketplaceTemplate: types.BoolValue(vc.IacVcsConfig.UseMarketplaceTemplate), + IacTemplateId: flatteners.StringPtr(strippedIacTemplateId), + CustomSource: types.ObjectNull(CustomSourceModel{}.AttributeTypes()), + } + if vc.IacVcsConfig.CustomSource != nil { + cs := vc.IacVcsConfig.CustomSource + csM := CustomSourceModel{ + SourceConfigDestKind: flatteners.String(string(cs.SourceConfigDestKind)), + Config: types.ObjectNull(CustomSourceConfigModel{}.AttributeTypes()), + } + if cs.Config != nil { + csCfgM := CustomSourceConfigModel{ + IsPrivate: flatteners.BoolPtr(cs.Config.IsPrivate), + Auth: flatteners.StringPtr(cs.Config.Auth), + WorkingDir: flatteners.StringPtr(cs.Config.WorkingDir), + GitSparseCheckoutConfig: flatteners.StringPtr(cs.Config.GitSparseCheckoutConfig), + GitCoreAutoCrlf: flatteners.BoolPtr(cs.Config.GitCoreAutoCrlf), + Ref: flatteners.StringPtr(cs.Config.Ref), + Repo: flatteners.StringPtr(cs.Config.Repo), + IncludeSubModule: flatteners.BoolPtr(cs.Config.IncludeSubModule), + } + cfgObj, diags := types.ObjectValueFrom(context.Background(), CustomSourceConfigModel{}.AttributeTypes(), csCfgM) + if diags.HasError() { + return nullObj, diags + } + csM.Config = cfgObj + } + csObj, diags := types.ObjectValueFrom(context.Background(), CustomSourceModel{}.AttributeTypes(), csM) + if diags.HasError() { + return nullObj, diags + } + iacM.CustomSource = csObj + } + var diags diag.Diagnostics + iacVcsNull, diags = types.ObjectValueFrom(context.Background(), IacVcsConfigModel{}.AttributeTypes(), iacM) + if diags.HasError() { + return nullObj, diags + } + } + m.IacVcsConfig = iacVcsNull + + iacInputNull := types.ObjectNull(IacInputDataModel{}.AttributeTypes()) + if vc.IacInputData != nil { + idM := IacInputDataModel{ + SchemaId: flatteners.StringPtr(vc.IacInputData.SchemaId), + SchemaType: flatteners.String(string(vc.IacInputData.SchemaType)), + Data: marshalToJSONString(vc.IacInputData.Data), + } + var diags diag.Diagnostics + iacInputNull, diags = types.ObjectValueFrom(context.Background(), IacInputDataModel{}.AttributeTypes(), idM) + if diags.HasError() { + return nullObj, diags + } + } + m.IacInputData = iacInputNull + + return types.ObjectValueFrom(context.Background(), VcsConfigModel{}.AttributeTypes(), m) +} + +func workflowsConfigFromAPI(wc *stacktemplaterevisions.StackTemplateRevisionWorkflowsConfig) (types.Object, diag.Diagnostics) { + nullObj := types.ObjectNull(WorkflowsConfigModel{}.AttributeTypes()) + if wc == nil { + return nullObj, nil + } + wfListNull := types.ListNull(types.ObjectType{AttrTypes: WorkflowInStackModel{}.AttributeTypes()}) + if wc.Workflows == nil { + m := WorkflowsConfigModel{Workflows: wfListNull} + return types.ObjectValueFrom(context.Background(), WorkflowsConfigModel{}.AttributeTypes(), m) + } + + elements := make([]attr.Value, 0, len(wc.Workflows)) + for _, wf := range wc.Workflows { + if wf == nil { + continue + } + wfSteps, diags := wfStepsConfigPointersFromAPI(wf.WfStepsConfig) + if diags.HasError() { + return nullObj, diags + } + tcObj, diags := terraformConfigFromAPI(wf.TerraformConfig) + if diags.HasError() { + return nullObj, diags + } + envVars, diags := envVarPointersFromAPI(wf.EnvironmentVariables) + if diags.HasError() { + return nullObj, diags + } + dpcs, diags := deploymentPlatformConfigFromAPI(wf.DeploymentPlatformConfig) + if diags.HasError() { + return nullObj, diags + } + vcs, diags := vcsConfigFromAPI(wf.VcsConfig) + if diags.HasError() { + return nullObj, diags + } + // IacInputData (TemplatesIacInputData) + iacInputDataObj := types.ObjectNull(WfStepInputDataModel{}.AttributeTypes()) + if wf.IacInputData != nil { + idm := WfStepInputDataModel{ + SchemaType: flatteners.String(wf.IacInputData.SchemaType), + Data: marshalToJSONString(wf.IacInputData.Data), + } + iacInputDataObj, diags = types.ObjectValueFrom(context.Background(), WfStepInputDataModel{}.AttributeTypes(), idm) + if diags.HasError() { + return nullObj, diags + } + } + us, diags := userSchedulesFromAPI(wf.UserSchedules) + if diags.HasError() { + return nullObj, diags + } + approvers, diags := types.ListValueFrom(context.Background(), types.StringType, wf.Approvers) + if diags.HasError() { + return nullObj, diags + } + rc, diags := runnerConstraintsFromAPI(wf.RunnerConstraints) + if diags.HasError() { + return nullObj, diags + } + // MiniSteps + msObj, diags := miniStepsFromAPI(context.Background(), wf.MiniSteps) + if diags.HasError() { + return nullObj, diags + } + + // InputSchemas + inputSchemasNull := types.ListNull(types.ObjectType{AttrTypes: StackInputSchemaModel{}.AttributeTypes()}) + if wf.InputSchemas != nil { + isElems := make([]attr.Value, 0, len(wf.InputSchemas)) + for _, is := range wf.InputSchemas { + if is == nil { + continue + } + ism := StackInputSchemaModel{ + Id: flatteners.StringPtr(is.Id), + Name: flatteners.StringPtr(is.Name), + Description: flatteners.StringPtr(is.Description), + Type: flatteners.String(string(is.Type)), + EncodedData: flatteners.StringPtr(is.EncodedData), + UiSchemaData: flatteners.StringPtr(is.UiSchemaData), + IsCommitted: flatteners.BoolPtr(is.IsCommitted), + } + isObj, diags2 := types.ObjectValueFrom(context.Background(), StackInputSchemaModel{}.AttributeTypes(), ism) + if diags2.HasError() { + return nullObj, diags2 + } + isElems = append(isElems, isObj) + } + inputSchemasNull, diags = types.ListValue(types.ObjectType{AttrTypes: StackInputSchemaModel{}.AttributeTypes()}, isElems) + if diags.HasError() { + return nullObj, diags + } + } + + // Strip // prefix from template_id returned by the API + var strippedTemplateId *string + if wf.TemplateId != nil { + parts := strings.Split(*wf.TemplateId, "/") + base := parts[len(parts)-1] + strippedTemplateId = &base + } + + wm := WorkflowInStackModel{ + Id: flatteners.StringPtr(wf.Id), + TemplateId: flatteners.StringPtr(strippedTemplateId), + ResourceName: flatteners.StringPtr(wf.ResourceName), + WfStepsConfig: wfSteps, + TerraformConfig: tcObj, + EnvironmentVariables: envVars, + DeploymentPlatformConfig: dpcs, + VcsConfig: vcs, + IacInputData: iacInputDataObj, + UserSchedules: us, + Approvers: approvers, + NumberOfApprovalsRequired: flatteners.Int64Ptr(wf.NumberOfApprovalsRequired), + RunnerConstraints: rc, + UserJobCpu: flatteners.Int64Ptr(wf.UserJobCpu), + UserJobMemory: flatteners.Int64Ptr(wf.UserJobMemory), + InputSchemas: inputSchemasNull, + MiniSteps: msObj, + } + obj, diags := types.ObjectValueFrom(context.Background(), WorkflowInStackModel{}.AttributeTypes(), wm) + if diags.HasError() { + return nullObj, diags + } + elements = append(elements, obj) + } + wfList, diags := types.ListValue(types.ObjectType{AttrTypes: WorkflowInStackModel{}.AttributeTypes()}, elements) + if diags.HasError() { + return nullObj, diags + } + m := WorkflowsConfigModel{Workflows: wfList} + return types.ObjectValueFrom(context.Background(), WorkflowsConfigModel{}.AttributeTypes(), m) +} + +func actionsFromAPI(actions map[string]*sgsdkgo.Actions) (types.Map, diag.Diagnostics) { + mapNull := types.MapNull(types.ObjectType{AttrTypes: ActionsModel{}.AttributeTypes()}) + if actions == nil { + return mapNull, nil + } + elements := make(map[string]attr.Value, len(actions)) + for k, a := range actions { + if a == nil { + continue + } + orderNull := types.MapNull(types.ObjectType{AttrTypes: ActionOrderModel{}.AttributeTypes()}) + if a.Order != nil { + orderElements := make(map[string]attr.Value, len(a.Order)) + for wfId, ao := range a.Order { + if ao == nil { + continue + } + // Parameters + paramsNull := types.ObjectNull(StackActionParametersModel{}.AttributeTypes()) + if ao.Parameters != nil { + p := ao.Parameters + // TerraformAction + taNull := types.ObjectNull(TerraformActionModel{}.AttributeTypes()) + if p.TerraformAction != nil && p.TerraformAction.Action != nil { + tam := TerraformActionModel{Action: flatteners.String(string(*p.TerraformAction.Action))} + var diags2 diag.Diagnostics + taNull, diags2 = types.ObjectValueFrom(context.Background(), TerraformActionModel{}.AttributeTypes(), tam) + if diags2.HasError() { + return mapNull, diags2 + } + } + // DPC + dpcList, diags2 := deploymentPlatformConfigFromAPI(p.DeploymentPlatformConfig) + if diags2.HasError() { + return mapNull, diags2 + } + // WfStepsConfig + wfStepsList, diags2 := wfStepsConfigPointersFromAPI(p.WfStepsConfig) + if diags2.HasError() { + return mapNull, diags2 + } + // EnvVars + envList, diags2 := envVarPointersFromAPI(p.EnvironmentVariables) + if diags2.HasError() { + return mapNull, diags2 + } + pm := StackActionParametersModel{ + TerraformAction: taNull, + DeploymentPlatformConfig: dpcList, + WfStepsConfig: wfStepsList, + EnvironmentVariables: envList, + } + var diags3 diag.Diagnostics + paramsNull, diags3 = types.ObjectValueFrom(context.Background(), StackActionParametersModel{}.AttributeTypes(), pm) + if diags3.HasError() { + return mapNull, diags3 + } + } + + // Dependencies + depListNull := types.ListNull(types.ObjectType{AttrTypes: ActionDependencyModel{}.AttributeTypes()}) + if ao.Dependencies != nil { + depElems := make([]attr.Value, 0, len(ao.Dependencies)) + for _, dep := range ao.Dependencies { + if dep == nil { + continue + } + condNull := types.ObjectNull(ActionDependencyConditionModel{}.AttributeTypes()) + if dep.Condition != nil { + condM := ActionDependencyConditionModel{LatestStatus: flatteners.String(dep.Condition.LatestStatus)} + var diags2 diag.Diagnostics + condNull, diags2 = types.ObjectValueFrom(context.Background(), ActionDependencyConditionModel{}.AttributeTypes(), condM) + if diags2.HasError() { + return mapNull, diags2 + } + } + dm := ActionDependencyModel{Id: flatteners.String(dep.Id), Condition: condNull} + depObj, diags2 := types.ObjectValueFrom(context.Background(), ActionDependencyModel{}.AttributeTypes(), dm) + if diags2.HasError() { + return mapNull, diags2 + } + depElems = append(depElems, depObj) + } + var diags2 diag.Diagnostics + depListNull, diags2 = types.ListValue(types.ObjectType{AttrTypes: ActionDependencyModel{}.AttributeTypes()}, depElems) + if diags2.HasError() { + return mapNull, diags2 + } + } + aoM := ActionOrderModel{Parameters: paramsNull, Dependencies: depListNull} + aoObj, diags2 := types.ObjectValueFrom(context.Background(), ActionOrderModel{}.AttributeTypes(), aoM) + if diags2.HasError() { + return mapNull, diags2 + } + orderElements[wfId] = aoObj + } + var diags2 diag.Diagnostics + orderNull, diags2 = types.MapValue(types.ObjectType{AttrTypes: ActionOrderModel{}.AttributeTypes()}, orderElements) + if diags2.HasError() { + return mapNull, diags2 + } + } + am := ActionsModel{ + Name: flatteners.String(a.Name), + Description: flatteners.StringPtr(a.Description), + Default: flatteners.BoolPtr(a.Default), + Order: orderNull, + } + obj, diags2 := types.ObjectValueFrom(context.Background(), ActionsModel{}.AttributeTypes(), am) + if diags2.HasError() { + return mapNull, diags2 + } + elements[k] = obj + } + return types.MapValue(types.ObjectType{AttrTypes: ActionsModel{}.AttributeTypes()}, elements) +} + +// --------------------------------------------------------------------------- +// ToAPIModel / ToUpdateAPIModel +// --------------------------------------------------------------------------- + +func (m *StackTemplateRevisionResourceModel) ToAPIModel(ctx context.Context, orgName string) (*stacktemplaterevisions.CreateStackTemplateRevisionRequest, diag.Diagnostics) { + diags := diag.Diagnostics{} + apiModel := &stacktemplaterevisions.CreateStackTemplateRevisionRequest{} + + if !m.Alias.IsNull() && !m.Alias.IsUnknown() { + apiModel.Alias = m.Alias.ValueString() + } + if !m.Notes.IsNull() && !m.Notes.IsUnknown() { + apiModel.Notes = m.Notes.ValueString() + } + if !m.LongDescription.IsNull() && !m.LongDescription.IsUnknown() { + apiModel.LongDescription = m.LongDescription.ValueStringPointer() + } + if !m.SourceConfigKind.IsNull() && !m.SourceConfigKind.IsUnknown() { + apiModel.SourceConfigKind = (*stacktemplates.StackTemplateSourceConfigKindEnum)(m.SourceConfigKind.ValueStringPointer()) + } + if !m.IsActive.IsNull() && !m.IsActive.IsUnknown() { + apiModel.IsActive = (*sgsdkgo.IsPublicEnum)(m.IsActive.ValueStringPointer()) + } + if !m.IsPublic.IsNull() && !m.IsPublic.IsUnknown() { + apiModel.IsPublic = (*sgsdkgo.IsPublicEnum)(m.IsPublic.ValueStringPointer()) + } + + tags, diagsTags := expanders.StringList(ctx, m.Tags) + diags.Append(diagsTags...) + if !diags.HasError() { + apiModel.Tags = tags + } + + if !m.ContextTags.IsNull() && !m.ContextTags.IsUnknown() { + contextTags := make(map[string]string) + diags.Append(m.ContextTags.ElementsAs(ctx, &contextTags, false)...) + if !diags.HasError() { + apiModel.ContextTags = contextTags + } + } + + if !m.Deprecation.IsNull() && !m.Deprecation.IsUnknown() { + var dep DeprecationModel + diags.Append(m.Deprecation.As(ctx, &dep, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true})...) + if !diags.HasError() { + apiModel.Deprecation = &stacktemplaterevisions.Deprecation{ + EffectiveDate: dep.EffectiveDate.ValueStringPointer(), + Message: dep.Message.ValueStringPointer(), + } + } + } + + if !m.WorkflowsConfig.IsNull() && !m.WorkflowsConfig.IsUnknown() { + wc, diagsWC := convertWorkflowsConfigToAPI(ctx, m.WorkflowsConfig, orgName) + diags.Append(diagsWC...) + if !diags.HasError() { + apiModel.WorkflowsConfig = wc + } + } + + if !m.Actions.IsNull() && !m.Actions.IsUnknown() { + acts, diagsActs := convertActionsToAPI(ctx, m.Actions) + diags.Append(diagsActs...) + if !diags.HasError() { + apiModel.Actions = acts + } + } + + return apiModel, diags +} + +func (m *StackTemplateRevisionResourceModel) ToUpdateAPIModel(ctx context.Context, orgName string) (*stacktemplaterevisions.UpdateStackTemplateRevisionRequest, diag.Diagnostics) { + diags := diag.Diagnostics{} + apiModel := &stacktemplaterevisions.UpdateStackTemplateRevisionRequest{} + + if !m.Alias.IsNull() && !m.Alias.IsUnknown() { + apiModel.Alias = sgsdkgo.Optional(m.Alias.ValueString()) + } else { + apiModel.Alias = sgsdkgo.Null[string]() + } + if !m.Notes.IsNull() && !m.Notes.IsUnknown() { + apiModel.Notes = sgsdkgo.Optional(m.Notes.ValueString()) + } else { + apiModel.Notes = sgsdkgo.Null[string]() + } + if !m.LongDescription.IsNull() && !m.LongDescription.IsUnknown() { + apiModel.LongDescription = sgsdkgo.Optional(m.LongDescription.ValueString()) + } else { + apiModel.LongDescription = sgsdkgo.Null[string]() + } + if !m.SourceConfigKind.IsNull() && !m.SourceConfigKind.IsUnknown() { + apiModel.SourceConfigKind = sgsdkgo.Optional(stacktemplates.StackTemplateSourceConfigKindEnum(m.SourceConfigKind.ValueString())) + } else { + apiModel.SourceConfigKind = sgsdkgo.Null[stacktemplates.StackTemplateSourceConfigKindEnum]() + } + if !m.IsActive.IsNull() && !m.IsActive.IsUnknown() { + apiModel.IsActive = sgsdkgo.Optional(sgsdkgo.IsPublicEnum(m.IsActive.ValueString())) + } + if !m.IsPublic.IsNull() && !m.IsPublic.IsUnknown() { + apiModel.IsPublic = sgsdkgo.Optional(sgsdkgo.IsPublicEnum(m.IsPublic.ValueString())) + } + + tags, diagsTags := expanders.StringList(ctx, m.Tags) + diags.Append(diagsTags...) + if tags != nil { + apiModel.Tags = sgsdkgo.Optional(tags) + } else { + apiModel.Tags = sgsdkgo.Null[[]string]() + } + + contextTags, diagsCT := expanders.MapStringString(ctx, m.ContextTags) + diags.Append(diagsCT...) + if contextTags != nil { + apiModel.ContextTags = sgsdkgo.Optional(contextTags) + } else { + apiModel.ContextTags = sgsdkgo.Null[map[string]string]() + } + + if !m.Deprecation.IsNull() && !m.Deprecation.IsUnknown() { + var dep DeprecationModel + diags.Append(m.Deprecation.As(ctx, &dep, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true})...) + if !diags.HasError() { + apiModel.Deprecation = sgsdkgo.Optional(stacktemplaterevisions.Deprecation{ + EffectiveDate: dep.EffectiveDate.ValueStringPointer(), + Message: dep.Message.ValueStringPointer(), + }) + } + } else { + apiModel.Deprecation = sgsdkgo.Null[stacktemplaterevisions.Deprecation]() + } + + if !m.WorkflowsConfig.IsNull() && !m.WorkflowsConfig.IsUnknown() { + wc, diagsWC := convertWorkflowsConfigToAPI(ctx, m.WorkflowsConfig, orgName) + diags.Append(diagsWC...) + if !diags.HasError() && wc != nil { + apiModel.WorkflowsConfig = sgsdkgo.Optional(*wc) + } + } else { + apiModel.WorkflowsConfig = sgsdkgo.Null[stacktemplaterevisions.StackTemplateRevisionWorkflowsConfig]() + } + + if !m.Actions.IsNull() && !m.Actions.IsUnknown() { + acts, diagsActs := convertActionsToAPI(ctx, m.Actions) + diags.Append(diagsActs...) + if !diags.HasError() && acts != nil { + apiModel.Actions = sgsdkgo.Optional(acts) + } + } else { + apiModel.Actions = sgsdkgo.Null[map[string]*sgsdkgo.Actions]() + } + + return apiModel, diags +} + +// --------------------------------------------------------------------------- +// BuildAPIModelToStackTemplateRevisionModel +// --------------------------------------------------------------------------- + +func BuildAPIModelToStackTemplateRevisionModel(ctx context.Context, apiResponse *stacktemplaterevisions.ReadStackTemplateRevisionModel) (*StackTemplateRevisionResourceModel, diag.Diagnostics) { + diags := diag.Diagnostics{} + + model := &StackTemplateRevisionResourceModel{ + Id: flatteners.StringPtr(apiResponse.Id), + TemplateId: flatteners.String(apiResponse.TemplateId), + Alias: flatteners.String(apiResponse.Alias), + Notes: flatteners.String(apiResponse.Notes), + LongDescription: flatteners.StringPtr(apiResponse.LongDescription), + IsPublic: flatteners.StringPtr((*string)(apiResponse.IsPublic)), + } + + if apiResponse.SourceConfigKind != nil { + model.SourceConfigKind = flatteners.String(string(*apiResponse.SourceConfigKind)) + } else { + model.SourceConfigKind = types.StringNull() + } + if apiResponse.IsActive != nil { + model.IsActive = flatteners.String(string(*apiResponse.IsActive)) + } else { + model.IsActive = types.StringNull() + } + + // Tags + if apiResponse.Tags != nil { + tagsList, diagsTags := types.ListValueFrom(context.Background(), types.StringType, apiResponse.Tags) + diags.Append(diagsTags...) + model.Tags = tagsList + } else { + model.Tags = types.ListNull(types.StringType) + } + + // ContextTags + if apiResponse.ContextTags != nil { + contextTags := make(map[string]types.String) + for k, v := range apiResponse.ContextTags { + contextTags[k] = flatteners.String(v) + } + contextTagsMap, diagsCT := types.MapValueFrom(context.Background(), types.StringType, contextTags) + diags.Append(diagsCT...) + model.ContextTags = contextTagsMap + } else { + model.ContextTags = types.MapNull(types.StringType) + } + + // Deprecation + if apiResponse.Deprecation != nil { + depM := DeprecationModel{ + EffectiveDate: flatteners.StringPtr(apiResponse.Deprecation.EffectiveDate), + Message: flatteners.StringPtr(apiResponse.Deprecation.Message), + } + depObj, diagsDep := types.ObjectValueFrom(context.Background(), DeprecationModel{}.AttributeTypes(), depM) + diags.Append(diagsDep...) + model.Deprecation = depObj + } else { + model.Deprecation = types.ObjectNull(DeprecationModel{}.AttributeTypes()) + } + + // WorkflowsConfig + wcObj, diagsWC := workflowsConfigFromAPI(apiResponse.WorkflowsConfig) + diags.Append(diagsWC...) + model.WorkflowsConfig = wcObj + + // Actions + actsMap, diagsActs := actionsFromAPI(apiResponse.Actions) + diags.Append(diagsActs...) + model.Actions = actsMap + + return model, diags +} diff --git a/internal/resource/stack_template_revision/resource.go b/internal/resource/stack_template_revision/resource.go new file mode 100644 index 0000000..3420cfa --- /dev/null +++ b/internal/resource/stack_template_revision/resource.go @@ -0,0 +1,222 @@ +package stacktemplaterevision + +import ( + "context" + "fmt" + + sgsdkgo "github.com/StackGuardian/sg-sdk-go" + sgclient "github.com/StackGuardian/sg-sdk-go/client" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/customTypes" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +var ( + _ resource.Resource = &stackTemplateRevisionResource{} + _ resource.ResourceWithConfigure = &stackTemplateRevisionResource{} + _ resource.ResourceWithImportState = &stackTemplateRevisionResource{} +) + +type stackTemplateRevisionResource struct { + client *sgclient.Client + org_name string +} + +// NewResource is a helper function to simplify the provider implementation. +func NewResource() resource.Resource { + return &stackTemplateRevisionResource{} +} + +// Metadata returns the resource type name. +func (r *stackTemplateRevisionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_stack_template_revision" +} + +// Configure adds the provider configured client to the resource. +func (r *stackTemplateRevisionResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + provider, ok := req.ProviderData.(*customTypes.ProviderInfo) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *customTypes.ProviderInfo, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = provider.Client + r.org_name = provider.Org_name +} + +// ImportState imports a stack template revision using its ID. +func (r *stackTemplateRevisionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...) +} + +// Create creates the resource and sets the initial Terraform state. +func (r *stackTemplateRevisionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan StackTemplateRevisionResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ownerOrg := fmt.Sprintf("/orgs/%s", r.org_name) + + payload, diags := plan.ToAPIModel(ctx, r.org_name) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + templateID := plan.ParentTemplateId.ValueString() + payload.OwnerOrg = ownerOrg + + createResp, err := r.client.StackTemplateRevisions.CreateStackTemplateRevision(ctx, r.org_name, templateID, payload) + if err != nil { + resp.Diagnostics.AddError("Error creating stack template revision", "Error in creating stack template revision API call: "+err.Error()) + return + } + + revisionID := createResp.Data.Revision.Id + + // Call read to get the full state since create response doesn't return all values + readResp, err := r.client.StackTemplateRevisions.ReadStackTemplateRevision(ctx, r.org_name, revisionID) + if err != nil { + resp.Diagnostics.AddError("Error reading created stack template revision", "Could not read the created stack template revision: "+err.Error()) + return + } + + revisionModel, diags := BuildAPIModelToStackTemplateRevisionModel(ctx, &readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Preserve the user-provided parent_template_id from the plan + revisionModel.ParentTemplateId = plan.ParentTemplateId + + resp.Diagnostics.Append(resp.State.Set(ctx, &revisionModel)...) +} + +// Read refreshes the Terraform state with the latest data. +func (r *stackTemplateRevisionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state StackTemplateRevisionResourceModel + + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + revisionID := state.Id.ValueString() + if revisionID == "" { + resp.Diagnostics.AddError("Error reading stack template revision", "Revision ID is empty") + return + } + + readResp, err := r.client.StackTemplateRevisions.ReadStackTemplateRevision(ctx, r.org_name, revisionID) + if err != nil { + resp.Diagnostics.AddError("Error reading stack template revision", "Error in reading stack template revision API call: "+err.Error()) + return + } + + if readResp == nil { + resp.Diagnostics.AddError("Error reading stack template revision", "API response is empty") + return + } + + revisionModel, diags := BuildAPIModelToStackTemplateRevisionModel(ctx, &readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Preserve parent_template_id from prior state + revisionModel.ParentTemplateId = state.ParentTemplateId + + resp.Diagnostics.Append(resp.State.Set(ctx, &revisionModel)...) +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *stackTemplateRevisionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan StackTemplateRevisionResourceModel + var state StackTemplateRevisionResourceModel + + diags := req.Plan.Get(ctx, &plan) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + diags = req.State.Get(ctx, &state) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + revisionID := state.Id.ValueString() + ownerOrg := fmt.Sprintf("/orgs/%s", r.org_name) + + payload, diags := plan.ToUpdateAPIModel(ctx, r.org_name) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + payload.OwnerOrg = sgsdkgo.Optional(ownerOrg) + + _, err := r.client.StackTemplateRevisions.UpdateStackTemplateRevision(ctx, r.org_name, revisionID, payload) + if err != nil { + resp.Diagnostics.AddError("Error updating stack template revision", "Error in updating stack template revision API call: "+err.Error()) + return + } + + // Call read to get the updated state since update response doesn't return all values + readResp, err := r.client.StackTemplateRevisions.ReadStackTemplateRevision(ctx, r.org_name, revisionID) + if err != nil { + resp.Diagnostics.AddError("Error reading updated stack template revision", "Could not read the updated stack template revision: "+err.Error()) + return + } + + revisionModel, diags := BuildAPIModelToStackTemplateRevisionModel(ctx, &readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Preserve parent_template_id from plan + revisionModel.ParentTemplateId = plan.ParentTemplateId + + resp.Diagnostics.Append(resp.State.Set(ctx, &revisionModel)...) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *stackTemplateRevisionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state StackTemplateRevisionResourceModel + + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + revisionID := state.Id.ValueString() + if revisionID == "" { + resp.Diagnostics.AddError("Error deleting stack template revision", "Revision ID is empty") + return + } + + err := r.client.StackTemplateRevisions.DeleteStackTemplateRevision(ctx, r.org_name, revisionID, true) + if err != nil { + resp.Diagnostics.AddError("Error deleting stack template revision", "Error in deleting stack template revision API call: "+err.Error()) + return + } +} diff --git a/internal/resource/stack_template_revision/resource_test.go b/internal/resource/stack_template_revision/resource_test.go new file mode 100644 index 0000000..706a377 --- /dev/null +++ b/internal/resource/stack_template_revision/resource_test.go @@ -0,0 +1,278 @@ +package stacktemplaterevision_test + +import ( + "context" + "fmt" + "net/http" + "os" + "testing" + + sgsdkgo "github.com/StackGuardian/sg-sdk-go" + sgclient "github.com/StackGuardian/sg-sdk-go/client" + sgoption "github.com/StackGuardian/sg-sdk-go/option" + "github.com/StackGuardian/sg-sdk-go/stacktemplates" + "github.com/StackGuardian/sg-sdk-go/workflowtemplates" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +var org = os.Getenv("STACKGUARDIAN_ORG_NAME") + +func getClient() *sgclient.Client { + customHeader := http.Header{} + customHeader.Set("x-sg-internal-auth-orgid", "sg-provider-test") + + return sgclient.NewClient( + sgoption.WithApiKey(fmt.Sprintf("apikey %s", os.Getenv("STACKGUARDIAN_API_KEY"))), + sgoption.WithBaseURL(os.Getenv("STACKGUARDIAN_API_URI")), + sgoption.WithHTTPHeader(customHeader), + ) +} + +// --- Fixture helpers: create resources via SDK --- + +func createWorkflowTemplateFixture(templateName, sourceConfigKind string) error { + client := getClient() + kind := workflowtemplates.WorkflowTemplateSourceConfigKindEnum(sourceConfigKind) + _, err := client.WorkflowTemplates.CreateWorkflowTemplate(context.TODO(), org, false, &workflowtemplates.CreateWorkflowTemplateRequest{ + Id: &templateName, + TemplateName: templateName, + SourceConfigKind: &kind, + TemplateType: sgsdkgo.TemplateTypeEnumIac, + IsPublic: sgsdkgo.IsPublicEnumZero.Ptr(), + OwnerOrg: fmt.Sprintf("/orgs/%s", org), + }) + return err +} + +func createStackTemplateFixture(templateName, sourceConfigKind string) error { + client := getClient() + kind := stacktemplates.StackTemplateSourceConfigKindEnum(sourceConfigKind) + _, err := client.StackTemplates.CreateStackTemplate(context.TODO(), org, false, &stacktemplates.CreateStackTemplateRequest{ + Id: &templateName, + TemplateName: templateName, + SourceConfigKind: &kind, + TemplateType: sgsdkgo.TemplateTypeEnumIacGroup, + IsPublic: sgsdkgo.IsPublicEnumZero.Ptr(), + OwnerOrg: fmt.Sprintf("/orgs/%s", org), + }) + return err +} + +// --- Fixture helpers: delete resources via SDK (best-effort, for cleanup) --- + +func deleteWorkflowTemplateFixture(templateId string) { + client := getClient() + client.WorkflowTemplates.DeleteWorkflowTemplate(context.TODO(), org, templateId) +} + +func deleteStackTemplateFixture(templateId string) { + client := getClient() + client.StackTemplates.DeleteStackTemplate(context.TODO(), org, templateId) +} + +func deleteStackTemplateRevisionFixture(revisionId string) { + client := getClient() + client.StackTemplateRevisions.DeleteStackTemplateRevision(context.TODO(), org, revisionId, true) +} + +// --- Terraform config generators --- + +func testAccStackTemplateRevisionConfig(stackTemplateID, wfTemplateID, alias string) string { + return fmt.Sprintf(` +resource "stackguardian_stack_template_revision" "test" { + parent_template_id = "%s" + alias = "%s" + notes = "Test revision notes" + description = "Test revision description" + source_config_kind = "TERRAFORM" + + workflows_config = { + workflows = [ + { + id = "d8dfaf15-2ad9-da29-8af0-c6b288b12089" + template_id = "%s" + resource_name = "wf-1" + + terraform_config = { + managed_terraform_state = true + terraform_version = "1.5.7" + } + } + ] + } +} +`, stackTemplateID, alias, wfTemplateID) +} + +func testAccStackTemplateRevisionConfigUpdated(stackTemplateID, wfTemplateID, alias string) string { + return fmt.Sprintf(` +resource "stackguardian_stack_template_revision" "test" { + parent_template_id = "%s" + alias = "%s" + notes = "Updated revision notes" + description = "Updated revision description" + source_config_kind = "TERRAFORM" + + workflows_config = { + workflows = [ + { + id = "d8dfaf15-2ad9-da29-8af0-c6b288b12089" + template_id = "%s" + resource_name = "wf-1" + + terraform_config = { + managed_terraform_state = true + terraform_version = "1.5.7" + } + } + ] + } +} +`, stackTemplateID, alias, wfTemplateID) +} + +func testAccStackTemplateRevisionWithWorkflowsConfig(stackTemplateID, wfTemplateID, alias string) string { + return fmt.Sprintf(` +resource "stackguardian_stack_template_revision" "test" { + parent_template_id = "%s" + alias = "%s" + notes = "Revision with workflows config" + description = "Test revision with workflows configuration" + source_config_kind = "TERRAFORM" + + workflows_config = { + workflows = [ + { + id = "d8dfaf15-2ad9-da29-8af0-c6b288b12089" + template_id = "%s" + resource_name = "wf-1" + + vcs_config = { + iac_vcs_config = { + use_marketplace_template = true + iac_template_id = "%s" + } + iac_input_data = { + schema_type = "RAW_JSON" + data = jsonencode({ + bucket_region = "eu-central-1" + }) + } + } + + terraform_config = { + managed_terraform_state = true + terraform_version = "1.5.7" + } + } + ] + } +} +`, stackTemplateID, alias, wfTemplateID, wfTemplateID) +} + +// --- Tests --- + +func TestAccStackTemplateRevision_Basic(t *testing.T) { + stackTemplateID := "provider-test-stack-template-rev1" + wfTemplateID := "provider-test-wft-for-stack-rev1" + revisionAlias := "v1" + + // Register cleanup before creation so defers run even if a later create fails + defer deleteStackTemplateRevisionFixture(fmt.Sprintf("%s:1", stackTemplateID)) + defer deleteStackTemplateFixture(stackTemplateID) + defer deleteWorkflowTemplateFixture(wfTemplateID) + + // Create prerequisite resources via SDK + if err := createWorkflowTemplateFixture(wfTemplateID, "TERRAFORM"); err != nil { + t.Fatalf("failed to create workflow template fixture: %s", err) + } + if err := createStackTemplateFixture(stackTemplateID, "TERRAFORM"); err != nil { + t.Fatalf("failed to create stack template fixture: %s", err) + } + + customHeader := http.Header{} + customHeader.Set("x-sg-internal-auth-orgid", "sg-provider-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), + }, + ProtoV6ProviderFactories: acctest.ProviderFactories(customHeader), + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccStackTemplateRevisionConfig(stackTemplateID, wfTemplateID, revisionAlias), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "alias", revisionAlias), + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "notes", "Test revision notes"), + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "description", "Test revision description"), + resource.TestCheckResourceAttrSet("stackguardian_stack_template_revision.test", "id"), + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "parent_template_id", stackTemplateID), + resource.TestCheckResourceAttrSet("stackguardian_stack_template_revision.test", "template_id"), + resource.TestCheckResourceAttrSet("stackguardian_stack_template_revision.test", "workflows_config.%"), + ), + }, + // Update and Read testing + { + Config: testAccStackTemplateRevisionConfigUpdated(stackTemplateID, wfTemplateID, revisionAlias), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "alias", revisionAlias), + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "notes", "Updated revision notes"), + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "description", "Updated revision description"), + resource.TestCheckResourceAttrSet("stackguardian_stack_template_revision.test", "id"), + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "parent_template_id", stackTemplateID), + ), + }, + // Delete testing automatically occurs + }, + }) +} + +func TestAccStackTemplateRevision_WithWorkflowsConfig(t *testing.T) { + stackTemplateID := "provider-test-stack-template-rev2" + wfTemplateID := "provider-test-wft-for-stack-rev2" + revisionAlias := "v1" + + // Register cleanup before creation so defers run even if a later create fails + defer deleteStackTemplateRevisionFixture(fmt.Sprintf("%s:1", stackTemplateID)) + defer deleteStackTemplateFixture(stackTemplateID) + defer deleteWorkflowTemplateFixture(wfTemplateID) + + // Create prerequisite resources via SDK + if err := createWorkflowTemplateFixture(wfTemplateID, "TERRAFORM"); err != nil { + t.Fatalf("failed to create workflow template fixture: %s", err) + } + if err := createStackTemplateFixture(stackTemplateID, "TERRAFORM"); err != nil { + t.Fatalf("failed to create stack template fixture: %s", err) + } + + customHeader := http.Header{} + customHeader.Set("x-sg-internal-auth-orgid", "sg-provider-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), + }, + ProtoV6ProviderFactories: acctest.ProviderFactories(customHeader), + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccStackTemplateRevisionWithWorkflowsConfig(stackTemplateID, wfTemplateID, revisionAlias), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "alias", revisionAlias), + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "notes", "Revision with workflows config"), + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "description", "Test revision with workflows configuration"), + resource.TestCheckResourceAttrSet("stackguardian_stack_template_revision.test", "id"), + resource.TestCheckResourceAttr("stackguardian_stack_template_revision.test", "parent_template_id", stackTemplateID), + resource.TestCheckResourceAttrSet("stackguardian_stack_template_revision.test", "template_id"), + resource.TestCheckResourceAttrSet("stackguardian_stack_template_revision.test", "workflows_config.%"), + ), + }, + }, + }) +} diff --git a/internal/resource/stack_template_revision/schema.go b/internal/resource/stack_template_revision/schema.go new file mode 100644 index 0000000..4290f78 --- /dev/null +++ b/internal/resource/stack_template_revision/schema.go @@ -0,0 +1,750 @@ +package stacktemplaterevision + +import ( + "context" + "fmt" + + "github.com/StackGuardian/terraform-provider-stackguardian/internal/constants" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Shared schema attribute maps reused across workflows_config and actions. + +var ministepsNotificationRecipients = schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "recipients": schema.ListAttribute{ + MarkdownDescription: constants.MiniStepsNotificationsRecipients, + Optional: true, + ElementType: types.StringType, + }, + }, + }, +} + +var ministepsWebhooks = schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "webhook_name": schema.StringAttribute{ + MarkdownDescription: constants.MiniStepsWebhookName, + Required: true, + }, + "webhook_url": schema.StringAttribute{ + MarkdownDescription: constants.MiniStepsWebhookURL, + Required: true, + }, + "webhook_secret": schema.StringAttribute{ + MarkdownDescription: constants.MiniStepsWebhookSecret, + Optional: true, + }, + }, + }, +} + +var ministepsWorkflowChaining = schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "workflow_group_id": schema.StringAttribute{ + MarkdownDescription: constants.MiniStepsWfChainingWorkflowGroupId, + Required: true, + }, + "stack_id": schema.StringAttribute{ + MarkdownDescription: constants.MiniStepsWfChainingStackId, + Optional: true, + }, + "stack_run_payload": schema.StringAttribute{ + MarkdownDescription: constants.MiniStepsWfChainingStackPayload, + Optional: true, + }, + "workflow_id": schema.StringAttribute{ + MarkdownDescription: constants.MiniStepsWfChainingWorkflowId, + Optional: true, + }, + "workflow_run_payload": schema.StringAttribute{ + MarkdownDescription: constants.MiniStepsWfChainingWorkflowPayload, + Optional: true, + }, + }, + }, +} + +var envVarsAttrs = map[string]schema.Attribute{ + "config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.EnvVarConfig, + Required: true, + Attributes: map[string]schema.Attribute{ + "var_name": schema.StringAttribute{ + MarkdownDescription: constants.EnvVarConfigVarName, + Required: true, + }, + "secret_id": schema.StringAttribute{ + MarkdownDescription: constants.EnvVarConfigSecretId, + Optional: true, + }, + "text_value": schema.StringAttribute{ + MarkdownDescription: constants.EnvVarConfigTextValue, + Optional: true, + }, + }, + }, + "kind": schema.StringAttribute{ + MarkdownDescription: constants.EnvVarKind, + Required: true, + }, +} + +var mountPointAttrs = map[string]schema.Attribute{ + "source": schema.StringAttribute{ + MarkdownDescription: constants.MountPointSource, + Optional: true, + }, + "target": schema.StringAttribute{ + MarkdownDescription: constants.MountPointTarget, + Optional: true, + }, + "read_only": schema.BoolAttribute{ + MarkdownDescription: constants.MountPointReadOnly, + Optional: true, + }, +} + +var wfStepsConfigNestedObj = schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: constants.WfStepName, + Required: true, + }, + "environment_variables": schema.ListNestedAttribute{ + MarkdownDescription: constants.WfStepEnvVars, + Optional: true, + NestedObject: schema.NestedAttributeObject{Attributes: envVarsAttrs}, + }, + "approval": schema.BoolAttribute{ + MarkdownDescription: constants.WfStepApproval, + Optional: true, + }, + "timeout": schema.Int64Attribute{ + MarkdownDescription: constants.WfStepTimeout, + Optional: true, + }, + "cmd_override": schema.StringAttribute{ + MarkdownDescription: constants.WfStepCmdOverride, + Optional: true, + }, + "mount_points": schema.ListNestedAttribute{ + MarkdownDescription: constants.WfStepMountPoints, + Optional: true, + NestedObject: schema.NestedAttributeObject{Attributes: mountPointAttrs}, + }, + "wf_step_template_id": schema.StringAttribute{ + MarkdownDescription: constants.WfStepTemplateId, + Required: true, + }, + "wf_step_input_data": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WfStepInputData, + Optional: true, + Attributes: map[string]schema.Attribute{ + "schema_type": schema.StringAttribute{ + MarkdownDescription: constants.WfStepInputDataSchemaType, + Optional: true, + }, + "data": schema.StringAttribute{ + MarkdownDescription: constants.WfStepInputDataData, + Optional: true, + }, + }, + }, + }, +} + +var deploymentPlatformConfigAttrs = map[string]schema.Attribute{ + "kind": schema.StringAttribute{ + MarkdownDescription: constants.DeploymentPlatformKind, + Required: true, + }, + "config": schema.StringAttribute{ + MarkdownDescription: constants.DeploymentPlatformConfigDetails + " (JSON string)", + Optional: true, + }, +} + +// workflowInStackAttrs defines the schema attributes for a single workflow inside workflows_config. +// Fields correspond to the SDK's StackTemplateRevisionWorkflow struct. +var workflowInStackAttrs = map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "UUID identifying the workflow within the stack template.", + Required: true, + }, + "template_id": schema.StringAttribute{ + MarkdownDescription: "ID of the workflow template that this workflow is based on.", + Required: true, + }, + "resource_name": schema.StringAttribute{ + MarkdownDescription: "Name of the workflow resource within the stack.", + Optional: true, + }, + "wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: "Workflow steps configuration.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + NestedObject: wfStepsConfigNestedObj, + }, + "terraform_config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.TerraformConfig, + Optional: true, + Attributes: map[string]schema.Attribute{ + "terraform_version": schema.StringAttribute{ + MarkdownDescription: constants.TerraformVersion, + Optional: true, + }, + "drift_check": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformDriftCheck, + Optional: true, + }, + "drift_cron": schema.StringAttribute{ + MarkdownDescription: constants.TerraformDriftCron, + Optional: true, + }, + "managed_terraform_state": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformManagedState, + Optional: true, + }, + "approval_pre_apply": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformApprovalPreApply, + Optional: true, + }, + "terraform_plan_options": schema.StringAttribute{ + MarkdownDescription: constants.TerraformPlanOptions, + Optional: true, + }, + "terraform_init_options": schema.StringAttribute{ + MarkdownDescription: constants.TerraformInitOptions, + Optional: true, + }, + "terraform_bin_path": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformBinPath, + Optional: true, + NestedObject: schema.NestedAttributeObject{Attributes: mountPointAttrs}, + }, + "timeout": schema.Int64Attribute{ + MarkdownDescription: constants.TerraformTimeout, + Optional: true, + }, + "post_apply_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPostApplyWfSteps, + Optional: true, + NestedObject: wfStepsConfigNestedObj, + }, + "pre_apply_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPreApplyWfSteps, + Optional: true, + NestedObject: wfStepsConfigNestedObj, + }, + "pre_plan_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPrePlanWfSteps, + Optional: true, + NestedObject: wfStepsConfigNestedObj, + }, + "post_plan_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPostPlanWfSteps, + Optional: true, + NestedObject: wfStepsConfigNestedObj, + }, + "pre_init_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPreInitHooks, + Optional: true, + ElementType: types.StringType, + }, + "pre_plan_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPrePlanHooks, + Optional: true, + ElementType: types.StringType, + }, + "post_plan_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPostPlanHooks, + Optional: true, + ElementType: types.StringType, + }, + "pre_apply_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPreApplyHooks, + Optional: true, + ElementType: types.StringType, + }, + "post_apply_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPostApplyHooks, + Optional: true, + ElementType: types.StringType, + }, + "run_pre_init_hooks_on_drift": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformRunPreInitHooksOnDrift, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + "environment_variables": schema.ListNestedAttribute{ + MarkdownDescription: "Environment variables for the workflow.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + NestedObject: schema.NestedAttributeObject{Attributes: envVarsAttrs}, + }, + "deployment_platform_config": schema.ListNestedAttribute{ + MarkdownDescription: "Deployment platform configuration.", + Optional: true, + NestedObject: schema.NestedAttributeObject{Attributes: deploymentPlatformConfigAttrs}, + }, + "vcs_config": schema.SingleNestedAttribute{ + MarkdownDescription: "VCS (version control) configuration for the workflow.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "iac_vcs_config": schema.SingleNestedAttribute{ + MarkdownDescription: "IaC VCS configuration.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "use_marketplace_template": schema.BoolAttribute{ + MarkdownDescription: "Whether to use a marketplace template.", + Optional: true, + }, + "iac_template_id": schema.StringAttribute{ + MarkdownDescription: "ID of the IaC template from the marketplace.", + Optional: true, + }, + "custom_source": schema.SingleNestedAttribute{ + MarkdownDescription: "Custom source configuration.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "source_config_dest_kind": schema.StringAttribute{ + MarkdownDescription: constants.RuntimeSourceDestKind, + Required: true, + }, + "config": schema.SingleNestedAttribute{ + MarkdownDescription: "Source configuration details.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "is_private": schema.BoolAttribute{ + Optional: true, + }, + "auth": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + "working_dir": schema.StringAttribute{ + Optional: true, + }, + "git_sparse_checkout_config": schema.StringAttribute{ + Optional: true, + }, + "git_core_auto_crlf": schema.BoolAttribute{ + Optional: true, + }, + "ref": schema.StringAttribute{ + Optional: true, + }, + "repo": schema.StringAttribute{ + Optional: true, + }, + "include_sub_module": schema.BoolAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "iac_input_data": schema.SingleNestedAttribute{ + MarkdownDescription: "IaC input data for the workflow.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "schema_id": schema.StringAttribute{ + Optional: true, + }, + "schema_type": schema.StringAttribute{ + Required: true, + }, + "data": schema.StringAttribute{ + MarkdownDescription: "Input data as a JSON string.", + Optional: true, + }, + }, + }, + }, + }, + // iac_input_data at the workflow level corresponds to TemplatesIacInputData in the SDK. + // Used when the workflow is instantiated from a workflow template (template_id). + "iac_input_data": schema.SingleNestedAttribute{ + MarkdownDescription: "Top-level IaC input data for this workflow, used when the workflow is instantiated from a workflow template (`template_id`).", + Optional: true, + Attributes: map[string]schema.Attribute{ + "schema_type": schema.StringAttribute{ + MarkdownDescription: "Schema type for the input data (e.g. RAW_JSON).", + Required: true, + }, + "data": schema.StringAttribute{ + MarkdownDescription: "Input data as a JSON string.", + Optional: true, + }, + }, + }, + "user_schedules": schema.ListNestedAttribute{ + MarkdownDescription: "Scheduled run configuration.", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Optional: true, + }, + "desc": schema.StringAttribute{ + Optional: true, + }, + "cron": schema.StringAttribute{ + MarkdownDescription: constants.UserScheduleCron, + Required: true, + }, + "state": schema.StringAttribute{ + MarkdownDescription: constants.UserScheduleState, + Required: true, + }, + }, + }, + }, + "approvers": schema.ListAttribute{ + MarkdownDescription: "List of approvers.", + ElementType: types.StringType, + Optional: true, + }, + "number_of_approvals_required": schema.Int64Attribute{ + MarkdownDescription: "Number of approvals required.", + Optional: true, + }, + "runner_constraints": schema.SingleNestedAttribute{ + MarkdownDescription: "Runner constraints for the workflow.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + MarkdownDescription: constants.RunnerConstraintsType, + Required: true, + }, + "names": schema.ListAttribute{ + MarkdownDescription: constants.RunnerConstraintsNames, + ElementType: types.StringType, + Optional: true, + }, + }, + }, + "user_job_cpu": schema.Int64Attribute{ + MarkdownDescription: "CPU limit for the user job.", + Optional: true, + }, + "user_job_memory": schema.Int64Attribute{ + MarkdownDescription: "Memory limit for the user job.", + Optional: true, + }, + "input_schemas": schema.ListNestedAttribute{ + MarkdownDescription: "Input schema definitions for this workflow.", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Optional: true, + }, + "name": schema.StringAttribute{ + Optional: true, + }, + "description": schema.StringAttribute{ + Optional: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "Schema type (e.g. FORM_JSONSCHEMA).", + Required: true, + }, + "encoded_data": schema.StringAttribute{ + MarkdownDescription: "Base64-encoded schema data.", + Optional: true, + }, + "ui_schema_data": schema.StringAttribute{ + Optional: true, + }, + "is_committed": schema.BoolAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + "mini_steps": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WorkflowTemplateRevisionMiniSteps, + Optional: true, + Attributes: map[string]schema.Attribute{ + "notifications": schema.SingleNestedAttribute{ + MarkdownDescription: constants.MiniStepsNotifications, + Optional: true, + Attributes: map[string]schema.Attribute{ + "email": schema.SingleNestedAttribute{ + MarkdownDescription: constants.MiniStepsNotificationsEmail, + Optional: true, + Attributes: map[string]schema.Attribute{ + "approval_required": ministepsNotificationRecipients, + "cancelled": ministepsNotificationRecipients, + "completed": ministepsNotificationRecipients, + "drift_detected": ministepsNotificationRecipients, + "errored": ministepsNotificationRecipients, + }, + }, + }, + }, + "webhooks": schema.SingleNestedAttribute{ + MarkdownDescription: constants.MiniStepsWebhooks, + Optional: true, + Attributes: map[string]schema.Attribute{ + "approval_required": ministepsWebhooks, + "cancelled": ministepsWebhooks, + "completed": ministepsWebhooks, + "drift_detected": ministepsWebhooks, + "errored": ministepsWebhooks, + }, + }, + "wf_chaining": schema.SingleNestedAttribute{ + Optional: true, + MarkdownDescription: constants.MiniStepsWorkflowChaining, + Attributes: map[string]schema.Attribute{ + "completed": ministepsWorkflowChaining, + "errored": ministepsWorkflowChaining, + }, + }, + }, + }, +} + +// actionOrderAttrs defines the schema for a single ActionOrder (value in the order map). +var actionOrderAttrs = map[string]schema.Attribute{ + "parameters": schema.SingleNestedAttribute{ + MarkdownDescription: "Run configuration parameters for the action step.", + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "terraform_action": schema.SingleNestedAttribute{ + MarkdownDescription: "Terraform-specific action parameters.", + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + MarkdownDescription: `Terraform action to execute. E.g., "apply", "plan", "destroy".`, + Computed: true, + }, + }, + }, + "deployment_platform_config": schema.ListNestedAttribute{ + MarkdownDescription: "Deployment platform configuration.", + Optional: true, + NestedObject: schema.NestedAttributeObject{Attributes: deploymentPlatformConfigAttrs}, + }, + "wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: "Workflow steps configuration.", + Optional: true, + Computed: true, + NestedObject: wfStepsConfigNestedObj, + }, + "environment_variables": schema.ListNestedAttribute{ + MarkdownDescription: "Environment variables.", + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{Attributes: envVarsAttrs}, + }, + }, + }, + "dependencies": schema.ListNestedAttribute{ + MarkdownDescription: "List of workflow dependencies that must complete before this step runs.", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "ID of the dependent workflow.", + Required: true, + }, + "condition": schema.SingleNestedAttribute{ + MarkdownDescription: "Condition that must be met by the dependency.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "latest_status": schema.StringAttribute{ + MarkdownDescription: "Required latest status of the dependency (e.g., COMPLETED).", + Required: true, + }, + }, + }, + }, + }, + }, +} + +// Schema defines the schema for the resource. +func (r *stackTemplateRevisionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateRevisionId, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "parent_template_id": schema.StringAttribute{ + MarkdownDescription: "ID of the parent stack template to create the revision under.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "template_id": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateRevisionTemplateId, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "alias": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateRevisionAlias, + Optional: true, + Computed: true, + }, + "notes": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateRevisionNotes, + Optional: true, + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateRevisionDescription, + Optional: true, + Computed: true, + }, + "source_config_kind": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateSourceConfigKindCommon, + Required: true, + }, + "is_active": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateIsActiveCommon, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "is_public": schema.StringAttribute{ + MarkdownDescription: constants.StackTemplateIsPublicCommon, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Tags, "stack template revision"), + ElementType: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: fmt.Sprintf(constants.ContextTags, "stack template revision"), + ElementType: types.StringType, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Map{ + mapplanmodifier.UseStateForUnknown(), + }, + }, + "deprecation": schema.SingleNestedAttribute{ + MarkdownDescription: constants.Deprecation, + Optional: true, + Attributes: map[string]schema.Attribute{ + "effective_date": schema.StringAttribute{ + MarkdownDescription: constants.TemplateRevisionDeprecationEffectiveDate, + Optional: true, + }, + "message": schema.StringAttribute{ + MarkdownDescription: constants.TemplateRevisionDeprecation, + Optional: true, + }, + }, + }, + "workflows_config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.StackTemplateRevisionWorkflowsConfig, + Optional: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + Attributes: map[string]schema.Attribute{ + "workflows": schema.ListNestedAttribute{ + MarkdownDescription: "List of workflows that make up the stack.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + NestedObject: schema.NestedAttributeObject{ + Attributes: workflowInStackAttrs, + }, + }, + }, + }, + "actions": schema.MapNestedAttribute{ + MarkdownDescription: constants.StackTemplateRevisionActions, + PlanModifiers: []planmodifier.Map{ + mapplanmodifier.UseStateForUnknown(), + }, + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the action.", + Optional: true, + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: "Description of the action.", + Optional: true, + Computed: true, + }, + "default": schema.BoolAttribute{ + MarkdownDescription: "Whether this is the default action.", + Optional: true, + Computed: true, + }, + "order": schema.MapNestedAttribute{ + MarkdownDescription: "Ordered map of workflow IDs to their action configurations.", + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: actionOrderAttrs, + }, + }, + }, + }, + }, + }, + } +}