From a63f56ae7d8fba1038ca7b7297333fb1deb7d1e6 Mon Sep 17 00:00:00 2001 From: Taher Kathanawala <28436188+taherkk@users.noreply.github.com> Date: Sat, 28 Feb 2026 12:13:23 +0530 Subject: [PATCH 1/6] [SG-4116] feat/workflow template (#90) * Add workflow template and revisions resource and datasource --- CLAUDE.md | 168 ++ .../workflow_template/datasource.tf | 7 + .../workflow_template_revision/datasource.tf | 7 + .../resources/workflow_template/resource.tf | 23 + .../workflow_template_revision/resource.tf | 22 + .../data-sources/workflow_template.md.tmpl | 15 + .../workflow_template_revision.md.tmpl | 15 + .../resources/workflow_template.md.tmpl | 32 + .../workflow_template_revision.md.tmpl | 32 + docs/data-sources/workflow_template.md | 93 + .../workflow_template_revision.md | 624 +++++ docs/resources/workflow_template.md | 131 + docs/resources/workflow_template_revision.md | 722 ++++++ go.mod | 2 + internal/acctest/acctest.go | 5 +- internal/constants/template.go | 165 ++ .../runner_group/datasource_test.go | 3 +- .../workflow_template/datasource.go | 84 + .../datasources/workflow_template/schema.go | 122 + .../workflow_template_revision/datasource.go | 89 + .../workflow_template_revision/schema.go | 440 ++++ internal/expanders/maps.go | 22 + internal/provider/provider.go | 18 +- internal/resource/connector/resource_test.go | 7 +- internal/resource/policy/resource_test.go | 7 +- internal/resource/role/resource_test.go | 35 +- .../resource/role_assignment/resource_test.go | 17 +- internal/resource/role_v4/resource_test.go | 11 +- .../resource/runner_group/resource_test.go | 9 +- internal/resource/runner_group/schema.go | 1 - .../resource/workflow_group/resource_test.go | 9 +- internal/resource/workflow_template/model.go | 516 ++++ .../resource/workflow_template/resource.go | 204 ++ .../workflow_template/resource_test.go | 116 + internal/resource/workflow_template/schema.go | 154 ++ .../workflow_template_revision/model.go | 2161 +++++++++++++++++ .../workflow_template_revision/resource.go | 222 ++ .../resource_test.go | 187 ++ .../workflow_template_revision/schema.go | 522 ++++ main.go | 4 +- 40 files changed, 6984 insertions(+), 39 deletions(-) create mode 100644 CLAUDE.md create mode 100644 docs-examples/datasources/workflow_template/datasource.tf create mode 100644 docs-examples/datasources/workflow_template_revision/datasource.tf create mode 100644 docs-examples/resources/workflow_template/resource.tf create mode 100644 docs-examples/resources/workflow_template_revision/resource.tf create mode 100644 docs-templates/data-sources/workflow_template.md.tmpl create mode 100644 docs-templates/data-sources/workflow_template_revision.md.tmpl create mode 100644 docs-templates/resources/workflow_template.md.tmpl create mode 100644 docs-templates/resources/workflow_template_revision.md.tmpl create mode 100644 docs/data-sources/workflow_template.md create mode 100644 docs/data-sources/workflow_template_revision.md create mode 100644 docs/resources/workflow_template.md create mode 100644 docs/resources/workflow_template_revision.md create mode 100644 internal/constants/template.go create mode 100644 internal/datasources/workflow_template/datasource.go create mode 100644 internal/datasources/workflow_template/schema.go create mode 100644 internal/datasources/workflow_template_revision/datasource.go create mode 100644 internal/datasources/workflow_template_revision/schema.go create mode 100644 internal/expanders/maps.go create mode 100644 internal/resource/workflow_template/model.go create mode 100644 internal/resource/workflow_template/resource.go create mode 100644 internal/resource/workflow_template/resource_test.go create mode 100644 internal/resource/workflow_template/schema.go create mode 100644 internal/resource/workflow_template_revision/model.go create mode 100644 internal/resource/workflow_template_revision/resource.go create mode 100644 internal/resource/workflow_template_revision/resource_test.go create mode 100644 internal/resource/workflow_template_revision/schema.go diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4c7a7f8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,168 @@ +# CLAUDE.md — terraform-provider-stackguardian + +## Project Overview + +Terraform provider for the StackGuardian Orchestrator platform. Built with the [Terraform Plugin Framework v1](https://developer.hashicorp.com/terraform/plugin/framework). + +**Module:** `github.com/StackGuardian/terraform-provider-stackguardian` +**Go version:** 1.21.4 +**SDK:** `github.com/StackGuardian/sg-sdk-go` (currently on `feat-workflow-templates` feature branch) + +--- + +## Common Commands + +```bash +# Build +make build # compile provider binary +make install # build + install to ~/.terraform.d/plugins + +# Tests +make test # unit tests (4 parallel, 30s timeout) +make test-acc # acceptance tests (requires env vars, 1 parallel, 15m timeout) + +# Documentation +make docs-generate # run tfplugindocs generate +make docs-validate # validate generated docs + +# Clean +make clean +``` + +### Required environment variables for acceptance tests + +``` +TF_ACC=1 +STACKGUARDIAN_API_KEY= +STACKGUARDIAN_API_URI= +STACKGUARDIAN_ORG_NAME= +``` + +--- + +## Architecture + +### Directory Layout + +``` +internal/ + acctest/ # Shared acceptance test helpers + constants/ # Shared documentation strings and enums + customTypes/ # ProviderInfo struct (API client + credentials) + datasources// # Data source implementations + expanders/ # Terraform types → SDK types + flatteners/ # SDK types → Terraform types + provider/ # Provider registration + resource// # Resource implementations +``` + +### Per-resource file pattern + +Each resource in `internal/resource//` contains: + +| File | Purpose | +|------|---------| +| `resource.go` | CRUD operations, `Configure()`, `ImportState()` | +| `schema.go` | Schema definitions (`schema.SingleNestedAttribute`, `schema.ListNestedAttribute`, etc.) | +| `model.go` | Go structs with `tfsdk:` tags + `AttributeTypes()` methods + expander/flattener functions | +| `resource_test.go` | Acceptance tests | + +### Type conversion conventions + +- **Expanders** (`expanders/` + inside `model.go`): convert Terraform `types.*` → SDK Go types + - Named `convertXxxToAPI` +- **Flatteners** (`flatteners/` + inside `model.go`): convert SDK Go types → Terraform `types.*` + - Named `convertXxxFromAPI` +- Use `flatteners.String()`, `flatteners.StringPtr()`, `flatteners.BoolPtr()`, `flatteners.Int64Ptr()` for scalar conversions +- Use `flatteners.ListOfStringToTerraformList()` for `[]string → types.List` +- Use `expanders.MapStringString()` for `types.Map → map[string]string` +- Use `sgsdkgo.Optional(value)` and `sgsdkgo.Null[T]()` for optional SDK fields + +### Model struct rules + +- Every model struct must implement `AttributeTypes() map[string]attr.Type` +- `AttributeTypes()` must exactly match the schema — type mismatches cause runtime panics +- Schema `SingleNestedAttribute` → `types.Object` field +- Schema `ListNestedAttribute` → `types.List` field with `ElemType: types.ObjectType{...}` +- Schema `BoolAttribute` → `types.BoolType` in `AttributeTypes()` (not `StringType`) + +--- + +## Key Patterns + +### Handling optional SDK fields (UpdateRequest) + +```go +if value != nil { + apiModel.Field = sgsdkgo.Optional(value) +} else { + apiModel.Field = sgsdkgo.Null[FieldType]() +} +``` + +### Null guards in fromAPI converters + +```go +func convertXxxFromAPI(ctx context.Context, input *SomeType) (types.Object, diag.Diagnostics) { + nullObj := types.ObjectNull(XxxModel{}.AttributeTypes()) + if input == nil { + return nullObj, nil + } + // ... convert fields +} +``` + +### Loop variable addresses (safe in Go 1.22+) + +The SDK uses value slices (`[]T`, not `[]*T`). Taking `&item` inside a range loop is safe because converters use the pointer synchronously: + +```go +for i, item := range items { + obj, diags := convertItemFromAPI(ctx, &item) + // ... +} +``` + +--- + +## Common Gotchas + +1. **`AttributeTypes()` must match schema exactly.** Runtime errors like `Expected framework type … / Received framework type` always mean a mismatch between the schema definition and the corresponding model's `AttributeTypes()`. Fix the side that is wrong — usually the model's `AttributeTypes()` return value. + +2. **`SingleNestedAttribute` vs `ListNestedAttribute`:** If the API returns a list, use `schema.ListNestedAttribute` + `types.List` in the model. If it returns a single object, use `schema.SingleNestedAttribute` + `types.Object`. + +3. **SDK value slices:** `WfStepsConfig`, `EnvVars`, `InputSchemas`, etc. are value slices (`[]T`), not pointer slices (`[]*T`). + +4. **`UpdateRequest` vs `CreateRequest`:** Some fields present in Create are absent in Update (and vice versa). Always check the SDK struct for the specific request type. + +5. **`go build` is the source of truth** for compilation errors. IDE diagnostics can be stale. + +--- + +## Resources + +| Resource | `internal/resource/` path | +|----------|--------------------------| +| `stackguardian_connector` | `connector/` | +| `stackguardian_workflow_group` | `workflow_group/` | +| `stackguardian_role` | `role/` | +| `stackguardian_role_v4` | `role_v4/` | +| `stackguardian_role_assignment` | `role_assignment/` | +| `stackguardian_policy` | `policy/` | +| `stackguardian_runner_group` | `runner_group/` | +| `stackguardian_workflow_template` | `workflow_template/` | +| `stackguardian_workflow_template_revision` | `workflow_template_revision/` | + +--- + +## SDK Location + +During development the SDK is replaced with a local path in `go.mod`: + +``` +replace github.com/StackGuardian/sg-sdk-go => /path/to/sg-sdk-go.git/feat-workflow-templates +``` + +Key SDK packages: +- `github.com/StackGuardian/sg-sdk-go/sgsdkgo` — shared types (`EnvVars`, `WfStepsConfig`, `MountPoint`, `Optional`, `Null`, etc.) +- `github.com/StackGuardian/sg-sdk-go/workflowtemplaterevisions` — revision-specific types and request structs diff --git a/docs-examples/datasources/workflow_template/datasource.tf b/docs-examples/datasources/workflow_template/datasource.tf new file mode 100644 index 0000000..4b05947 --- /dev/null +++ b/docs-examples/datasources/workflow_template/datasource.tf @@ -0,0 +1,7 @@ +data "stackguardian_workflow_template" "example" { + template_name = "my-terraform-template" +} + +output "workflow_template_output" { + value = data.stackguardian_workflow_template.example.description +} diff --git a/docs-examples/datasources/workflow_template_revision/datasource.tf b/docs-examples/datasources/workflow_template_revision/datasource.tf new file mode 100644 index 0000000..dc95b08 --- /dev/null +++ b/docs-examples/datasources/workflow_template_revision/datasource.tf @@ -0,0 +1,7 @@ +data "stackguardian_workflow_template_revision" "example" { + id = "my-terraform-template:1" +} + +output "workflow_template_revision_output" { + value = data.stackguardian_workflow_template_revision.example.notes +} diff --git a/docs-examples/resources/workflow_template/resource.tf b/docs-examples/resources/workflow_template/resource.tf new file mode 100644 index 0000000..4e58284 --- /dev/null +++ b/docs-examples/resources/workflow_template/resource.tf @@ -0,0 +1,23 @@ +# Example 1: Basic workflow template +resource "stackguardian_workflow_template" "basic" { + template_name = "my-terraform-template" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "production"] +} + +# Example 2: Workflow template with runtime source +resource "stackguardian_workflow_template" "with_runtime" { + template_name = "template-with-runtime" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "github"] + + runtime_source = { + source_config_dest_kind = "GITHUB_COM" + config = { + is_private = false + repo = "https://github.com/example/terraform-modules.git" + } + } +} diff --git a/docs-examples/resources/workflow_template_revision/resource.tf b/docs-examples/resources/workflow_template_revision/resource.tf new file mode 100644 index 0000000..b2621c5 --- /dev/null +++ b/docs-examples/resources/workflow_template_revision/resource.tf @@ -0,0 +1,22 @@ +# Example 1: Basic workflow template revision +resource "stackguardian_workflow_template_revision" "basic" { + template_id = "my-terraform-template" + alias = "v1" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "revision"] +} + +# Example 2: Workflow template revision with detailed configuration +resource "stackguardian_workflow_template_revision" "detailed" { + template_id = "my-terraform-template" + alias = "v3" + source_config_kind = "TERRAFORM" + is_public = "0" + notes = "Production-ready revision with approval workflow" + user_job_cpu = 2 + user_job_memory = 4096 + number_of_approvals_required = 1 + tags = ["terraform", "production", "approved"] + approvers = ["user1@example.com", "user2@example.com"] +} diff --git a/docs-templates/data-sources/workflow_template.md.tmpl b/docs-templates/data-sources/workflow_template.md.tmpl new file mode 100644 index 0000000..bfc15f6 --- /dev/null +++ b/docs-templates/data-sources/workflow_template.md.tmpl @@ -0,0 +1,15 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_template Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_template (Data Source) + +## Example Usage + +{{tffile "docs-examples/datasources/workflow_template/datasource.tf"}} + +{{ .SchemaMarkdown }} diff --git a/docs-templates/data-sources/workflow_template_revision.md.tmpl b/docs-templates/data-sources/workflow_template_revision.md.tmpl new file mode 100644 index 0000000..b1208ab --- /dev/null +++ b/docs-templates/data-sources/workflow_template_revision.md.tmpl @@ -0,0 +1,15 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_template_revision Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_template_revision (Data Source) + +## Example Usage + +{{tffile "docs-examples/datasources/workflow_template_revision/datasource.tf"}} + +{{ .SchemaMarkdown }} diff --git a/docs-templates/resources/workflow_template.md.tmpl b/docs-templates/resources/workflow_template.md.tmpl new file mode 100644 index 0000000..4dc5f09 --- /dev/null +++ b/docs-templates/resources/workflow_template.md.tmpl @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_template Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_template (Resource) + +## Example Usage + +{{tffile "docs-examples/resources/workflow_template/resource.tf"}} + +{{ .SchemaMarkdown }} + +## Import + +Import existing resource + +### Using Import block (terraform v1.5.0 and later) +```terraform +import { + to = stackguardian_workflow_template.example + id = "template-name" +} +``` + +### Using CLI +```bash +terraform import stackguardian_workflow_template.example template-name +``` diff --git a/docs-templates/resources/workflow_template_revision.md.tmpl b/docs-templates/resources/workflow_template_revision.md.tmpl new file mode 100644 index 0000000..1c00f99 --- /dev/null +++ b/docs-templates/resources/workflow_template_revision.md.tmpl @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_template_revision Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_template_revision (Resource) + +## Example Usage + +{{tffile "docs-examples/resources/workflow_template_revision/resource.tf"}} + +{{ .SchemaMarkdown }} + +## Import + +Import existing resource + +### Using Import block (terraform v1.5.0 and later) +```terraform +import { + to = stackguardian_workflow_template_revision.example + id = "template-name:revision-version" +} +``` + +### Using CLI +```bash +terraform import stackguardian_workflow_template_revision.example template-name:revision-version +``` diff --git a/docs/data-sources/workflow_template.md b/docs/data-sources/workflow_template.md new file mode 100644 index 0000000..15b0895 --- /dev/null +++ b/docs/data-sources/workflow_template.md @@ -0,0 +1,93 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_template Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_template (Data Source) + +## Example Usage + +```terraform +data "stackguardian_workflow_template" "example" { + template_name = "my-terraform-template" +} + +output "workflow_template_output" { + value = data.stackguardian_workflow_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) Contextual tags to give context to your tags. +- `description` (String) A brief description of the workflow template. Must be less than 256 characters. +- `is_public` (String) Whether the template is public. +- `owner_org` (String) Organization the template belongs to. +- `runtime_source` (Attributes) Runtime source configuration for the template. (see [below for nested schema](#nestedatt--runtime_source)) +- `shared_orgs_list` (List of String) List of organizations the template is shared with. +- `source_config_kind` (String) +- `tags` (List of String) A list of tags associated with the workflow template. A maximum of 10 tags are allowed. +- `template_name` (String) Name of the workflow template. +- `template_type` (String) Type of the template. +- `vcs_triggers` (Attributes) VCS trigger configuration for the template. (see [below for nested schema](#nestedatt--vcs_triggers)) + + +### Nested Schema for `runtime_source` + +Read-Only: + +- `config` (Attributes) (see [below for nested schema](#nestedatt--runtime_source--config)) +- `source_config_dest_kind` (String) + + +### Nested Schema for `runtime_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 `vcs_triggers` + +Read-Only: + +- `create_tag` (Attributes) (see [below for nested schema](#nestedatt--vcs_triggers--create_tag)) +- `type` (String) + + +### Nested Schema for `vcs_triggers.create_tag` + +Read-Only: + +- `create_revision` (Attributes) (see [below for nested schema](#nestedatt--vcs_triggers--create_tag--create_revision)) + + +### Nested Schema for `vcs_triggers.create_tag.create_revision` + +Read-Only: + +- `enabled` (Boolean) + + + + + diff --git a/docs/data-sources/workflow_template_revision.md b/docs/data-sources/workflow_template_revision.md new file mode 100644 index 0000000..fb46486 --- /dev/null +++ b/docs/data-sources/workflow_template_revision.md @@ -0,0 +1,624 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_template_revision Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_template_revision (Data Source) + +## Example Usage + +```terraform +data "stackguardian_workflow_template_revision" "example" { + id = "my-terraform-template:1" +} + +output "workflow_template_revision_output" { + value = data.stackguardian_workflow_template_revision.example.notes +} +``` + + +## Schema + +### Required + +- `id` (String) ID of the resource. Should be used to import the resource. + +### Optional + +- `template_id` (String) Resource ID of the parent workflow template. + +### Read-Only + +- `alias` (String) +- `approvers` (List of String) +- `context_tags` (Map of String) Context tags for the revision. +- `deployment_platform_config` (Attributes) (see [below for nested schema](#nestedatt--deployment_platform_config)) +- `deprecation` (Attributes) (see [below for nested schema](#nestedatt--deprecation)) +- `description` (String) A brief description of the workflow template revision. Must be less than 256 characters. +- `environment_variables` (Attributes List) (see [below for nested schema](#nestedatt--environment_variables)) +- `input_schemas` (Attributes List) (see [below for nested schema](#nestedatt--input_schemas)) +- `is_public` (String) +- `mini_steps` (Attributes) (see [below for nested schema](#nestedatt--mini_steps)) +- `notes` (String) +- `number_of_approvals_required` (Number) +- `runner_constraints` (Attributes) (see [below for nested schema](#nestedatt--runner_constraints)) +- `runtime_source` (Attributes) (see [below for nested schema](#nestedatt--runtime_source)) +- `source_config_kind` (String) +- `tags` (List of String) A list of tags associated with the workflow template revision. A maximum of 10 tags are allowed. +- `terraform_config` (Attributes) (see [below for nested schema](#nestedatt--terraform_config)) +- `user_job_cpu` (Number) +- `user_job_memory` (Number) +- `user_schedules` (Attributes List) (see [below for nested schema](#nestedatt--user_schedules)) +- `wf_steps_config` (Attributes List) (see [below for nested schema](#nestedatt--wf_steps_config)) + + +### Nested Schema for `deployment_platform_config` + +Read-Only: + +- `config` (Attributes) (see [below for nested schema](#nestedatt--deployment_platform_config--config)) +- `kind` (String) + + +### Nested Schema for `deployment_platform_config.config` + +Read-Only: + +- `integration_id` (String) +- `profile_name` (String) + + + + +### Nested Schema for `deprecation` + +Read-Only: + +- `effective_date` (String) +- `message` (String) + + + +### Nested Schema for `environment_variables` + +Read-Only: + +- `config` (Attributes) (see [below for nested schema](#nestedatt--environment_variables--config)) +- `kind` (String) + + +### Nested Schema for `environment_variables.config` + +Read-Only: + +- `secret_id` (String) +- `text_value` (String) +- `var_name` (String) + + + + +### Nested Schema for `input_schemas` + +Read-Only: + +- `encoded_data` (String) +- `type` (String) +- `ui_schema_data` (String) + + + +### Nested Schema for `mini_steps` + +Read-Only: + +- `notifications` (Attributes) (see [below for nested schema](#nestedatt--mini_steps--notifications)) +- `webhooks` (Attributes) (see [below for nested schema](#nestedatt--mini_steps--webhooks)) +- `wf_chaining` (Attributes) (see [below for nested schema](#nestedatt--mini_steps--wf_chaining)) + + +### Nested Schema for `mini_steps.notifications` + +Read-Only: + +- `email` (Attributes) (see [below for nested schema](#nestedatt--mini_steps--notifications--email)) + + +### Nested Schema for `mini_steps.notifications.email` + +Read-Only: + +- `approval_required` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--notifications--email--approval_required)) +- `cancelled` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--notifications--email--cancelled)) +- `completed` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--notifications--email--completed)) +- `drift_detected` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--notifications--email--drift_detected)) +- `errored` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--notifications--email--errored)) + + +### Nested Schema for `mini_steps.notifications.email.approval_required` + +Read-Only: + +- `recipients` (List of String) + + + +### Nested Schema for `mini_steps.notifications.email.cancelled` + +Read-Only: + +- `recipients` (List of String) + + + +### Nested Schema for `mini_steps.notifications.email.completed` + +Read-Only: + +- `recipients` (List of String) + + + +### Nested Schema for `mini_steps.notifications.email.drift_detected` + +Read-Only: + +- `recipients` (List of String) + + + +### Nested Schema for `mini_steps.notifications.email.errored` + +Read-Only: + +- `recipients` (List of String) + + + + + +### Nested Schema for `mini_steps.webhooks` + +Read-Only: + +- `approval_required` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--webhooks--approval_required)) +- `cancelled` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--webhooks--cancelled)) +- `completed` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--webhooks--completed)) +- `drift_detected` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--webhooks--drift_detected)) +- `errored` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--webhooks--errored)) + + +### Nested Schema for `mini_steps.webhooks.approval_required` + +Read-Only: + +- `webhook_name` (String) +- `webhook_secret` (String) +- `webhook_url` (String) + + + +### Nested Schema for `mini_steps.webhooks.cancelled` + +Read-Only: + +- `webhook_name` (String) +- `webhook_secret` (String) +- `webhook_url` (String) + + + +### Nested Schema for `mini_steps.webhooks.completed` + +Read-Only: + +- `webhook_name` (String) +- `webhook_secret` (String) +- `webhook_url` (String) + + + +### Nested Schema for `mini_steps.webhooks.drift_detected` + +Read-Only: + +- `webhook_name` (String) +- `webhook_secret` (String) +- `webhook_url` (String) + + + +### Nested Schema for `mini_steps.webhooks.errored` + +Read-Only: + +- `webhook_name` (String) +- `webhook_secret` (String) +- `webhook_url` (String) + + + + +### Nested Schema for `mini_steps.wf_chaining` + +Read-Only: + +- `completed` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--wf_chaining--completed)) +- `errored` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--wf_chaining--errored)) + + +### Nested Schema for `mini_steps.wf_chaining.completed` + +Read-Only: + +- `stack_id` (String) +- `stack_run_payload` (String) +- `workflow_group_id` (String) +- `workflow_id` (String) +- `workflow_run_payload` (String) + + + +### Nested Schema for `mini_steps.wf_chaining.errored` + +Read-Only: + +- `stack_id` (String) +- `stack_run_payload` (String) +- `workflow_group_id` (String) +- `workflow_id` (String) +- `workflow_run_payload` (String) + + + + + +### Nested Schema for `runner_constraints` + +Read-Only: + +- `names` (List of String) +- `type` (String) + + + +### Nested Schema for `runtime_source` + +Read-Only: + +- `config` (Attributes) (see [below for nested schema](#nestedatt--runtime_source--config)) +- `source_config_dest_kind` (String) + + +### Nested Schema for `runtime_source.config` + +Read-Only: + +- `auth` (String) +- `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 `terraform_config` + +Read-Only: + +- `approval_pre_apply` (Boolean) +- `drift_check` (Boolean) +- `drift_cron` (String) +- `managed_terraform_state` (Boolean) +- `post_apply_hooks` (List of String) +- `post_apply_wf_steps_config` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--post_apply_wf_steps_config)) +- `post_plan_hooks` (List of String) +- `post_plan_wf_steps_config` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--post_plan_wf_steps_config)) +- `pre_apply_hooks` (List of String) +- `pre_apply_wf_steps_config` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--pre_apply_wf_steps_config)) +- `pre_init_hooks` (List of String) +- `pre_plan_hooks` (List of String) +- `pre_plan_wf_steps_config` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--pre_plan_wf_steps_config)) +- `run_pre_init_hooks_on_drift` (Boolean) +- `terraform_bin_path` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--terraform_bin_path)) +- `terraform_init_options` (String) +- `terraform_plan_options` (String) +- `terraform_version` (String) +- `timeout` (Number) + + +### Nested Schema for `terraform_config.post_apply_wf_steps_config` + +Read-Only: + +- `approval` (Boolean) +- `cmd_override` (String) +- `environment_variables` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--post_apply_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--post_apply_wf_steps_config--mount_points)) +- `name` (String) +- `timeout` (Number) +- `wf_step_input_data` (Attributes) (see [below for nested schema](#nestedatt--terraform_config--post_apply_wf_steps_config--wf_step_input_data)) +- `wf_step_template_id` (String) + + +### Nested Schema for `terraform_config.post_apply_wf_steps_config.environment_variables` + +Read-Only: + +- `config` (Attributes) (see [below for nested schema](#nestedatt--terraform_config--post_apply_wf_steps_config--environment_variables--config)) +- `kind` (String) + + +### Nested Schema for `terraform_config.post_apply_wf_steps_config.environment_variables.config` + +Read-Only: + +- `secret_id` (String) +- `text_value` (String) +- `var_name` (String) + + + + +### Nested Schema for `terraform_config.post_apply_wf_steps_config.mount_points` + +Read-Only: + +- `read_only` (Boolean) +- `source` (String) +- `target` (String) + + + +### Nested Schema for `terraform_config.post_apply_wf_steps_config.wf_step_input_data` + +Read-Only: + +- `data` (String) +- `schema_type` (String) + + + + +### Nested Schema for `terraform_config.post_plan_wf_steps_config` + +Read-Only: + +- `approval` (Boolean) +- `cmd_override` (String) +- `environment_variables` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--post_plan_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--post_plan_wf_steps_config--mount_points)) +- `name` (String) +- `timeout` (Number) +- `wf_step_input_data` (Attributes) (see [below for nested schema](#nestedatt--terraform_config--post_plan_wf_steps_config--wf_step_input_data)) +- `wf_step_template_id` (String) + + +### Nested Schema for `terraform_config.post_plan_wf_steps_config.environment_variables` + +Read-Only: + +- `config` (Attributes) (see [below for nested schema](#nestedatt--terraform_config--post_plan_wf_steps_config--environment_variables--config)) +- `kind` (String) + + +### Nested Schema for `terraform_config.post_plan_wf_steps_config.environment_variables.config` + +Read-Only: + +- `secret_id` (String) +- `text_value` (String) +- `var_name` (String) + + + + +### Nested Schema for `terraform_config.post_plan_wf_steps_config.mount_points` + +Read-Only: + +- `read_only` (Boolean) +- `source` (String) +- `target` (String) + + + +### Nested Schema for `terraform_config.post_plan_wf_steps_config.wf_step_input_data` + +Read-Only: + +- `data` (String) +- `schema_type` (String) + + + + +### Nested Schema for `terraform_config.pre_apply_wf_steps_config` + +Read-Only: + +- `approval` (Boolean) +- `cmd_override` (String) +- `environment_variables` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--pre_apply_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--pre_apply_wf_steps_config--mount_points)) +- `name` (String) +- `timeout` (Number) +- `wf_step_input_data` (Attributes) (see [below for nested schema](#nestedatt--terraform_config--pre_apply_wf_steps_config--wf_step_input_data)) +- `wf_step_template_id` (String) + + +### Nested Schema for `terraform_config.pre_apply_wf_steps_config.environment_variables` + +Read-Only: + +- `config` (Attributes) (see [below for nested schema](#nestedatt--terraform_config--pre_apply_wf_steps_config--environment_variables--config)) +- `kind` (String) + + +### Nested Schema for `terraform_config.pre_apply_wf_steps_config.environment_variables.config` + +Read-Only: + +- `secret_id` (String) +- `text_value` (String) +- `var_name` (String) + + + + +### Nested Schema for `terraform_config.pre_apply_wf_steps_config.mount_points` + +Read-Only: + +- `read_only` (Boolean) +- `source` (String) +- `target` (String) + + + +### Nested Schema for `terraform_config.pre_apply_wf_steps_config.wf_step_input_data` + +Read-Only: + +- `data` (String) +- `schema_type` (String) + + + + +### Nested Schema for `terraform_config.pre_plan_wf_steps_config` + +Read-Only: + +- `approval` (Boolean) +- `cmd_override` (String) +- `environment_variables` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--pre_plan_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) (see [below for nested schema](#nestedatt--terraform_config--pre_plan_wf_steps_config--mount_points)) +- `name` (String) +- `timeout` (Number) +- `wf_step_input_data` (Attributes) (see [below for nested schema](#nestedatt--terraform_config--pre_plan_wf_steps_config--wf_step_input_data)) +- `wf_step_template_id` (String) + + +### Nested Schema for `terraform_config.pre_plan_wf_steps_config.environment_variables` + +Read-Only: + +- `config` (Attributes) (see [below for nested schema](#nestedatt--terraform_config--pre_plan_wf_steps_config--environment_variables--config)) +- `kind` (String) + + +### Nested Schema for `terraform_config.pre_plan_wf_steps_config.environment_variables.config` + +Read-Only: + +- `secret_id` (String) +- `text_value` (String) +- `var_name` (String) + + + + +### Nested Schema for `terraform_config.pre_plan_wf_steps_config.mount_points` + +Read-Only: + +- `read_only` (Boolean) +- `source` (String) +- `target` (String) + + + +### Nested Schema for `terraform_config.pre_plan_wf_steps_config.wf_step_input_data` + +Read-Only: + +- `data` (String) +- `schema_type` (String) + + + + +### Nested Schema for `terraform_config.terraform_bin_path` + +Read-Only: + +- `read_only` (Boolean) +- `source` (String) +- `target` (String) + + + + +### Nested Schema for `user_schedules` + +Read-Only: + +- `cron` (String) +- `desc` (String) +- `name` (String) +- `state` (String) + + + +### Nested Schema for `wf_steps_config` + +Read-Only: + +- `approval` (Boolean) +- `cmd_override` (String) +- `environment_variables` (Attributes List) (see [below for nested schema](#nestedatt--wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) (see [below for nested schema](#nestedatt--wf_steps_config--mount_points)) +- `name` (String) +- `timeout` (Number) +- `wf_step_input_data` (Attributes) (see [below for nested schema](#nestedatt--wf_steps_config--wf_step_input_data)) +- `wf_step_template_id` (String) + + +### Nested Schema for `wf_steps_config.environment_variables` + +Read-Only: + +- `config` (Attributes) (see [below for nested schema](#nestedatt--wf_steps_config--environment_variables--config)) +- `kind` (String) + + +### Nested Schema for `wf_steps_config.environment_variables.config` + +Read-Only: + +- `secret_id` (String) +- `text_value` (String) +- `var_name` (String) + + + + +### Nested Schema for `wf_steps_config.mount_points` + +Read-Only: + +- `read_only` (Boolean) +- `source` (String) +- `target` (String) + + + +### Nested Schema for `wf_steps_config.wf_step_input_data` + +Read-Only: + +- `data` (String) +- `schema_type` (String) + + + + diff --git a/docs/resources/workflow_template.md b/docs/resources/workflow_template.md new file mode 100644 index 0000000..a277e9f --- /dev/null +++ b/docs/resources/workflow_template.md @@ -0,0 +1,131 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_template Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_template (Resource) + +## Example Usage + +```terraform +# Example 1: Basic workflow template +resource "stackguardian_workflow_template" "basic" { + template_name = "my-terraform-template" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "production"] +} + +# Example 2: Workflow template with runtime source +resource "stackguardian_workflow_template" "with_runtime" { + template_name = "template-with-runtime" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "github"] + + runtime_source = { + source_config_dest_kind = "GITHUB_COM" + config = { + is_private = false + repo = "https://github.com/example/terraform-modules.git" + } + } +} +``` + + +## Schema + +### Required + +- `source_config_kind` (String) Source configuration kind. Options: TERRAFORM, OPENTOFU, ANSIBLE_PLAYBOOK, HELM, KUBECTL, CLOUDFORMATION, CUSTOM +- `template_name` (String) Name of the workflow template. + +### Optional + +- `context_tags` (Map of String) Context tags for workflow template +- `description` (String) A brief description of the Description for workflow template. Must be less than 256 characters. +- `id` (String) ID of the resource — Use this attribute:
  • Set the Id of the resource manually
  • To reference the resource in other resources. The `resource_name` attribute is still available but its use is discouraged and may not work in some cases.
+- `is_public` (String) Make template available to other organisations. Available values: "0" or "1" +- `runtime_source` (Attributes) Runtime source configuration for the template. (see [below for nested schema](#nestedatt--runtime_source)) +- `shared_orgs_list` (List of String) List of organizations the template is shared with. +- `tags` (List of String) A list of tags associated with the Tags for workflow template. A maximum of 10 tags are allowed. +- `vcs_triggers` (Attributes) VCS trigger configuration for the workflow. (see [below for nested schema](#nestedatt--vcs_triggers)) + +### Read-Only + +- `owner_org` (String) Organization the template belongs to + + +### Nested Schema for `runtime_source` + +Optional: + +- `config` (Attributes) Configuration for the runtime environment. (see [below for nested schema](#nestedatt--runtime_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 `runtime_source.config` + +Required: + +- `repo` (String) Git repository URL. + +Optional: + +- `auth` (String, Sensitive) Connector id to access private git repository +- `git_core_auto_crlf` (Boolean) Whether to automatically handle CRLF line endings. +- `git_sparse_checkout_config` (String) Git sparse checkout command line git cli options. +- `include_sub_module` (Boolean) Whether to include git submodules. +- `is_private` (Boolean) Whether the repository is private. Auth is required if the repository is private +- `ref` (String) Git reference (branch, tag, or commit hash). +- `working_dir` (String) Working directory within the repository. + + + + +### Nested Schema for `vcs_triggers` + +Required: + +- `create_tag` (Attributes) Trigger configuration on tag creation in VCS (see [below for nested schema](#nestedatt--vcs_triggers--create_tag)) +- `type` (String) VCS provider type. Options: GITHUB_COM, GITHUB_APP_CUSTOM, GITLAB_OAUTH_SSH, GITLAB_COM + + +### Nested Schema for `vcs_triggers.create_tag` + +Required: + +- `create_revision` (Attributes) Create new revision on tag creation (see [below for nested schema](#nestedatt--vcs_triggers--create_tag--create_revision)) + + +### Nested Schema for `vcs_triggers.create_tag.create_revision` + +Optional: + +- `enabled` (Boolean) Whether to create revision when tag is created. + + + + + + +## Import + +Import existing resource + +### Using Import block (terraform v1.5.0 and later) +```terraform +import { + to = stackguardian_workflow_template.example + id = "template-name" +} +``` + +### Using CLI +```bash +terraform import stackguardian_workflow_template.example template-name +``` diff --git a/docs/resources/workflow_template_revision.md b/docs/resources/workflow_template_revision.md new file mode 100644 index 0000000..51eafef --- /dev/null +++ b/docs/resources/workflow_template_revision.md @@ -0,0 +1,722 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_template_revision Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_template_revision (Resource) + +## Example Usage + +```terraform +# Example 1: Basic workflow template revision +resource "stackguardian_workflow_template_revision" "basic" { + template_id = "my-terraform-template" + alias = "v1" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "revision"] +} + +# Example 2: Workflow template revision with detailed configuration +resource "stackguardian_workflow_template_revision" "detailed" { + template_id = "my-terraform-template" + alias = "v3" + source_config_kind = "TERRAFORM" + is_public = "0" + notes = "Production-ready revision with approval workflow" + user_job_cpu = 2 + user_job_memory = 4096 + number_of_approvals_required = 1 + tags = ["terraform", "production", "approved"] + approvers = ["user1@example.com", "user2@example.com"] +} +``` + + +## Schema + +### Required + +- `source_config_kind` (String) Source configuration kind. Options: TERRAFORM, OPENTOFU, ANSIBLE_PLAYBOOK, HELM, KUBECTL, CLOUDFORMATION, CUSTOM +- `template_id` (String) Resource ID of the parent workflow template. + +### Optional + +- `alias` (String) Alias for the template revision +- `approvers` (List of String) List of approvers for approvals during workflow execution. +- `context_tags` (Map of String) Context tags for workflow template revision +- `deployment_platform_config` (Attributes) Deployment platform configuration for the revision. (see [below for nested schema](#nestedatt--deployment_platform_config)) +- `deprecation` (Attributes) Marking a template revision for deprecation (see [below for nested schema](#nestedatt--deprecation)) +- `description` (String) A brief description of the workflow template revision. Must be less than 256 characters. +- `environment_variables` (Attributes List) List of environment variables for the revision. (see [below for nested schema](#nestedatt--environment_variables)) +- `input_schemas` (Attributes List) JSONSchema Form representation of input JSON data (see [below for nested schema](#nestedatt--input_schemas)) +- `is_public` (String) Whether a revision is published to be used. Options: "1", "0" +- `mini_steps` (Attributes) Actions that are required to be performed once workflow execution is complete (see [below for nested schema](#nestedatt--mini_steps)) +- `notes` (String) Notes for the revision +- `number_of_approvals_required` (Number) Number of approvals required. +- `runner_constraints` (Attributes) (see [below for nested schema](#nestedatt--runner_constraints)) +- `runtime_source` (Attributes) Runtime source configuration for the revision. (see [below for nested schema](#nestedatt--runtime_source)) +- `tags` (List of String) A list of tags associated with the workflow template revision. A maximum of 10 tags are allowed. +- `terraform_config` (Attributes) Terraform configuration. Valid only for terraform type template (see [below for nested schema](#nestedatt--terraform_config)) +- `user_job_cpu` (Number) Limits to set user job CPU. +- `user_job_memory` (Number) Limits to set user job memory. +- `user_schedules` (Attributes List) Configuration for scheduling runs for the workflows. (see [below for nested schema](#nestedatt--user_schedules)) +- `wf_steps_config` (Attributes List) Workflow steps configuration. Valid for custom workflow types. (see [below for nested schema](#nestedatt--wf_steps_config)) + +### Read-Only + +- `id` (String) ID of the resource — Use this attribute:
  • Set the Id of the resource manually
  • To reference the resource in other resources. The `resource_name` attribute is still available but its use is discouraged and may not work in some cases.
+ + +### Nested Schema for `deployment_platform_config` + +Required: + +- `config` (Attributes) Deployment platform configuration details. (see [below for nested schema](#nestedatt--deployment_platform_config--config)) +- `kind` (String) Deployment platform kind. Options: AWS_STATIC, AWS_RBAC, AWS_OIDC, AZURE_STATIC, AZURE_OIDC, GCP_STATIC, GCP_OIDC + + +### Nested Schema for `deployment_platform_config.config` + +Required: + +- `integration_id` (String) Integration ID for the deployment platform. + +Optional: + +- `profile_name` (String) Profile name for the deployment platform. + + + + +### Nested Schema for `deprecation` + +Optional: + +- `effective_date` (String) Effective date for after which revision will be deprecated +- `message` (String) Deprecation message + + + +### Nested Schema for `environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `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 `input_schemas` + +Optional: + +- `encoded_data` (String) JSON schema for the Form in templates. The schema needs to be base64 encoded. +- `type` (String) Type of the schema. +- `ui_schema_data` (String) Schema for how the JSON schema is to be visualized. The schema needs to be base64 encoded. + + + +### Nested Schema for `mini_steps` + +Optional: + +- `notifications` (Attributes) Configuration for notifications to be sent on workflow completion (see [below for nested schema](#nestedatt--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--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--mini_steps--wf_chaining)) + + +### Nested Schema for `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--mini_steps--notifications--email)) + + +### Nested Schema for `mini_steps.notifications.email` + +Optional: + +- `approval_required` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--notifications--email--approval_required)) +- `cancelled` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--notifications--email--cancelled)) +- `completed` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--notifications--email--completed)) +- `drift_detected` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--notifications--email--drift_detected)) +- `errored` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--notifications--email--errored)) + + +### Nested Schema for `mini_steps.notifications.email.approval_required` + +Optional: + +- `recipients` (List of String) List of emails + + + +### Nested Schema for `mini_steps.notifications.email.cancelled` + +Optional: + +- `recipients` (List of String) List of emails + + + +### Nested Schema for `mini_steps.notifications.email.completed` + +Optional: + +- `recipients` (List of String) List of emails + + + +### Nested Schema for `mini_steps.notifications.email.drift_detected` + +Optional: + +- `recipients` (List of String) List of emails + + + +### Nested Schema for `mini_steps.notifications.email.errored` + +Optional: + +- `recipients` (List of String) List of emails + + + + + +### Nested Schema for `mini_steps.webhooks` + +Optional: + +- `approval_required` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--webhooks--approval_required)) +- `cancelled` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--webhooks--cancelled)) +- `completed` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--webhooks--completed)) +- `drift_detected` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--webhooks--drift_detected)) +- `errored` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--webhooks--errored)) + + +### Nested Schema for `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 `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 `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 `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 `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 `mini_steps.wf_chaining` + +Optional: + +- `completed` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--wf_chaining--completed)) +- `errored` (Attributes List) (see [below for nested schema](#nestedatt--mini_steps--wf_chaining--errored)) + + +### Nested Schema for `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 `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 `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 `runtime_source` + +Optional: + +- `config` (Attributes) Configuration for the runtime environment. (see [below for nested schema](#nestedatt--runtime_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 `runtime_source.config` + +Required: + +- `repo` (String) Git repository URL. + +Optional: + +- `auth` (String, Sensitive) Connector id to access private git repository +- `git_core_auto_crlf` (Boolean) Whether to automatically handle CRLF line endings. +- `git_sparse_checkout_config` (String) Git sparse checkout command line git cli options. +- `include_sub_module` (Boolean) Whether to include git submodules. +- `is_private` (Boolean) Whether the repository is private. Auth is required if the repository is private +- `ref` (String) Git reference (branch, tag, or commit hash). +- `working_dir` (String) Working directory within the repository. + + + + +### Nested Schema for `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--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--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--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--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--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 `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--terraform_config--post_apply_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--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--terraform_config--post_apply_wf_steps_config--wf_step_input_data)) + + +### Nested Schema for `terraform_config.post_apply_wf_steps_config.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--terraform_config--post_apply_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `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 `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 `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 `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--terraform_config--post_plan_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--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--terraform_config--post_plan_wf_steps_config--wf_step_input_data)) + + +### Nested Schema for `terraform_config.post_plan_wf_steps_config.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--terraform_config--post_plan_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `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 `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 `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 `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--terraform_config--pre_apply_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--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--terraform_config--pre_apply_wf_steps_config--wf_step_input_data)) + + +### Nested Schema for `terraform_config.pre_apply_wf_steps_config.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--terraform_config--pre_apply_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `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 `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 `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 `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--terraform_config--pre_plan_wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--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--terraform_config--pre_plan_wf_steps_config--wf_step_input_data)) + + +### Nested Schema for `terraform_config.pre_plan_wf_steps_config.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--terraform_config--pre_plan_wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `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 `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 `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 `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 `user_schedules` + +Required: + +- `cron` (String) Cron expression defining the schedule. + +Optional: + +- `desc` (String) Description of the schedule. +- `name` (String) Name of the schedule. +- `state` (String) State of the schedule. Options: ENABLED, DISABLED + + + +### Nested Schema for `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--wf_steps_config--environment_variables)) +- `mount_points` (Attributes List) Mount points for the step. (see [below for nested schema](#nestedatt--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--wf_steps_config--wf_step_input_data)) + + +### Nested Schema for `wf_steps_config.environment_variables` + +Required: + +- `config` (Attributes) Configuration for the environment variable. (see [below for nested schema](#nestedatt--wf_steps_config--environment_variables--config)) +- `kind` (String) Kind of the environment variable. Options: TEXT, SECRET_REF + + +### Nested Schema for `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 `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 `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_workflow_template_revision.example + id = "template-name:revision-version" +} +``` + +### Using CLI +```bash +terraform import stackguardian_workflow_template_revision.example template-name:revision-version +``` diff --git a/go.mod b/go.mod index 59ac2b4..d0367c2 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/StackGuardian/terraform-provider-stackguardian go 1.21.4 +replace github.com/StackGuardian/sg-sdk-go v1.2.1 => "../../sg-sdk-go.git/feat-workflow-templates" + require ( github.com/StackGuardian/sg-sdk-go v1.2.1 github.com/hashicorp/terraform-plugin-framework v1.11.0 diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index dcaa379..de64919 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -1,6 +1,7 @@ package acctest import ( + "net/http" "os" "testing" @@ -11,9 +12,9 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" ) -func ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) { +func ProviderFactories(customHeader http.Header) map[string]func() (tfprotov6.ProviderServer, error) { return map[string]func() (tfprotov6.ProviderServer, error){ - "stackguardian": providerserver.NewProtocol6WithError(stackguardianprovider.New("")()), + "stackguardian": providerserver.NewProtocol6WithError(stackguardianprovider.New("", customHeader)()), } } diff --git a/internal/constants/template.go b/internal/constants/template.go new file mode 100644 index 0000000..aeb1e16 --- /dev/null +++ b/internal/constants/template.go @@ -0,0 +1,165 @@ +package constants + +// common template attributes +const ( + TemplateRevisionAlias string = "Alias for the template revision" + TemplateRevisionNotes string = "Notes for the revision" + TemplateRevisionIsPublic string = `Whether a revision is published to be used. Options: "1", "0"` + TemplateRevisionDeprecation string = "Marking a template revision for deprecation" + TemplateRevisionDeprecationEffectiveDate string = "Effective date for after which revision will be deprecated" + TemplateRevisionDeprecationMessage string = "Deprecation message" + DeprecationMessage string = "Deprecation message" +) + +// Common attributes shared between workflow template and revision +const ( + SourceConfigKind string = `Source configuration kind. Options: TERRAFORM, OPENTOFU, ANSIBLE_PLAYBOOK, HELM, KUBECTL, CLOUDFORMATION, CUSTOM` + ContextTags string = "Context tags for %s" +) + +// Workflow Template attributes +const ( + WorkflowTemplateName = "Name of the workflow template." + WorkflowTemplateOwnerOrg = "Organization the template belongs to" + WorkflowTemplateIsPublic = `Make template available to other organisations. Available values: "0" or "1"` + WorkflowTemplateSharedOrgs = "List of organizations the template is shared with." +) + +// Workflow Template Revision attributes +const ( + WorkflowTemplateRevisionTemplateId = "Resource ID of the parent workflow template." + WorkflowTemplateRevisionApprovers = "List of approvers for approvals during workflow execution." + WorkflowTemplateRevisionNumberOfApprovals = "Number of approvals required." + WorkflowTemplateRevisionUserJobCPU = "Limits to set user job CPU." + WorkflowTemplateRevisionUserJobMemory = "Limits to set user job memory." + WorkflowTemplateRevisionEnvironmentVariables = "List of environment variables for the revision." + WorkflowTemplateRevisionInputSchemas = "JSONSchema Form representation of input JSON data" + WorkflowTemplateRevisionMiniSteps = "Actions that are required to be performed once workflow execution is complete" + WorkflowTemplateRevisionUserSchedules = "Configuration for scheduling runs for the workflows." + WorkflowTemplateRevisionDeploymentPlatformConfig = "Deployment platform configuration for the revision." + WorkflowTemplateRevisionWfStepsConfig = "Workflow steps configuration. Valid for custom workflow types." +) + +// Runtime Source attributes (shared) +const ( + RuntimeSource = "Runtime source configuration for the %s." + RuntimeSourceDestKind = `Destination kind for the source configuration. Options: GITHUB_COM, GITHUB_APP_CUSTOM, GITLAB_OAUTH_SSH, GITLAB_COM, AZURE_DEVOPS` + RuntimeSourceConfig = "Configuration for the runtime environment." + RuntimeSourceConfigAuth = "Connector id to access private git repository" + RuntimeSourceConfigGitCoreCRLF = "Whether to automatically handle CRLF line endings." + RuntimeSourceConfigGitSparse = "Git sparse checkout command line git cli options." + RuntimeSourceConfigIncludeSubmodule = "Whether to include git submodules." + RuntimeSourceConfigIsPrivate = "Whether the repository is private. Auth is required if the repository is private" + RuntimeSourceConfigRef = "Git reference (branch, tag, or commit hash)." + RuntimeSourceConfigRepo = "Git repository URL." + RuntimeSourceConfigWorkingDir = "Working directory within the repository." +) + +// VCS Triggers attributes +const ( + VCSTriggers = "VCS trigger configuration for the workflow." + VCSTriggersType = `VCS provider type. Options: GITHUB_COM, GITHUB_APP_CUSTOM, GITLAB_OAUTH_SSH, GITLAB_COM` + VCSTriggersCreateTag = "Trigger configuration on tag creation in VCS" + VCSTriggersCreateTagRevision = "Create new revision on tag creation" + VCSTriggersCreateTagRevisionEnabled = "Whether to create revision when tag is created." +) + +// Environment Variables attributes +const ( + EnvVarConfig = "Configuration for the environment variable." + EnvVarConfigVarName = "Name of the variable." + EnvVarConfigSecretId = `ID of the secret (if using vault secret). Only if type is SECRET_REF` + EnvVarConfigTextValue = `Text value (if using plain text). Only if type is TEXT` + EnvVarKind = `Kind of the environment variable. Options: TEXT, SECRET_REF` +) + +// Input Schemas attributes +const ( + InputSchemaType = "Type of the schema." + InputSchemaEncodedData = "JSON schema for the Form in templates. The schema needs to be base64 encoded." + InputSchemaUISchemaData = "Schema for how the JSON schema is to be visualized. The schema needs to be base64 encoded." +) + +// Mini Steps attributes +const ( + MiniStepsNotifications = "Configuration for notifications to be sent on workflow completion" + MiniStepsNotificationsEmail = `Configuration for email notifications to be sent on completion. Statuses on which notifications can be sent: approval_required, cancelled, completed, drift_detected, errored` + MiniStepsNotificationsRecipients = "List of emails" + MiniStepsWebhooks = `Configuration for webhooks to be triggered on completion. Statuses on which webhooks can be sent: approval_required, cancelled, completed, drift_detected, errored` + MiniStepsWebhookName = "Webhook name" + MiniStepsWebhookURL = "Webhook URL" + MiniStepsWebhookSecret = "Secret to be sent with API request to webhook url" + MiniStepsWorkflowChaining = `Configuration for other workflows to be triggered on completion. Statuses on which workflows can be chained: completed, errored` + MiniStepsWfChainingWorkflowGroupId = "Workflow group id for the workflow." + MiniStepsWfChainingStackId = "Stack id for the stack to be triggered." + MiniStepsWfChainingStackPayload = "JSON string specifying overrides for the stack to be triggered" + MiniStepsWfChainingWorkflowId = "Workflow id for the workflow to be triggered" + MiniStepsWfChainingWorkflowPayload = "JSON string specifying overrides for the workflow to be triggered" +) + +// Runner Constraints attributes +const ( + RunnerConstraintsType = `Type of runner. Valid options: shared or external` + RunnerConstraintsNames = "Id of the runner group" +) + +// User Schedules attributes +const ( + UserScheduleCron = "Cron expression defining the schedule." + UserScheduleState = `State of the schedule. Options: ENABLED, DISABLED` + UserScheduleDesc = "Description of the schedule." + UserScheduleName = "Name of the schedule." +) + +// Deployment Platform Config attributes +const ( + DeploymentPlatformKind = `Deployment platform kind. Options: AWS_STATIC, AWS_RBAC, AWS_OIDC, AZURE_STATIC, AZURE_OIDC, GCP_STATIC, GCP_OIDC` + DeploymentPlatformConfigDetails = "Deployment platform configuration details." + DeploymentPlatformIntegrationId = "Integration ID for the deployment platform." + DeploymentPlatformProfileName = "Profile name for the deployment platform." +) + +// Mount Point attributes +const ( + MountPointSource = "Source path for mount point." + MountPointTarget = "Target path for mount point." + MountPointReadOnly = "If the directory is to be mounted as read only or not" +) + +// Workflow Steps Config attributes +const ( + WfStepName = "Step name." + WfStepEnvVars = "Environment variables for the workflow steps." + WfStepApproval = "Enable approval for the workflow step." + WfStepTimeout = "Workflow step execution timeout in seconds." + WfStepCmdOverride = "Override command for the step." + WfStepMountPoints = "Mount points for the step." + WfStepTemplateId = "Workflow step template ID." + WfStepInputData = "Workflow step input data (JSON string)" + WfStepInputDataSchemaType = `Schema type for the input data. Options: FORM_JSONSCHEMA` + WfStepInputDataData = "Input data (JSON)." +) + +// Terraform Config attributes +const ( + TerraformConfig = "Terraform configuration. Valid only for terraform type template" + TerraformVersion = "Terraform version to use." + TerraformDriftCheck = "Enable drift check." + TerraformDriftCron = "Cron expression for drift check." + TerraformManagedState = "Enable stackguardian managed terraform state." + TerraformApprovalPreApply = "Require approval before apply." + TerraformPlanOptions = "Additional options for terraform plan." + TerraformInitOptions = "Additional options for terraform init." + TerraformBinPath = "Mount points for terraform binary." + TerraformTimeout = "Timeout for terraform operations in seconds." + TerraformPostApplyWfSteps = "Workflow steps configuration to run after apply." + TerraformPreApplyWfSteps = "Workflow steps configuration to run before apply." + TerraformPrePlanWfSteps = "Workflow steps configuration to run before plan." + TerraformPostPlanWfSteps = "Workflow steps configuration to run after plan." + TerraformPreInitHooks = "Hooks to run before init." + TerraformPrePlanHooks = "Hooks to run before plan." + TerraformPostPlanHooks = "Hooks to run after plan." + TerraformPreApplyHooks = "Hooks to run before apply." + TerraformPostApplyHooks = "Hooks to run after apply." + TerraformRunPreInitHooksOnDrift = "Run pre-init hooks on drift detection." +) diff --git a/internal/datasources/runner_group/datasource_test.go b/internal/datasources/runner_group/datasource_test.go index 2028b2c..7c15520 100644 --- a/internal/datasources/runner_group/datasource_test.go +++ b/internal/datasources/runner_group/datasource_test.go @@ -1,6 +1,7 @@ package runnergroupdatasource_test import ( + "net/http" "testing" "github.com/StackGuardian/terraform-provider-stackguardian/internal/acctest" @@ -14,7 +15,7 @@ func TestAccRunnerGroupDatasource(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: `data "stackguardian_runner_group" "example" { diff --git a/internal/datasources/workflow_template/datasource.go b/internal/datasources/workflow_template/datasource.go new file mode 100644 index 0000000..860d0dc --- /dev/null +++ b/internal/datasources/workflow_template/datasource.go @@ -0,0 +1,84 @@ +package workflowtemplate + +import ( + "context" + "fmt" + + sgclient "github.com/StackGuardian/sg-sdk-go/client" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/customTypes" + workflowtemplate "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_template" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +var ( + _ datasource.DataSource = &workflowTemplateDataSource{} + _ datasource.DataSourceWithConfigure = &workflowTemplateDataSource{} +) + +// NewDataSource is a helper function to simplify the provider implementation. +func NewDataSource() datasource.DataSource { + return &workflowTemplateDataSource{} +} + +type workflowTemplateDataSource struct { + client *sgclient.Client + orgName string +} + +func (d *workflowTemplateDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_workflow_template" +} + +func (d *workflowTemplateDataSource) 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 *workflowTemplateDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config workflowtemplate.WorkflowTemplateResourceModel + + 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.WorkflowTemplates.ReadWorkflowTemplate(ctx, d.orgName, templateID) + if err != nil { + resp.Diagnostics.AddError("Unable to read workflow template.", err.Error()) + return + } + + if readResp == nil { + resp.Diagnostics.AddError("Error reading workflow template", "API response is empty") + return + } + + model, diags := workflowtemplate.BuildAPIModelToWorkflowTemplateModel(&readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) +} diff --git a/internal/datasources/workflow_template/schema.go b/internal/datasources/workflow_template/schema.go new file mode 100644 index 0000000..588d08c --- /dev/null +++ b/internal/datasources/workflow_template/schema.go @@ -0,0 +1,122 @@ +package workflowtemplate + +import ( + "context" + "fmt" + + "github.com/StackGuardian/terraform-provider-stackguardian/internal/constants" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (d *workflowTemplateDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Use this data source to read a workflow template.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: constants.DatasourceId, + Required: true, + }, + "owner_org": schema.StringAttribute{ + MarkdownDescription: "Organization the template belongs to.", + Computed: true, + }, + "template_name": schema.StringAttribute{ + MarkdownDescription: "Name of the workflow template.", + Computed: true, + }, + "template_type": schema.StringAttribute{ + MarkdownDescription: "Type of the template.", + Computed: true, + }, + "source_config_kind": schema.StringAttribute{ + Computed: true, + }, + "is_public": schema.StringAttribute{ + MarkdownDescription: "Whether the template is public.", + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Description, "workflow template"), + Computed: true, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Tags, "workflow template"), + ElementType: types.StringType, + Computed: true, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: "Contextual tags to give context to your tags.", + ElementType: types.StringType, + Computed: true, + }, + "shared_orgs_list": schema.ListAttribute{ + MarkdownDescription: "List of organizations the template is shared with.", + ElementType: types.StringType, + Computed: true, + }, + "runtime_source": schema.SingleNestedAttribute{ + MarkdownDescription: "Runtime source configuration for the template.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "source_config_dest_kind": schema.StringAttribute{ + Computed: true, + }, + "config": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "auth": schema.StringAttribute{ + Computed: true, + Sensitive: true, + }, + "git_core_auto_crlf": schema.BoolAttribute{ + Computed: true, + }, + "git_sparse_checkout_config": schema.StringAttribute{ + Computed: true, + }, + "include_sub_module": schema.BoolAttribute{ + Computed: true, + }, + "is_private": schema.BoolAttribute{ + Computed: true, + }, + "ref": schema.StringAttribute{ + Computed: true, + }, + "repo": schema.StringAttribute{ + Computed: true, + }, + "working_dir": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + }, + "vcs_triggers": schema.SingleNestedAttribute{ + MarkdownDescription: "VCS trigger configuration for the template.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Computed: true, + }, + "create_tag": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "create_revision": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} diff --git a/internal/datasources/workflow_template_revision/datasource.go b/internal/datasources/workflow_template_revision/datasource.go new file mode 100644 index 0000000..a93bf27 --- /dev/null +++ b/internal/datasources/workflow_template_revision/datasource.go @@ -0,0 +1,89 @@ +package workflowtemplaterevision + +import ( + "context" + "fmt" + + sgclient "github.com/StackGuardian/sg-sdk-go/client" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/customTypes" + workflowtemplaterevision "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_template_revision" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +var ( + _ datasource.DataSource = &workflowTemplateRevisionDataSource{} + _ datasource.DataSourceWithConfigure = &workflowTemplateRevisionDataSource{} +) + +// NewDataSource is a helper function to simplify the provider implementation. +func NewDataSource() datasource.DataSource { + return &workflowTemplateRevisionDataSource{} +} + +type workflowTemplateRevisionDataSource struct { + client *sgclient.Client + orgName string +} + +func (d *workflowTemplateRevisionDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_workflow_template_revision" +} + +func (d *workflowTemplateRevisionDataSource) 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 *workflowTemplateRevisionDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config workflowtemplaterevision.WorkflowTemplateRevisionResourceModel + + 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.WorkflowTemplatesRevisions.ReadWorkflowTemplateRevision(ctx, d.orgName, revisionID) + if err != nil { + resp.Diagnostics.AddError("Unable to read workflow template revision.", err.Error()) + return + } + + if readResp == nil { + resp.Diagnostics.AddError("Error reading workflow template revision", "API response is empty") + return + } + + model, diags := workflowtemplaterevision.BuildAPIModelToWorkflowTemplateRevisionModel(ctx, &readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Preserve template_id from config if provided + if !config.TemplateId.IsNull() && !config.TemplateId.IsUnknown() { + model.TemplateId = config.TemplateId + } + + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) +} diff --git a/internal/datasources/workflow_template_revision/schema.go b/internal/datasources/workflow_template_revision/schema.go new file mode 100644 index 0000000..cbac783 --- /dev/null +++ b/internal/datasources/workflow_template_revision/schema.go @@ -0,0 +1,440 @@ +package workflowtemplaterevision + +import ( + "context" + "fmt" + + "github.com/StackGuardian/terraform-provider-stackguardian/internal/constants" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ministepsNotificationRecepients = schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "recipients": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + }, + }, + }, +} + +var ministepsWebhooks = schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "webhook_name": schema.StringAttribute{ + Computed: true, + }, + "webhook_url": schema.StringAttribute{ + Computed: true, + }, + "webhook_secret": schema.StringAttribute{ + Computed: true, + }, + }, + }, +} + +var ministepsWorkflowChaining = schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "workflow_group_id": schema.StringAttribute{ + Computed: true, + }, + "stack_id": schema.StringAttribute{ + Computed: true, + }, + "stack_run_payload": schema.StringAttribute{ + Computed: true, + }, + "workflow_id": schema.StringAttribute{ + Computed: true, + }, + "workflow_run_payload": schema.StringAttribute{ + Computed: true, + }, + }, + }, +} + +var miniStepsSchema = schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "notifications": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "email": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "approval_required": ministepsNotificationRecepients, + "cancelled": ministepsNotificationRecepients, + "completed": ministepsNotificationRecepients, + "drift_detected": ministepsNotificationRecepients, + "errored": ministepsNotificationRecepients, + }, + }, + }, + }, + "webhooks": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "approval_required": ministepsWebhooks, + "cancelled": ministepsWebhooks, + "completed": ministepsWebhooks, + "drift_detected": ministepsWebhooks, + "errored": ministepsWebhooks, + }, + }, + "wf_chaining": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "completed": ministepsWorkflowChaining, + "errored": ministepsWorkflowChaining, + }, + }, + }, +} + +var environmentVariablesSchema = schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "config": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "var_name": schema.StringAttribute{ + Computed: true, + }, + "secret_id": schema.StringAttribute{ + Computed: true, + }, + "text_value": schema.StringAttribute{ + Computed: true, + }, + }, + }, + "kind": schema.StringAttribute{ + Computed: true, + }, + }, + }, +} + +var mountPointsSchema = schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "source": schema.StringAttribute{ + Computed: true, + }, + "target": schema.StringAttribute{ + Computed: true, + }, + "read_only": schema.BoolAttribute{ + Computed: true, + }, + }, + }, +} + +var wfStepsConfigSchema = schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Computed: true, + }, + "wf_step_template_id": schema.StringAttribute{ + Computed: true, + }, + "timeout": schema.Int64Attribute{ + Computed: true, + }, + "approval": schema.BoolAttribute{ + Computed: true, + }, + "cmd_override": schema.StringAttribute{ + Computed: true, + }, + "environment_variables": environmentVariablesSchema, + "mount_points": mountPointsSchema, + "wf_step_input_data": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "schema_type": schema.StringAttribute{ + Computed: true, + }, + "data": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, +} + +var terraformConfigSchema = schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "terraform_version": schema.StringAttribute{ + Computed: true, + }, + "terraform_plan_options": schema.StringAttribute{ + Computed: true, + }, + "terraform_init_options": schema.StringAttribute{ + Computed: true, + }, + "terraform_bin_path": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "source": schema.StringAttribute{ + Computed: true, + }, + "target": schema.StringAttribute{ + Computed: true, + }, + "read_only": schema.BoolAttribute{ + Computed: true, + }, + }, + }, + }, + "timeout": schema.Int64Attribute{ + Computed: true, + }, + "managed_terraform_state": schema.BoolAttribute{ + Computed: true, + }, + "drift_check": schema.BoolAttribute{ + Computed: true, + }, + "drift_cron": schema.StringAttribute{ + Computed: true, + }, + "approval_pre_apply": schema.BoolAttribute{ + Computed: true, + }, + "run_pre_init_hooks_on_drift": schema.BoolAttribute{ + Computed: true, + }, + "pre_init_hooks": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + }, + "pre_plan_hooks": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + }, + "post_plan_hooks": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + }, + "pre_apply_hooks": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + }, + "post_apply_hooks": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + }, + "post_apply_wf_steps_config": schema.ListNestedAttribute{ + Computed: true, + NestedObject: wfStepsConfigSchema, + }, + "pre_apply_wf_steps_config": schema.ListNestedAttribute{ + Computed: true, + NestedObject: wfStepsConfigSchema, + }, + "pre_plan_wf_steps_config": schema.ListNestedAttribute{ + Computed: true, + NestedObject: wfStepsConfigSchema, + }, + "post_plan_wf_steps_config": schema.ListNestedAttribute{ + Computed: true, + NestedObject: wfStepsConfigSchema, + }, + }, +} + +var runtimeSourceSchema = schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "source_config_dest_kind": schema.StringAttribute{ + Computed: true, + }, + "config": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "is_private": schema.BoolAttribute{ + Computed: true, + }, + "auth": schema.StringAttribute{ + Computed: true, + }, + "git_core_auto_crlf": schema.BoolAttribute{ + Computed: true, + }, + "git_sparse_checkout_config": schema.StringAttribute{ + Computed: true, + }, + "include_sub_module": schema.BoolAttribute{ + Computed: true, + }, + "ref": schema.StringAttribute{ + Computed: true, + }, + "repo": schema.StringAttribute{ + Computed: true, + }, + "working_dir": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, +} + +func (d *workflowTemplateRevisionDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Use this data source to read a workflow template revision.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: constants.DatasourceId, + Required: true, + }, + "template_id": schema.StringAttribute{ + MarkdownDescription: "Resource ID of the parent workflow template.", + Optional: true, + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Description, "workflow template revision"), + Computed: true, + }, + "alias": schema.StringAttribute{ + Computed: true, + }, + "notes": schema.StringAttribute{ + Computed: true, + }, + "source_config_kind": schema.StringAttribute{ + Computed: true, + }, + "is_public": schema.StringAttribute{ + Computed: true, + }, + "deprecation": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "effective_date": schema.StringAttribute{ + Computed: true, + }, + "message": schema.StringAttribute{ + Computed: true, + }, + }, + }, + "environment_variables": environmentVariablesSchema, + "input_schemas": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Computed: true, + }, + "encoded_data": schema.StringAttribute{ + Computed: true, + }, + "ui_schema_data": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + "mini_steps": miniStepsSchema, + "runner_constraints": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Computed: true, + }, + "names": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + }, + }, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Tags, "workflow template revision"), + ElementType: types.StringType, + Computed: true, + }, + "user_schedules": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "cron": schema.StringAttribute{ + Computed: true, + }, + "state": schema.StringAttribute{ + Computed: true, + }, + "desc": schema.StringAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: "Context tags for the revision.", + ElementType: types.StringType, + Computed: true, + }, + "approvers": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + }, + "number_of_approvals_required": schema.Int64Attribute{ + Computed: true, + }, + "user_job_cpu": schema.Int64Attribute{ + Computed: true, + }, + "user_job_memory": schema.Int64Attribute{ + Computed: true, + }, + "runtime_source": runtimeSourceSchema, + "terraform_config": terraformConfigSchema, + "deployment_platform_config": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "kind": schema.StringAttribute{ + Computed: true, + }, + "config": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "integration_id": schema.StringAttribute{ + Computed: true, + }, + "profile_name": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + }, + "wf_steps_config": schema.ListNestedAttribute{ + Computed: true, + NestedObject: wfStepsConfigSchema, + }, + }, + } +} diff --git a/internal/expanders/maps.go b/internal/expanders/maps.go new file mode 100644 index 0000000..f8f35b2 --- /dev/null +++ b/internal/expanders/maps.go @@ -0,0 +1,22 @@ +package expanders + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func MapStringString(ctx context.Context, input types.Map) (map[string]string, diag.Diagnostics) { + if input.IsNull() || input.IsUnknown() { + return nil, nil + } + + result := make(map[string]string) + diags := input.ElementsAs(ctx, &result, false) + if diags.HasError() { + return nil, diags + } + + return result, nil +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index bb55652..d0ef2bc 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -3,6 +3,7 @@ package sgprovider import ( "context" "fmt" + "net/http" "os" sgclient "github.com/StackGuardian/sg-sdk-go/client" @@ -18,6 +19,8 @@ import ( 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" + workflowtemplatedatasource "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/workflow_template" + workflowtemplaterevisiondatasource "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/workflow_template_revision" "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/connector" "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/policy" "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/role" @@ -25,6 +28,8 @@ import ( rolev4 "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/role_v4" runnergroup "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/runner_group" workflowgroup "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_group" + workflowtemplate "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_template" + workflowtemplaterevision "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_template_revision" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/provider" @@ -40,10 +45,11 @@ var ( ) // New is a helper function to simplify provider server and testing implementation. -func New(version string) func() provider.Provider { +func New(version string, customHeader http.Header) func() provider.Provider { return func() provider.Provider { return &stackguardianProvider{ - version: version, + version: version, + customHeaders: customHeader, } } } @@ -53,7 +59,8 @@ type stackguardianProvider struct { // version is set to the provider version on release, "dev" when the // provider is built and ran locally, and "test" when running acceptance // testing. - version string + version string + customHeaders http.Header } type stackguardianProviderModel struct { @@ -176,6 +183,7 @@ func (p *stackguardianProvider) Configure(ctx context.Context, req provider.Conf client := sgclient.NewClient( sgoption.WithApiKey(api_key), sgoption.WithBaseURL(api_uri), + sgoption.WithHTTPHeader(p.customHeaders), ) //Set the values in our struct provInfo := customTypes.ProviderInfo{ @@ -212,6 +220,8 @@ func (p *stackguardianProvider) DataSources(_ context.Context) []func() datasour policydatasource.NewDataSource, runnergroupdatasource.NewDataSource, runnergrouptoken.NewDataSource, + workflowtemplatedatasource.NewDataSource, + workflowtemplaterevisiondatasource.NewDataSource, } } @@ -225,5 +235,7 @@ func (p *stackguardianProvider) Resources(_ context.Context) []func() resource.R policy.NewResource, runnergroup.NewResource, rolev4.NewResource, + workflowtemplate.NewResource, + workflowtemplaterevision.NewResource, } } diff --git a/internal/resource/connector/resource_test.go b/internal/resource/connector/resource_test.go index 12e33a9..785e2d0 100644 --- a/internal/resource/connector/resource_test.go +++ b/internal/resource/connector/resource_test.go @@ -1,6 +1,7 @@ package connector_test import ( + "net/http" "testing" "github.com/StackGuardian/terraform-provider-stackguardian/internal/acctest" @@ -49,7 +50,7 @@ func TestAccConnector(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: testAccResource, @@ -104,7 +105,7 @@ func TestAccConnectorIncompatibleResourceName(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: testResource, @@ -154,7 +155,7 @@ func TestAccConnectorOptionalId(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: testResource, diff --git a/internal/resource/policy/resource_test.go b/internal/resource/policy/resource_test.go index 0f79996..ee362b1 100644 --- a/internal/resource/policy/resource_test.go +++ b/internal/resource/policy/resource_test.go @@ -3,6 +3,7 @@ package policy_test import ( "context" "fmt" + "net/http" "os" "testing" @@ -137,7 +138,7 @@ func TestAccPolicy(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, resourceName, policyName), @@ -158,7 +159,7 @@ func TestAccPolicyRecreateOnExternalDelete(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, resourceName, policyName), @@ -201,7 +202,7 @@ func TestAccPolicyOptionalId(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: testResource, diff --git a/internal/resource/role/resource_test.go b/internal/resource/role/resource_test.go index 30c2638..fb868cf 100644 --- a/internal/resource/role/resource_test.go +++ b/internal/resource/role/resource_test.go @@ -3,6 +3,7 @@ package role_test import ( "context" "fmt" + "net/http" "os" "testing" @@ -86,7 +87,7 @@ func TestAccRole(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, workflowGroupResourceName, workflowGroupName, roleResourceName, roleName, workflowGroupName), @@ -109,7 +110,7 @@ func TestAccRoleRecreateOnExternalDelete(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, workflowGroupResourceName, workflowGroupName, roleResourceName, roleName, workflowGroupName), @@ -160,7 +161,7 @@ resource "stackguardian_role" "%s" { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testResource, roleResourceName, roleName), @@ -213,7 +214,7 @@ resource "stackguardian_role" "role-example-role4" { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: testResource, @@ -239,3 +240,29 @@ resource "stackguardian_role" "role-example-role4" { }, }) } + +func TestAccRoleWithoutAllowedPermissions(t *testing.T) { + roleResourceName := "role-example-role5" + roleName := "role-example-role5" + + testResource := ` +resource "stackguardian_role" "%s" { + resource_name = "%s" + description = "Example of terraform-provider-stackguardian for a Role without allowed permissions" + tags = [ + "example-org", + ] +}` + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), + }, + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(testResource, roleResourceName, roleName), + }, + }, + }) +} diff --git a/internal/resource/role_assignment/resource_test.go b/internal/resource/role_assignment/resource_test.go index cba3ac9..44ad200 100644 --- a/internal/resource/role_assignment/resource_test.go +++ b/internal/resource/role_assignment/resource_test.go @@ -3,6 +3,7 @@ package roleassignment_test import ( "context" "fmt" + "net/http" "os" "testing" @@ -101,7 +102,7 @@ func TestAccRoleAssignment(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, workflowGroupResourceName, workflowGroupName, roleResourceName, roleName, workflowGroupName, roleAssignmentName, userId, roleName), @@ -126,7 +127,7 @@ func TestAccRoleAssignmentRecreateOnExternalDelete(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, workflowGroupResourceName, workflowGroupName, roleResourceName, roleName, workflowGroupName, roleAssignmentName, userId, roleName), @@ -164,7 +165,7 @@ func TestAccRoleAssignmentRecreateOnChangeInUserId(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, workflowGroupResourceName, workflowGroupName, roleResourceName, roleName, workflowGroupName, roleAssignmentName, userId, roleName), @@ -230,7 +231,7 @@ resource "stackguardian_role_assignment" "%s" { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testResource, workflowGroupResourceName, workflowGroupName, roleResourceName, roleName, workflowGroupName, roleAssignmentName, userId, roleName, "true"), @@ -258,7 +259,7 @@ func TestRoleAssignmentGroupAlias(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, roleAssignmentName, userId, alias), @@ -293,7 +294,7 @@ func TestRoleAssignmentMultipleRoles(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, roleAssignmentName, userId), @@ -329,7 +330,7 @@ func TestRoleAssignmentRoleToRoles(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, roleAssignmentName, userId), @@ -365,7 +366,7 @@ func TestRoleAssignmentRolesToRole(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, roleAssignmentName, userId), diff --git a/internal/resource/role_v4/resource_test.go b/internal/resource/role_v4/resource_test.go index 7d636ad..c04d800 100644 --- a/internal/resource/role_v4/resource_test.go +++ b/internal/resource/role_v4/resource_test.go @@ -3,6 +3,7 @@ package rolev4_test import ( "context" "fmt" + "net/http" "os" "testing" @@ -86,7 +87,7 @@ func TestAccRole(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, workflowGroupResourceName, workflowGroupName, roleResourceName, roleName, workflowGroupName), @@ -109,7 +110,7 @@ func TestAccRoleRecreateOnExternalDelete(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, workflowGroupResourceName, workflowGroupName, roleResourceName, roleName, workflowGroupName), @@ -160,7 +161,7 @@ resource "stackguardian_rolev4" "%s" { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testResource, roleResourceName, roleName), @@ -181,7 +182,7 @@ func TestAccRoleV4IncompatibleResourceName(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, workflowGroupName, workflowGroupResourceName, roleName, roleResourceName, workflowGroupName), @@ -237,7 +238,7 @@ resource "stackguardian_rolev4" "rolev4-example-role5" { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: testResource, diff --git a/internal/resource/runner_group/resource_test.go b/internal/resource/runner_group/resource_test.go index 36b5090..2d414d1 100644 --- a/internal/resource/runner_group/resource_test.go +++ b/internal/resource/runner_group/resource_test.go @@ -3,6 +3,7 @@ package runnergroup_test import ( "context" "fmt" + "net/http" "os" "testing" @@ -69,7 +70,7 @@ func TestAccRunnerGroupAWSS3(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, runnerGroupResourceName, runnerGroupName), @@ -87,7 +88,7 @@ func TestAccRunnerGroupAzureBlobStorage(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(`resource "stackguardian_runner_group" "example-runner-group2" { @@ -124,7 +125,7 @@ func TestAccRunnerGroupRecreateOnExternalDelete(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, runnerGroupResourceName, runnerGroupName), @@ -176,7 +177,7 @@ func TestAccConnectorOptionalId(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testResource, azureStorageBackendAccessKey), diff --git a/internal/resource/runner_group/schema.go b/internal/resource/runner_group/schema.go index 0d72d8a..d44769f 100644 --- a/internal/resource/runner_group/schema.go +++ b/internal/resource/runner_group/schema.go @@ -91,7 +91,6 @@ func (r *runnerGroupResource) Schema(_ context.Context, _ resource.SchemaRequest MarkdownDescription: constants.DiscoverySettingsBenchmarksRuntimeSource, Optional: true, Attributes: map[string]schema.Attribute{ - "source_config_dest_kind": schema.StringAttribute{ MarkdownDescription: constants.DiscoverySettingsBenchmarksRuntimeSourceSourceConfigDestKind, Optional: true, diff --git a/internal/resource/workflow_group/resource_test.go b/internal/resource/workflow_group/resource_test.go index 14d82af..148b3f6 100644 --- a/internal/resource/workflow_group/resource_test.go +++ b/internal/resource/workflow_group/resource_test.go @@ -3,6 +3,7 @@ package workflowgroup_test import ( "context" "fmt" + "net/http" "os" "testing" @@ -36,7 +37,7 @@ func TestAccWorkflowGroup(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, workflowGroupResrouceName, workflowGroupName), @@ -57,7 +58,7 @@ func TestAccWorkflowGroupRecreateOnExternalDelete(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, workflowGroupResourceName, workflowGroupName), @@ -92,7 +93,7 @@ func TestAccWorkflowGroupIncompatibleResourceName(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: fmt.Sprintf(testAccResource, workflowGroupName, workflowGroupResourceName), @@ -123,7 +124,7 @@ func TestAccWorkflowGroupOptionalId(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_1_0), }, - ProtoV6ProviderFactories: acctest.ProviderFactories(), + ProtoV6ProviderFactories: acctest.ProviderFactories(http.Header{}), Steps: []resource.TestStep{ { Config: testResource, diff --git a/internal/resource/workflow_template/model.go b/internal/resource/workflow_template/model.go new file mode 100644 index 0000000..134d508 --- /dev/null +++ b/internal/resource/workflow_template/model.go @@ -0,0 +1,516 @@ +package workflowtemplate + +import ( + "context" + + sgsdkgo "github.com/StackGuardian/sg-sdk-go" + "github.com/StackGuardian/sg-sdk-go/workflowtemplates" + "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" +) + +type WorkflowTemplateResourceModel 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"` + IsPublic types.String `tfsdk:"is_public"` + ShortDescription types.String `tfsdk:"description"` + RuntimeSource types.Object `tfsdk:"runtime_source"` + SharedOrgsList types.List `tfsdk:"shared_orgs_list"` + Tags types.List `tfsdk:"tags"` + ContextTags types.Map `tfsdk:"context_tags"` + VCSTriggers types.Object `tfsdk:"vcs_triggers"` +} + +type RuntimeSourceModel struct { + SourceConfigDestKind types.String `tfsdk:"source_config_dest_kind"` + Config types.Object `tfsdk:"config"` +} + +func (RuntimeSourceModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "source_config_dest_kind": types.StringType, + "config": types.ObjectType{ + AttrTypes: RuntimeSourceConfigModel{}.AttributeTypes(), + }, + } +} + +type RuntimeSourceConfigModel struct { + IsPrivate types.Bool `tfsdk:"is_private"` + Auth types.String `tfsdk:"auth"` + GitCoreAutoCrlf types.Bool `tfsdk:"git_core_auto_crlf"` + GitSparseCheckoutConfig types.String `tfsdk:"git_sparse_checkout_config"` + IncludeSubModule types.Bool `tfsdk:"include_sub_module"` + Ref types.String `tfsdk:"ref"` + Repo types.String `tfsdk:"repo"` + WorkingDir types.String `tfsdk:"working_dir"` +} + +func (RuntimeSourceConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "is_private": types.BoolType, + "auth": types.StringType, + "git_core_auto_crlf": types.BoolType, + "git_sparse_checkout_config": types.StringType, + "include_sub_module": types.BoolType, + "ref": types.StringType, + "repo": types.StringType, + "working_dir": types.StringType, + } +} + +type VCSTriggersModel struct { + Type types.String `tfsdk:"type"` + CreateTag types.Object `tfsdk:"create_tag"` +} + +type VCSTriggersCreateRevisionModel struct { + Enabled types.Bool `tfsdk:"enabled"` +} + +func (VCSTriggersCreateRevisionModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "enabled": types.BoolType, + } +} + +type VCSTriggersCreateTagModel struct { + CreateRevision types.Object `tfsdk:"create_revision"` +} + +func (VCSTriggersCreateTagModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "create_revision": types.ObjectType{AttrTypes: VCSTriggersCreateRevisionModel{}.AttributeTypes()}, + } +} + +func (VCSTriggersModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "type": types.StringType, + "create_tag": types.ObjectType{ + AttrTypes: VCSTriggersCreateTagModel{}.AttributeTypes(), + }, + } +} + +func VCSTriggersToAPIModel(ctx context.Context, m types.Object) (*workflowtemplates.VCSTriggers, diag.Diagnostics) { + var vcsTriggersModel VCSTriggersModel + if m.IsNull() || m.IsUnknown() { + return nil, nil + } + + diags := m.As(ctx, &vcsTriggersModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + + vcsTriggers := &workflowtemplates.VCSTriggers{ + Type: workflowtemplates.VCSTriggersTypeEnum(vcsTriggersModel.Type.ValueString()).Ptr(), + } + + // Convert create_tag + if !vcsTriggersModel.CreateTag.IsNull() && !vcsTriggersModel.CreateTag.IsUnknown() { + var createTagModel VCSTriggersCreateTagModel + createTagAPIModel := workflowtemplates.VCSTriggersCreateTag{} + diags := vcsTriggersModel.CreateTag.As(ctx, &createTagModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + + if !createTagModel.CreateRevision.IsNull() && !createTagModel.CreateRevision.IsUnknown() { + var createRevision VCSTriggersCreateRevisionModel + diags := createTagModel.CreateRevision.As(ctx, &createRevision, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}) + if diags.HasError() { + return nil, diags + } + + vcsCreateRevisionAPIModel := workflowtemplates.VCSTriggersCreateTagCreateRevision{ + Enabled: createRevision.Enabled.ValueBoolPointer(), + } + createTagAPIModel.CreateRevision = &vcsCreateRevisionAPIModel + } + vcsTriggers.CreateTag = &createTagAPIModel + } + + return vcsTriggers, nil +} + +func (m RuntimeSourceModel) ToAPIModel(ctx context.Context) (*workflowtemplates.RuntimeSource, diag.Diagnostics) { + runtimeSource := &workflowtemplates.RuntimeSource{ + SourceConfigDestKind: workflowtemplates.SourceConfigDestKindEnum(m.SourceConfigDestKind.ValueString()).Ptr(), + } + + // Convert config + if !m.Config.IsNull() && !m.Config.IsUnknown() { + var configModel RuntimeSourceConfigModel + diag_cfg := m.Config.As(ctx, &configModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diag_cfg.HasError() { + return nil, diag_cfg + } + + runtimeSource.Config = &workflowtemplates.RuntimeSourceConfig{ + IsPrivate: configModel.IsPrivate.ValueBoolPointer(), + Auth: configModel.Auth.ValueStringPointer(), + GitCoreAutoCRLF: configModel.GitCoreAutoCrlf.ValueBoolPointer(), + GitSparseCheckoutConfig: configModel.GitSparseCheckoutConfig.ValueStringPointer(), + IncludeSubModule: configModel.IncludeSubModule.ValueBoolPointer(), + Ref: configModel.Ref.ValueStringPointer(), + Repo: configModel.Repo.ValueString(), + WorkingDir: configModel.WorkingDir.ValueStringPointer(), + } + } + return runtimeSource, nil +} + +func (m *WorkflowTemplateResourceModel) ToAPIModel(ctx context.Context) (*workflowtemplates.CreateWorkflowTemplateRequest, diag.Diagnostics) { + diag := diag.Diagnostics{} + + apiModel := &workflowtemplates.CreateWorkflowTemplateRequest{ + TemplateName: m.TemplateName.ValueString(), + OwnerOrg: m.OwnerOrg.ValueString(), + ShortDescription: m.ShortDescription.ValueStringPointer(), + } + + if !m.SourceConfigKind.IsNull() && !m.SourceConfigKind.IsUnknown() { + apiModel.SourceConfigKind = (*workflowtemplates.WorkflowTemplateSourceConfigKindEnum)(m.SourceConfigKind.ValueStringPointer()) + } + + if !m.IsPublic.IsNull() && !m.IsPublic.IsUnknown() { + apiModel.IsPublic = (*sgsdkgo.IsPublicEnum)(m.IsPublic.ValueStringPointer()) + } + + // Convert Tags from types.List to []string + if !m.Tags.IsNull() && !m.Tags.IsUnknown() { + tags, diags_tags := expanders.StringList(ctx, m.Tags) + diag.Append(diags_tags...) + if !diag.HasError() { + apiModel.Tags = tags + } + } + + // Convert SharedOrgsList + if !m.SharedOrgsList.IsNull() && !m.SharedOrgsList.IsUnknown() { + sharedOrgs, diags_shared := expanders.StringList(ctx, m.SharedOrgsList) + diag.Append(diags_shared...) + if !diag.HasError() { + apiModel.SharedOrgsList = sharedOrgs + } + } + + // Convert ContextTags from types.Map to map[string]string + if !m.ContextTags.IsNull() && !m.ContextTags.IsUnknown() { + contextTags := make(map[string]string) + diag_ct := m.ContextTags.ElementsAs(ctx, &contextTags, false) + diag.Append(diag_ct...) + if !diag.HasError() && len(contextTags) > 0 { + apiModel.ContextTags = contextTags + } + } + + // Convert RuntimeSource + if !m.RuntimeSource.IsNull() && !m.RuntimeSource.IsUnknown() { + var runtimeSourceModel RuntimeSourceModel + diags := m.RuntimeSource.As(ctx, &runtimeSourceModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + runtimeSourceApiModel, diags := runtimeSourceModel.ToAPIModel(ctx) + if diags.HasError() { + return nil, diags + } + apiModel.RuntimeSource = runtimeSourceApiModel + } + + // Convert VCSTriggers + vcsTriggersAPIModel, diags := VCSTriggersToAPIModel(ctx, m.VCSTriggers) + if diags.HasError() { + return nil, diags + } + apiModel.VCSTriggers = vcsTriggersAPIModel + + return apiModel, diag +} + +func (m *WorkflowTemplateResourceModel) ToUpdateAPIModel(ctx context.Context) (*workflowtemplates.UpdateWorkflowTemplateRequest, diag.Diagnostics) { + diag := diag.Diagnostics{} + + apiModel := &workflowtemplates.UpdateWorkflowTemplateRequest{ + TemplateName: sgsdkgo.Optional(m.TemplateName.ValueString()), + SourceConfigKind: sgsdkgo.Optional(workflowtemplates.WorkflowTemplateSourceConfigKindEnum(m.SourceConfigKind.ValueString())), + } + + if !m.ShortDescription.IsNull() && !m.ShortDescription.IsUnknown() { + apiModel.ShortDescription = sgsdkgo.Optional(m.ShortDescription.ValueString()) + } else { + apiModel.ShortDescription = sgsdkgo.Null[string]() + } + + if !m.IsPublic.IsNull() && !m.IsPublic.IsUnknown() { + apiModel.IsPublic = sgsdkgo.Optional(sgsdkgo.IsPublicEnum(m.IsPublic.ValueString())) + } + + // Convert Tags + tags, diags := expanders.StringList(ctx, m.Tags) + if diags.HasError() { + return nil, diags + } + if tags != nil { + apiModel.Tags = sgsdkgo.Optional(tags) + } else { + apiModel.Tags = sgsdkgo.Null[[]string]() + } + + // Convert ContextTags + contextTags, diags := expanders.MapStringString(ctx, m.ContextTags) + if contextTags != nil { + apiModel.ContextTags = sgsdkgo.Optional(contextTags) + } else { + apiModel.ContextTags = sgsdkgo.Null[map[string]string]() + } + + // Convert RuntimeSource + if !m.RuntimeSource.IsNull() && !m.RuntimeSource.IsUnknown() { + var runtimeSourceModel RuntimeSourceModel + diag_rt := m.RuntimeSource.As(ctx, &runtimeSourceModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diag_rt.HasError() { + return nil, diag_rt + } + + runtimeSource := &workflowtemplates.RuntimeSourceUpdate{ + SourceConfigDestKind: workflowtemplates.SourceConfigDestKindEnum(runtimeSourceModel.SourceConfigDestKind.ValueString()).Ptr(), + } + + // Convert config + if !runtimeSourceModel.Config.IsNull() && !runtimeSourceModel.Config.IsUnknown() { + var configModel RuntimeSourceConfigModel + diag_cfg := runtimeSourceModel.Config.As(ctx, &configModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diag_cfg.HasError() { + return nil, diag_cfg + } + + runtimeSource.Config = &workflowtemplates.RuntimeSourceConfigUpdate{ + IsPrivate: configModel.IsPrivate.ValueBoolPointer(), + GitCoreAutoCRLF: configModel.GitCoreAutoCrlf.ValueBoolPointer(), + GitSparseCheckoutConfig: configModel.GitSparseCheckoutConfig.ValueStringPointer(), + IncludeSubModule: configModel.IncludeSubModule.ValueBoolPointer(), + Ref: configModel.Ref.ValueStringPointer(), + WorkingDir: configModel.WorkingDir.ValueStringPointer(), + } + } + apiModel.RuntimeSource = sgsdkgo.Optional(*runtimeSource) + } else { + apiModel.RuntimeSource = sgsdkgo.Null[workflowtemplates.RuntimeSourceUpdate]() + } + + // convert SharedOrgsList + sharedOrgsList, diags := expanders.StringList(ctx, m.SharedOrgsList) + if diags.HasError() { + return nil, diags + } + if sharedOrgsList != nil { + apiModel.SharedOrgsList = sgsdkgo.Optional(sharedOrgsList) + } else { + apiModel.SharedOrgsList = sgsdkgo.Null[[]string]() + } + + // convert VCSTriggers + vcsTriggersAPIModel, diags := VCSTriggersToAPIModel(ctx, m.VCSTriggers) + if diags.HasError() { + return nil, diags + } + if vcsTriggersAPIModel != nil { + apiModel.VCSTriggers = sgsdkgo.Optional(*vcsTriggersAPIModel) + } else { + apiModel.VCSTriggers = sgsdkgo.Null[workflowtemplates.VCSTriggers]() + } + + return apiModel, diag +} + +func VCSTriggersToTerraType(vcsTriggers *workflowtemplates.VCSTriggers) (types.Object, diag.Diagnostics) { + nullObject := types.ObjectNull(VCSTriggersModel{}.AttributeTypes()) + if vcsTriggers == nil { + return nullObject, nil + } + + vcsTriggersModel := VCSTriggersModel{} + if vcsTriggers.Type != nil { + vcsTriggersModel.Type = flatteners.String(string(*vcsTriggers.Type)) + } else { + vcsTriggersModel.Type = types.StringNull() + } + + if vcsTriggers.CreateTag != nil { + createTagModel := VCSTriggersCreateTagModel{} + if vcsTriggers.CreateTag.CreateRevision != nil { + createRevisionModel := VCSTriggersCreateRevisionModel{} + if vcsTriggers.CreateTag.CreateRevision.Enabled != nil { + createRevisionModel.Enabled = flatteners.BoolPtr(vcsTriggers.CreateTag.CreateRevision.Enabled) + } else { + createRevisionModel.Enabled = types.BoolNull() + } + createRevisionTerraType, diags := types.ObjectValueFrom(context.TODO(), VCSTriggersCreateRevisionModel{}.AttributeTypes(), &createRevisionModel) + if diags.HasError() { + return nullObject, diags + } + + createTagModel = VCSTriggersCreateTagModel{ + CreateRevision: createRevisionTerraType, + } + } else { + createTagModel.CreateRevision = types.ObjectNull(VCSTriggersCreateRevisionModel{}.AttributeTypes()) + } + + createTagTerraType, diags := types.ObjectValueFrom(context.Background(), VCSTriggersCreateTagModel{}.AttributeTypes(), &createTagModel) + if diags.HasError() { + return nullObject, diags + } + + vcsTriggersModel.CreateTag = createTagTerraType + } else { + vcsTriggersModel.CreateTag = types.ObjectNull(VCSTriggersCreateTagModel{}.AttributeTypes()) + } + + vcsTriggersTerraType, diags := types.ObjectValueFrom(context.Background(), VCSTriggersModel{}.AttributeTypes(), vcsTriggersModel) + if diags.HasError() { + return nullObject, diags + } + + return vcsTriggersTerraType, nil +} + +func RuntimeSourceToTerraType(runtimeSource *workflowtemplates.RuntimeSource) (types.Object, diag.Diagnostics) { + nullObject := types.ObjectNull(RuntimeSourceModel{}.AttributeTypes()) + if runtimeSource == nil { + return nullObject, nil + } + + runtimeSourceModel := RuntimeSourceModel{} + + if runtimeSource.SourceConfigDestKind != nil { + runtimeSourceModel.SourceConfigDestKind = flatteners.String(string(*runtimeSource.SourceConfigDestKind)) + } else { + runtimeSourceModel.SourceConfigDestKind = types.StringNull() + } + + if runtimeSource.Config != nil { + configModel := &RuntimeSourceConfigModel{ + IsPrivate: flatteners.BoolPtr(runtimeSource.Config.IsPrivate), + Auth: flatteners.StringPtr(runtimeSource.Config.Auth), + GitCoreAutoCrlf: flatteners.BoolPtr(runtimeSource.Config.GitCoreAutoCRLF), + GitSparseCheckoutConfig: flatteners.StringPtr(runtimeSource.Config.GitSparseCheckoutConfig), + IncludeSubModule: flatteners.BoolPtr(runtimeSource.Config.IncludeSubModule), + Ref: flatteners.StringPtr(runtimeSource.Config.Ref), + Repo: flatteners.String(runtimeSource.Config.Repo), + WorkingDir: flatteners.StringPtr(runtimeSource.Config.WorkingDir), + } + + configObj, diags := types.ObjectValueFrom(context.Background(), RuntimeSourceConfigModel{}.AttributeTypes(), configModel) + if diags.HasError() { + return nullObject, diags + } + runtimeSourceModel.Config = configObj + } else { + runtimeSourceModel.Config = types.ObjectNull(RuntimeSourceConfigModel{}.AttributeTypes()) + } + + var runtimeSourceTerraType types.Object + runtimeSourceTerraType, diags := types.ObjectValueFrom(context.Background(), RuntimeSourceModel{}.AttributeTypes(), runtimeSourceModel) + if diags.HasError() { + return nullObject, diags + } + + return runtimeSourceTerraType, nil +} + +func BuildAPIModelToWorkflowTemplateModel(apiResponse *workflowtemplates.ReadWorkflowTemplateResponse) (*WorkflowTemplateResourceModel, diag.Diagnostics) { + diag := diag.Diagnostics{} + + model := &WorkflowTemplateResourceModel{ + Id: flatteners.StringPtr(apiResponse.Id), + TemplateName: flatteners.StringPtr(apiResponse.TemplateName), + OwnerOrg: flatteners.StringPtr(apiResponse.OwnerOrg), + SourceConfigKind: flatteners.String(string(*apiResponse.SourceConfigKind)), + IsPublic: flatteners.String(string(*apiResponse.IsPublic)), + ShortDescription: flatteners.StringPtr(apiResponse.ShortDescription), + } + + // Convert Tags + if apiResponse.Tags != nil { + var tags []types.String + for _, tag := range apiResponse.Tags { + tags = append(tags, flatteners.String(tag)) + } + tagsList, diags_tags := types.ListValueFrom(context.Background(), types.StringType, tags) + diag.Append(diags_tags...) + 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, diags_shared := types.ListValueFrom(context.Background(), types.StringType, sharedOrgs) + diag.Append(diags_shared...) + 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, diags_ct := types.MapValueFrom(context.Background(), types.StringType, contextTags) + diag.Append(diags_ct...) + model.ContextTags = contextTagsMap + } else { + model.ContextTags = types.MapNull(types.StringType) + } + + // Convert RuntimeSource + runtimeSourceTerraType, diags := RuntimeSourceToTerraType(apiResponse.RuntimeSource) + if diags.HasError() { + return nil, diags + } + model.RuntimeSource = runtimeSourceTerraType + + // Convert VCSTriggers + vcsTriggersTerraType, diags := VCSTriggersToTerraType(apiResponse.VCSTriggers) + if diags.HasError() { + return nil, diags + } + model.VCSTriggers = vcsTriggersTerraType + + return model, diag +} diff --git a/internal/resource/workflow_template/resource.go b/internal/resource/workflow_template/resource.go new file mode 100644 index 0000000..fdce75b --- /dev/null +++ b/internal/resource/workflow_template/resource.go @@ -0,0 +1,204 @@ +package workflowtemplate + +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 = &workflowTemplateResource{} + _ resource.ResourceWithConfigure = &workflowTemplateResource{} + _ resource.ResourceWithImportState = &workflowTemplateResource{} +) + +type workflowTemplateResource struct { + client *sgclient.Client + org_name string +} + +// NewResource is a helper function to simplify the provider implementation. +func NewResource() resource.Resource { + return &workflowTemplateResource{} +} + +// Metadata returns the resource type name. +func (r *workflowTemplateResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_workflow_template" +} + +// Configure adds the provider configured client to the resource. +func (r *workflowTemplateResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Add a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + 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 workflow template using its ID. +func (r *workflowTemplateResource) 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 *workflowTemplateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan WorkflowTemplateResourceModel + + 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) + payload.TemplateType = sgsdkgo.TemplateTypeEnum("IAC") + + createResp, err := r.client.WorkflowTemplates.CreateWorkflowTemplate(ctx, r.org_name, false, payload) + if err != nil { + resp.Diagnostics.AddError("Error creating workflow template", "Error in creating workflow template API call: "+err.Error()) + return + } + + // Set the ID from the create response + templateID := *createResp.Data.Parent.Id + + // Call read to get the full state since create response doesn't return all values + readResp, err := r.client.WorkflowTemplates.ReadWorkflowTemplate(ctx, r.org_name, templateID) + if err != nil { + resp.Diagnostics.AddError("Error reading created workflow template", "Could not read the created workflow template: "+err.Error()) + return + } + + if readResp == nil { + resp.Diagnostics.AddError("Error reading workflow template", "API response is empty") + return + } + + templateModel, diags := BuildAPIModelToWorkflowTemplateModel(&readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, &templateModel)...) +} + +// Read refreshes the Terraform state with the latest data. +func (r *workflowTemplateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state WorkflowTemplateResourceModel + + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + templateID := state.Id.ValueString() + + readResp, err := r.client.WorkflowTemplates.ReadWorkflowTemplate(ctx, r.org_name, templateID) + if err != nil { + resp.Diagnostics.AddError("Error reading workflow template", "Error in reading workflow template API call: "+err.Error()) + return + } + + templateModel, diags := BuildAPIModelToWorkflowTemplateModel(&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 *workflowTemplateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan WorkflowTemplateResourceModel + var state WorkflowTemplateResourceModel + + 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 + } + + payload.OwnerOrg = sgsdkgo.Optional(r.org_name) + + _, err := r.client.WorkflowTemplates.UpdateWorkflowTemplate(ctx, r.org_name, templateID, payload) + if err != nil { + resp.Diagnostics.AddError("Error updating workflow template", "Error in updating workflow 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.WorkflowTemplates.ReadWorkflowTemplate(ctx, r.org_name, templateID) + if err != nil { + resp.Diagnostics.AddError("Error reading updated workflow template", "Could not read the updated workflow template: "+err.Error()) + return + } + + templateModel, diags := BuildAPIModelToWorkflowTemplateModel(&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 *workflowTemplateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state WorkflowTemplateResourceModel + + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.WorkflowTemplates.DeleteWorkflowTemplate(ctx, r.org_name, state.Id.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error deleting workflow template", "Error in deleting workflow template API call: "+err.Error()) + return + } +} diff --git a/internal/resource/workflow_template/resource_test.go b/internal/resource/workflow_template/resource_test.go new file mode 100644 index 0000000..b16e1b6 --- /dev/null +++ b/internal/resource/workflow_template/resource_test.go @@ -0,0 +1,116 @@ +package workflowtemplate_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 TestAccWorkflowTemplate_Basic(t *testing.T) { + templateName := "tf-provider-workflow-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: testAccWorkflowTemplateConfig(templateName, sourceConfigKind), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_workflow_template.test", "template_name", templateName), + resource.TestCheckResourceAttr("stackguardian_workflow_template.test", "is_public", "0"), + resource.TestCheckResourceAttrSet("stackguardian_workflow_template.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccWorkflowTemplateConfigUpdated(templateName, sourceConfigKind), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_workflow_template.test", "template_name", templateName), + resource.TestCheckResourceAttr("stackguardian_workflow_template.test", "description", "Updated description"), + ), + }, + // Delete testing automatically occurs + }, + }) +} + +func TestAccWorkflowTemplate_WithRuntime(t *testing.T) { + templateName := "tf-provider-workflow-template-2" + + 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 with runtime source + { + Config: testAccWorkflowTemplateConfigWithRuntime(templateName, sourceConfigKind), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_workflow_template.test", "template_name", templateName), + resource.TestCheckResourceAttrSet("stackguardian_workflow_template.test", "runtime_source.source_config_dest_kind"), + ), + }, + // Delete testing automatically occurs + }, + }) +} + +func testAccWorkflowTemplateConfig(name, sourceConfigKind string) string { + return fmt.Sprintf(` +resource "stackguardian_workflow_template" "test" { + template_name = "%s" + source_config_kind = "%s" + is_public = "0" + tags = ["test", "terraform"] +} +`, name, sourceConfigKind) +} + +func testAccWorkflowTemplateConfigUpdated(name, sourceConfigKind string) string { + return fmt.Sprintf(` +resource "stackguardian_workflow_template" "test" { + template_name = "%s" + source_config_kind = "%s" + is_public = "1" + description = "Updated description" + tags = ["test", "terraform", "updated"] +} +`, name, sourceConfigKind) +} + +func testAccWorkflowTemplateConfigWithRuntime(name, sourceConfigKind string) string { + return fmt.Sprintf(` +resource "stackguardian_workflow_template" "test" { + template_name = "%s" + source_config_kind = "%s" + is_public = "0" + tags = ["test", "terraform"] + + runtime_source = { + source_config_dest_kind = "GITHUB_COM" + config = { + is_private = false + repo = "https://github.com/taherkk/taher-null-resource.git" + } + } +} +`, name, sourceConfigKind) +} diff --git a/internal/resource/workflow_template/schema.go b/internal/resource/workflow_template/schema.go new file mode 100644 index 0000000..806d9f0 --- /dev/null +++ b/internal/resource/workflow_template/schema.go @@ -0,0 +1,154 @@ +package workflowtemplate + +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" +) + +func WorkflowTemplateRuntimeSourceConfig() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "source_config_dest_kind": schema.StringAttribute{ + MarkdownDescription: constants.RuntimeSourceDestKind, + Optional: true, + }, + "config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.RuntimeSourceConfig, + Optional: true, + Attributes: map[string]schema.Attribute{ + "auth": schema.StringAttribute{ + MarkdownDescription: constants.RuntimeSourceConfigAuth, + Optional: true, + Sensitive: true, + }, + "git_core_auto_crlf": schema.BoolAttribute{ + MarkdownDescription: constants.RuntimeSourceConfigGitCoreCRLF, + Optional: true, + Computed: true, + }, + "git_sparse_checkout_config": schema.StringAttribute{ + MarkdownDescription: constants.RuntimeSourceConfigGitSparse, + Optional: true, + }, + "include_sub_module": schema.BoolAttribute{ + MarkdownDescription: constants.RuntimeSourceConfigIncludeSubmodule, + Optional: true, + }, + "is_private": schema.BoolAttribute{ + MarkdownDescription: constants.RuntimeSourceConfigIsPrivate, + Optional: true, + Computed: true, + }, + "ref": schema.StringAttribute{ + MarkdownDescription: constants.RuntimeSourceConfigRef, + Optional: true, + Computed: true, + }, + "repo": schema.StringAttribute{ + MarkdownDescription: constants.RuntimeSourceConfigRepo, + Required: true, + }, + "working_dir": schema.StringAttribute{ + MarkdownDescription: constants.RuntimeSourceConfigWorkingDir, + Optional: true, + }, + }, + }, + } +} + +// Schema defines the schema for the resource. +func (r *workflowTemplateResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Manages a workflow 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.WorkflowTemplateOwnerOrg, + Computed: true, + }, + "template_name": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowTemplateName, + Required: true, + }, + "source_config_kind": schema.StringAttribute{ + MarkdownDescription: constants.SourceConfigKind, + Required: true, + }, + "is_public": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowTemplateIsPublic, + Optional: true, + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Description, "Description for workflow template"), + Optional: true, + Computed: true, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Tags, "Tags for workflow template"), + ElementType: types.StringType, + Optional: true, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: fmt.Sprintf(constants.ContextTags, "workflow template"), + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "shared_orgs_list": schema.ListAttribute{ + MarkdownDescription: constants.WorkflowTemplateSharedOrgs, + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "runtime_source": schema.SingleNestedAttribute{ + MarkdownDescription: fmt.Sprintf(constants.RuntimeSource, "template"), + Optional: true, + Computed: true, + Attributes: WorkflowTemplateRuntimeSourceConfig(), + }, + "vcs_triggers": schema.SingleNestedAttribute{ + MarkdownDescription: constants.VCSTriggers, + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + MarkdownDescription: constants.VCSTriggersType, + Required: true, + }, + "create_tag": schema.SingleNestedAttribute{ + MarkdownDescription: constants.VCSTriggersCreateTag, + Required: true, + Attributes: map[string]schema.Attribute{ + "create_revision": schema.SingleNestedAttribute{ + MarkdownDescription: constants.VCSTriggersCreateTagRevision, + Required: true, + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + MarkdownDescription: constants.VCSTriggersCreateTagRevisionEnabled, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} diff --git a/internal/resource/workflow_template_revision/model.go b/internal/resource/workflow_template_revision/model.go new file mode 100644 index 0000000..c60ade9 --- /dev/null +++ b/internal/resource/workflow_template_revision/model.go @@ -0,0 +1,2161 @@ +package workflowtemplaterevision + +import ( + "context" + "encoding/json" + + sgsdkgo "github.com/StackGuardian/sg-sdk-go" + "github.com/StackGuardian/sg-sdk-go/workflowtemplaterevisions" + "github.com/StackGuardian/sg-sdk-go/workflowtemplates" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/expanders" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/flatteners" + workflowtemplate "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_template" + "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" +) + +// WorkflowTemplateRevisionResourceModel describes the resource data model based on schema.go +type WorkflowTemplateRevisionResourceModel struct { + Id types.String `tfsdk:"id"` + TemplateId types.String `tfsdk:"template_id"` + LongDescription types.String `tfsdk:"description"` + Alias types.String `tfsdk:"alias"` + Notes types.String `tfsdk:"notes"` + SourceConfigKind types.String `tfsdk:"source_config_kind"` + IsPublic types.String `tfsdk:"is_public"` + Deprecation types.Object `tfsdk:"deprecation"` + EnvironmentVariables types.List `tfsdk:"environment_variables"` + InputSchemas types.List `tfsdk:"input_schemas"` + MiniSteps types.Object `tfsdk:"mini_steps"` + RunnerConstraints types.Object `tfsdk:"runner_constraints"` + Tags types.List `tfsdk:"tags"` + UserSchedules types.List `tfsdk:"user_schedules"` + ContextTags types.Map `tfsdk:"context_tags"` + Approvers types.List `tfsdk:"approvers"` + NumberOfApprovalsRequired types.Int64 `tfsdk:"number_of_approvals_required"` + UserJobCPU types.Int64 `tfsdk:"user_job_cpu"` + UserJobMemory types.Int64 `tfsdk:"user_job_memory"` + RuntimeSource types.Object `tfsdk:"runtime_source"` + TerraformConfig types.Object `tfsdk:"terraform_config"` + DeploymentPlatformConfig types.Object `tfsdk:"deployment_platform_config"` + WfStepsConfig types.List `tfsdk:"wf_steps_config"` +} + +// Supporting model structs based on schema structure + +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, + } +} + +type EnvironmentVariableConfigModel struct { + VarName types.String `tfsdk:"var_name"` + SecretId types.String `tfsdk:"secret_id"` + TextValue types.String `tfsdk:"text_value"` +} + +func (EnvironmentVariableConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "var_name": types.StringType, + "secret_id": types.StringType, + "text_value": types.StringType, + } +} + +type EnvironmentVariableModel struct { + Config types.Object `tfsdk:"config"` + Kind types.String `tfsdk:"kind"` +} + +func (EnvironmentVariableModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "config": types.ObjectType{AttrTypes: EnvironmentVariableConfigModel{}.AttributeTypes()}, + "kind": types.StringType, + } +} + +type InputSchemaModel struct { + Type types.String `tfsdk:"type"` + EncodedData types.String `tfsdk:"encoded_data"` + UISchemaData types.String `tfsdk:"ui_schema_data"` +} + +func (InputSchemaModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "type": types.StringType, + "encoded_data": types.StringType, + "ui_schema_data": types.StringType, + } +} + +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 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}, + } +} + +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()}, + } +} + +type RuntimeSourceConfigModel struct { + IsPrivate types.Bool `tfsdk:"is_private"` + Auth types.String `tfsdk:"auth"` + GitCoreAutoCrlf types.Bool `tfsdk:"git_core_auto_crlf"` + GitSparseCheckoutConfig types.String `tfsdk:"git_sparse_checkout_config"` + IncludeSubModule types.Bool `tfsdk:"include_sub_module"` + Ref types.String `tfsdk:"ref"` + Repo types.String `tfsdk:"repo"` + WorkingDir types.String `tfsdk:"working_dir"` +} + +func (RuntimeSourceConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "is_private": types.BoolType, + "auth": types.StringType, + "git_core_auto_crlf": types.BoolType, + "git_sparse_checkout_config": types.StringType, + "include_sub_module": types.BoolType, + "ref": types.StringType, + "repo": types.StringType, + "working_dir": types.StringType, + } +} + +type RuntimeSourceModel struct { + SourceConfigDestKind types.String `tfsdk:"source_config_dest_kind"` + Config types.Object `tfsdk:"config"` +} + +func (RuntimeSourceModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "source_config_dest_kind": types.StringType, + "config": types.ObjectType{ + AttrTypes: RuntimeSourceConfigModel{}.AttributeTypes(), + }, + } +} + +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, + } +} + +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, + } +} + +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: EnvironmentVariableModel{}.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()}, + } +} + +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 { + 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": types.ListType{ElemType: types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}}, + "pre_apply_wf_steps_config": types.ListType{ElemType: types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}}, + "pre_plan_wf_steps_config": types.ListType{ElemType: types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}}, + "post_plan_wf_steps_config": types.ListType{ElemType: types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}}, + "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, + } +} + +type DeploymentPlatformConfigConfigModel struct { + IntegrationId types.String `tfsdk:"integration_id"` + ProfileName types.String `tfsdk:"profile_name"` +} + +func (DeploymentPlatformConfigConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "integration_id": types.StringType, + "profile_name": types.StringType, + } +} + +type DeploymentPlatformConfigModel struct { + Kind types.String `tfsdk:"kind"` + Config types.Object `tfsdk:"config"` +} + +func (DeploymentPlatformConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "kind": types.StringType, + "config": types.ObjectType{ + AttrTypes: DeploymentPlatformConfigConfigModel{}.AttributeTypes(), + }, + } +} + + +type UserSchedulesModel struct { + Cron types.String `tfsdk:"cron"` + State types.String `tfsdk:"state"` + Desc types.String `tfsdk:"desc"` + Name types.String `tfsdk:"name"` +} + +func (UserSchedulesModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "cron": types.StringType, + "state": types.StringType, + "desc": types.StringType, + "name": types.StringType, + } +} + +func convertDeprecationToAPIModel(ctx context.Context, deprecationObj types.Object) (*workflowtemplaterevisions.Deprecation, diag.Diagnostics) { + if deprecationObj.IsNull() || deprecationObj.IsUnknown() { + return nil, nil + } + + var deprecationTerraModel DeprecationModel + diags := deprecationObj.As(ctx, &deprecationTerraModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, diags + } + + return &workflowtemplaterevisions.Deprecation{ + EffectiveDate: deprecationTerraModel.EffectiveDate.ValueStringPointer(), + Message: deprecationTerraModel.Message.ValueStringPointer(), + }, nil +} + +func convertRunnerConstraintsToAPIModel(ctx context.Context, runnerConstraintsTerraType types.Object) (*sgsdkgo.RunnerConstraints, diag.Diagnostics) { + if runnerConstraintsTerraType.IsNull() || runnerConstraintsTerraType.IsUnknown() { + return nil, nil + } + + var runnerConstraintsModel RunnerConstraintsModel + diags := runnerConstraintsTerraType.As(ctx, &runnerConstraintsModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, diags + } + + names, diags := expanders.StringList(ctx, runnerConstraintsModel.Names) + if diags.HasError() { + return nil, diags + } + + return &sgsdkgo.RunnerConstraints{ + Type: sgsdkgo.RunnerConstraintsTypeEnum(runnerConstraintsModel.Type.ValueString()), + Names: names, + }, nil +} + +func convertUserSchedulesToAPIModel(ctx context.Context, userSchedulesList types.List) ([]workflowtemplaterevisions.UserSchedules, diag.Diagnostics) { + if userSchedulesList.IsNull() || userSchedulesList.IsUnknown() { + return nil, nil + } + + var models []UserSchedulesModel + diags := userSchedulesList.ElementsAs(ctx, &models, false) + if diags.HasError() { + return nil, diags + } + + result := make([]workflowtemplaterevisions.UserSchedules, len(models)) + for i, m := range models { + schedule := workflowtemplaterevisions.UserSchedules{ + Cron: m.Cron.ValueString(), + State: workflowtemplaterevisions.UserSchedulesStateEnum(m.State.ValueString()), + Desc: m.Desc.ValueStringPointer(), + Name: m.Name.ValueStringPointer(), + } + + result[i] = schedule + } + return result, nil +} + +// ToAPIModel converts the Terraform model to the API request model +func (m *WorkflowTemplateRevisionResourceModel) ToAPIModel(ctx context.Context) (*workflowtemplaterevisions.CreateWorkflowTemplateRevisionsRequest, diag.Diagnostics) { + apiModel := &workflowtemplaterevisions.CreateWorkflowTemplateRevisionsRequest{ + LongDescription: m.LongDescription.ValueStringPointer(), + SourceConfigKind: (*workflowtemplates.WorkflowTemplateSourceConfigKindEnum)(m.SourceConfigKind.ValueStringPointer()), + Alias: m.Alias.ValueString(), + Notes: m.Notes.ValueString(), + TemplateType: "IAC", + IsPublic: (*sgsdkgo.IsPublicEnum)(m.IsPublic.ValueStringPointer()), + NumberOfApprovalsRequired: expanders.IntPtr(m.NumberOfApprovalsRequired.ValueInt64Pointer()), + UserJobCPU: expanders.IntPtr(m.UserJobCPU.ValueInt64Pointer()), + UserJobMemory: expanders.IntPtr(m.UserJobMemory.ValueInt64Pointer()), + } + + // TODO: + deprecation, diags := convertDeprecationToAPIModel(ctx, m.Deprecation) + if diags.HasError() { + return nil, diags + } + apiModel.Deprecation = deprecation + + // handle runner constraints + runnerConstraints, diags := convertRunnerConstraintsToAPIModel(ctx, m.RunnerConstraints) + if diags.HasError() { + return nil, diags + } + apiModel.RunnerConstraints = runnerConstraints + + // Handle UserSchedules + userSchedules, diags := convertUserSchedulesToAPIModel(ctx, m.UserSchedules) + if diags.HasError() { + return nil, diags + } + apiModel.UserSchedules = userSchedules + + // Handle Tags + if !m.Tags.IsNull() && !m.Tags.IsUnknown() { + tags, diags := expanders.StringList(ctx, m.Tags) + if diags.HasError() { + return nil, diags + } + apiModel.Tags = tags + } + + // Handle ContextTags + if !m.ContextTags.IsNull() && !m.ContextTags.IsUnknown() { + contextTags := make(map[string]string) + diags := m.ContextTags.ElementsAs(ctx, &contextTags, false) + if diags.HasError() { + return nil, diags + } + apiModel.ContextTags = contextTags + } + + // Handle Approvers + if !m.Approvers.IsNull() && !m.Approvers.IsUnknown() { + approvers, diags := expanders.StringList(ctx, m.Approvers) + if diags.HasError() { + return nil, diags + } + apiModel.Approvers = approvers + } + + // Handle EnvironmentVariables + if !m.EnvironmentVariables.IsNull() && !m.EnvironmentVariables.IsUnknown() { + envVars, diags := convertEnvironmentVariablesToAPI(ctx, m.EnvironmentVariables) + if diags.HasError() { + return nil, diags + } + apiModel.EnvironmentVariables = envVars + } + + // Handle InputSchemas + if !m.InputSchemas.IsNull() && !m.InputSchemas.IsUnknown() { + inputSchemas, diags := convertInputSchemasToAPI(ctx, m.InputSchemas) + if diags.HasError() { + return nil, diags + } + apiModel.InputSchemas = inputSchemas + } + + // Handle MiniSteps + if !m.MiniSteps.IsNull() && !m.MiniSteps.IsUnknown() { + miniSteps, diags := convertMinistepsToAPI(ctx, m.MiniSteps) + if diags.HasError() { + return nil, diags + } + apiModel.Ministeps = miniSteps + } + + // Handle RuntimeSource + if !m.RuntimeSource.IsNull() && !m.RuntimeSource.IsUnknown() { + var runtimeSourceModel workflowtemplate.RuntimeSourceModel + diags := m.RuntimeSource.As(ctx, &runtimeSourceModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + runtimeSource, diags := runtimeSourceModel.ToAPIModel(ctx) + if diags.HasError() { + return nil, diags + } + apiModel.RuntimeSource = runtimeSource + } + + // Handle TerraformConfig + if !m.TerraformConfig.IsNull() && !m.TerraformConfig.IsUnknown() { + terraformConfig, diags := convertTerraformConfigToAPI(ctx, m.TerraformConfig) + if diags.HasError() { + return nil, diags + } + apiModel.TerraformConfig = terraformConfig + } + + // Handle DeploymentPlatformConfig + if !m.DeploymentPlatformConfig.IsNull() && !m.DeploymentPlatformConfig.IsUnknown() { + deploymentConfig, diags := convertDeploymentPlatformConfigToAPI(ctx, m.DeploymentPlatformConfig) + if diags.HasError() { + return nil, diags + } + apiModel.DeploymentPlatformConfig = deploymentConfig + } + + // Handle WfStepsConfig + if !m.WfStepsConfig.IsNull() && !m.WfStepsConfig.IsUnknown() { + wfStepsConfigs, diags := convertWfStepsConfigListToAPI(ctx, m.WfStepsConfig) + if diags.HasError() { + return nil, diags + } + apiModel.WfStepsConfig = wfStepsConfigs + } + + return apiModel, nil +} + +// TODO: Review Update function +func (m *WorkflowTemplateRevisionResourceModel) ToUpdateAPIModel(ctx context.Context) (*workflowtemplaterevisions.UpdateWorkflowTemplateRevisionRequest, diag.Diagnostics) { + diagn := diag.Diagnostics{} + + apiModel := &workflowtemplaterevisions.UpdateWorkflowTemplateRevisionRequest{ + IsPublic: sgsdkgo.Optional(sgsdkgo.IsPublicEnum(m.IsPublic.ValueString())), + UserJobCPU: sgsdkgo.Optional(int(m.UserJobCPU.ValueInt64())), + UserJobMemory: sgsdkgo.Optional(int(m.UserJobMemory.ValueInt64())), + } + + // Handle LongDescription + if !m.LongDescription.IsNull() && !m.LongDescription.IsUnknown() { + apiModel.LongDescription = sgsdkgo.Optional(m.LongDescription.ValueString()) + } else { + apiModel.LongDescription = sgsdkgo.Null[string]() + } + + // Handle SourceConfigKind + if !m.SourceConfigKind.IsNull() && !m.SourceConfigKind.IsUnknown() { + sourceConfigKind := workflowtemplates.WorkflowTemplateSourceConfigKindEnum(m.SourceConfigKind.ValueString()) + apiModel.SourceConfigKind = sgsdkgo.Optional(sourceConfigKind) + } else { + apiModel.SourceConfigKind = sgsdkgo.Null[workflowtemplates.WorkflowTemplateSourceConfigKindEnum]() + } + + // Handle Alias + if !m.Alias.IsNull() && !m.Alias.IsUnknown() { + apiModel.Alias = sgsdkgo.Optional(m.Alias.ValueString()) + } else { + apiModel.Alias = sgsdkgo.Null[string]() + } + + // Handle Notes + if !m.Notes.IsNull() && !m.Notes.IsUnknown() { + apiModel.Notes = sgsdkgo.Optional(m.Notes.ValueString()) + } else { + apiModel.Notes = sgsdkgo.Null[string]() + } + + // Handle NumberOfApprovalsRequired + if !m.NumberOfApprovalsRequired.IsNull() && !m.NumberOfApprovalsRequired.IsUnknown() { + numApprovals := int(m.NumberOfApprovalsRequired.ValueInt64()) + apiModel.NumberOfApprovalsRequired = sgsdkgo.Optional(numApprovals) + } + + // handle deprecation + deprecation, diags := convertDeprecationToAPIModel(ctx, m.Deprecation) + if diags.HasError() { + return nil, diags + } + if deprecation != nil { + apiModel.Deprecation = sgsdkgo.Optional(*deprecation) + } else { + apiModel.Deprecation = sgsdkgo.Null[workflowtemplaterevisions.Deprecation]() + } + + // handle runner constraints + runnerConstraints, diags := convertRunnerConstraintsToAPIModel(ctx, m.RunnerConstraints) + if diags.HasError() { + return nil, diags + } + if runnerConstraints != nil { + apiModel.RunnerConstraints = sgsdkgo.Optional(*runnerConstraints) + } else { + apiModel.RunnerConstraints = sgsdkgo.Null[sgsdkgo.RunnerConstraints]() + } + + // handle user schedules + userSchedules, diags := convertUserSchedulesToAPIModel(ctx, m.UserSchedules) + if diags.HasError() { + return nil, diags + } + if userSchedules != nil { + apiModel.UserSchedules = sgsdkgo.Optional(userSchedules) + } else { + apiModel.UserSchedules = sgsdkgo.Null[[]workflowtemplaterevisions.UserSchedules]() + } + + // Handle Tags + if !m.Tags.IsNull() && !m.Tags.IsUnknown() { + tags, diags := expanders.StringList(ctx, m.Tags) + if diags.HasError() { + return nil, diags + } + apiModel.Tags = sgsdkgo.Optional(tags) + } else { + apiModel.Tags = sgsdkgo.Null[[]string]() + } + + // Handle ContextTags + if !m.ContextTags.IsNull() && !m.ContextTags.IsUnknown() { + contextTags := make(map[string]string) + diags := m.ContextTags.ElementsAs(ctx, &contextTags, false) + if diags.HasError() { + return nil, diags + } + apiModel.ContextTags = sgsdkgo.Optional(contextTags) + } else { + apiModel.ContextTags = sgsdkgo.Null[map[string]string]() + } + + // Handle Approvers + if !m.Approvers.IsNull() && !m.Approvers.IsUnknown() { + approvers, diags := expanders.StringList(ctx, m.Approvers) + if diags.HasError() { + return nil, diags + } + apiModel.Approvers = sgsdkgo.Optional(approvers) + } else { + apiModel.Approvers = sgsdkgo.Null[[]string]() + } + + // Handle RuntimeSource + if !m.RuntimeSource.IsNull() && !m.RuntimeSource.IsUnknown() { + runtimeSource, diags := convertRuntimeSourceToAPI(ctx, m.RuntimeSource) + if diags.HasError() { + return nil, diags + } + runtimeSourceUpdate := workflowtemplates.RuntimeSourceUpdate{ + SourceConfigDestKind: runtimeSource.SourceConfigDestKind, + } + apiModel.RuntimeSource = sgsdkgo.Optional(runtimeSourceUpdate) + } + + // Handle TerraformConfig + if !m.TerraformConfig.IsNull() && !m.TerraformConfig.IsUnknown() { + terraformConfig, diags := convertTerraformConfigToAPI(ctx, m.TerraformConfig) + diagn.Append(diags...) + if !diagn.HasError() && terraformConfig != nil { + apiModel.TerraformConfig = sgsdkgo.Optional(*terraformConfig) + } + } + + // Handle DeploymentPlatformConfig + if !m.DeploymentPlatformConfig.IsNull() && !m.DeploymentPlatformConfig.IsUnknown() { + deploymentConfig, diags := convertDeploymentPlatformConfigToAPI(ctx, m.DeploymentPlatformConfig) + diagn.Append(diags...) + if !diagn.HasError() && deploymentConfig != nil { + apiModel.DeploymentPlatformConfig = sgsdkgo.Optional(*deploymentConfig) + } + } + + // Handle WfStepsConfig at root level + if !m.WfStepsConfig.IsNull() && !m.WfStepsConfig.IsUnknown() { + wfStepsConfigs, diags := convertWfStepsConfigListToAPI(ctx, m.WfStepsConfig) + diagn.Append(diags...) + if !diagn.HasError() { + apiModel.WfStepsConfig = sgsdkgo.Optional(wfStepsConfigs) + } + } + + // Handle EnvironmentVariables + envVars, diags := convertEnvironmentVariablesToAPI(ctx, m.EnvironmentVariables) + if diags.HasError() { + return nil, diags + } + if envVars != nil { + apiModel.EnvironmentVariables = sgsdkgo.Optional(envVars) + } else { + apiModel.EnvironmentVariables = sgsdkgo.Null[[]sgsdkgo.EnvVars]() + } + + // Handle InputSchemas + inputSchemas, diags := convertInputSchemasToAPI(ctx, m.InputSchemas) + if diags.HasError() { + return nil, diags + } + if inputSchemas != nil { + apiModel.InputSchemas = sgsdkgo.Optional(inputSchemas) + } else { + apiModel.InputSchemas = sgsdkgo.Null[[]sgsdkgo.InputSchemas]() + } + + // Handle Ministeps + ministeps, diags := convertMinistepsToAPI(ctx, m.MiniSteps) + if diags.HasError() { + return nil, diags + } + if ministeps != nil { + apiModel.Ministeps = sgsdkgo.Optional(*ministeps) + } else { + apiModel.Ministeps = sgsdkgo.Null[workflowtemplaterevisions.Ministeps]() + } + + return apiModel, diagn +} + +// Helper functions for type conversion +func convertRuntimeSourceToAPI(ctx context.Context, runtimeSourceObj types.Object) (*workflowtemplates.RuntimeSource, diag.Diagnostics) { + if runtimeSourceObj.IsNull() || runtimeSourceObj.IsUnknown() { + return nil, nil + } + + var runtimeSourceModel RuntimeSourceModel + diags := runtimeSourceObj.As(ctx, &runtimeSourceModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + + runtimeSource := &workflowtemplates.RuntimeSource{ + SourceConfigDestKind: workflowtemplates.SourceConfigDestKindEnum(runtimeSourceModel.SourceConfigDestKind.ValueString()).Ptr(), + } + + // Convert config + if !runtimeSourceModel.Config.IsNull() && !runtimeSourceModel.Config.IsUnknown() { + var configModel RuntimeSourceConfigModel + diag_cfg := runtimeSourceModel.Config.As(ctx, &configModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diag_cfg.HasError() { + return nil, diag_cfg + } + + runtimeSource.Config = &workflowtemplates.RuntimeSourceConfig{ + IsPrivate: configModel.IsPrivate.ValueBoolPointer(), + Auth: configModel.Auth.ValueStringPointer(), + GitCoreAutoCRLF: configModel.GitCoreAutoCrlf.ValueBoolPointer(), + GitSparseCheckoutConfig: configModel.GitSparseCheckoutConfig.ValueStringPointer(), + IncludeSubModule: configModel.IncludeSubModule.ValueBoolPointer(), + Ref: configModel.Ref.ValueStringPointer(), + Repo: configModel.Repo.ValueString(), + WorkingDir: configModel.WorkingDir.ValueStringPointer(), + } + } + + return runtimeSource, nil +} + +func convertTerraformConfigToAPI(ctx context.Context, terraformConfigObj types.Object) (*sgsdkgo.TerraformConfig, diag.Diagnostics) { + diagn := diag.Diagnostics{} + + var terraformConfigModel TerraformConfigModel + diag_tc := terraformConfigObj.As(ctx, &terraformConfigModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diag_tc.HasError() { + return nil, diag_tc + } + + terraformConfig := &sgsdkgo.TerraformConfig{ + TerraformVersion: terraformConfigModel.TerraformVersion.ValueStringPointer(), + DriftCheck: terraformConfigModel.DriftCheck.ValueBoolPointer(), + DriftCron: terraformConfigModel.DriftCron.ValueStringPointer(), + ManagedTerraformState: terraformConfigModel.ManagedTerraformState.ValueBoolPointer(), + ApprovalPreApply: terraformConfigModel.ApprovalPreApply.ValueBoolPointer(), + TerraformPlanOptions: terraformConfigModel.TerraformPlanOptions.ValueStringPointer(), + TerraformInitOptions: terraformConfigModel.TerraformInitOptions.ValueStringPointer(), + Timeout: expanders.IntPtr(terraformConfigModel.Timeout.ValueInt64Pointer()), + RunPreInitHooksOnDrift: terraformConfigModel.RunPreInitHooksOnDrift.ValueBoolPointer(), + } + + // Convert TerraformBinPath (MountPoints) + if !terraformConfigModel.TerraformBinPath.IsNull() && !terraformConfigModel.TerraformBinPath.IsUnknown() { + mountPoints, diags := convertMountPointsListToAPI(ctx, terraformConfigModel.TerraformBinPath) + if diags.HasError() { + return nil, diags + } + terraformConfig.TerraformBinPath = mountPoints + } + + // Convert PostApplyWfStepsConfig + if !terraformConfigModel.PostApplyWfStepsConfig.IsNull() && !terraformConfigModel.PostApplyWfStepsConfig.IsUnknown() { + wfSteps, diags := convertWfStepsConfigListToAPI(ctx, terraformConfigModel.PostApplyWfStepsConfig) + if diags.HasError() { + return nil, diags + } + terraformConfig.PostApplyWfStepsConfig = wfSteps + } + + // Convert PreApplyWfStepsConfig + if !terraformConfigModel.PreApplyWfStepsConfig.IsNull() && !terraformConfigModel.PreApplyWfStepsConfig.IsUnknown() { + wfSteps, diags := convertWfStepsConfigListToAPI(ctx, terraformConfigModel.PreApplyWfStepsConfig) + if diags.HasError() { + return nil, diags + } + terraformConfig.PreApplyWfStepsConfig = wfSteps + } + + // Convert PrePlanWfStepsConfig + if !terraformConfigModel.PrePlanWfStepsConfig.IsNull() && !terraformConfigModel.PrePlanWfStepsConfig.IsUnknown() { + wfSteps, diags := convertWfStepsConfigListToAPI(ctx, terraformConfigModel.PrePlanWfStepsConfig) + if diags.HasError() { + return nil, diags + } + terraformConfig.PrePlanWfStepsConfig = wfSteps + } + + // Convert PostPlanWfStepsConfig + if !terraformConfigModel.PostPlanWfStepsConfig.IsNull() && !terraformConfigModel.PostPlanWfStepsConfig.IsUnknown() { + wfSteps, diags := convertWfStepsConfigListToAPI(ctx, terraformConfigModel.PostPlanWfStepsConfig) + if diags.HasError() { + return nil, diags + } + terraformConfig.PostPlanWfStepsConfig = wfSteps + } + + // Convert PreInitHooks + if !terraformConfigModel.PreInitHooks.IsNull() && !terraformConfigModel.PreInitHooks.IsUnknown() { + preInitHooks, diags := expanders.StringList(ctx, terraformConfigModel.PreInitHooks) + if diags.HasError() { + return nil, diags + } + terraformConfig.PreInitHooks = preInitHooks + } + + // Convert PrePlanHooks + if !terraformConfigModel.PrePlanHooks.IsNull() && !terraformConfigModel.PrePlanHooks.IsUnknown() { + prePlanHooks, diags := expanders.StringList(ctx, terraformConfigModel.PrePlanHooks) + if diags.HasError() { + return nil, diags + } + terraformConfig.PrePlanHooks = prePlanHooks + } + + // Convert PostPlanHooks + if !terraformConfigModel.PostPlanHooks.IsNull() && !terraformConfigModel.PostPlanHooks.IsUnknown() { + postPlanHooks, diags := expanders.StringList(ctx, terraformConfigModel.PostPlanHooks) + if diags.HasError() { + return nil, diags + } + terraformConfig.PostPlanHooks = postPlanHooks + } + + // Convert PreApplyHooks + if !terraformConfigModel.PreApplyHooks.IsNull() && !terraformConfigModel.PreApplyHooks.IsUnknown() { + preApplyHooks, diags := expanders.StringList(ctx, terraformConfigModel.PreApplyHooks) + if diags.HasError() { + return nil, diags + } + terraformConfig.PreApplyHooks = preApplyHooks + } + + // Convert PostApplyHooks + if !terraformConfigModel.PostApplyHooks.IsNull() && !terraformConfigModel.PostApplyHooks.IsUnknown() { + postApplyHooks, diags := expanders.StringList(ctx, terraformConfigModel.PostApplyHooks) + if diags.HasError() { + return nil, diags + } + terraformConfig.PostApplyHooks = postApplyHooks + } + + return terraformConfig, diagn +} + +func convertDeploymentPlatformConfigToAPI(ctx context.Context, deploymentConfigObj types.Object) (*workflowtemplaterevisions.DeploymentPlatformConfig, diag.Diagnostics) { + var deploymentModel DeploymentPlatformConfigModel + diags := deploymentConfigObj.As(ctx, &deploymentModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + + deploymentConfig := &workflowtemplaterevisions.DeploymentPlatformConfig{ + Kind: workflowtemplaterevisions.DeploymentPlatformConfigKindEnum(deploymentModel.Kind.ValueString()), + } + + // Convert config + if !deploymentModel.Config.IsNull() && !deploymentModel.Config.IsUnknown() { + var configModel DeploymentPlatformConfigConfigModel + diags := deploymentModel.Config.As(ctx, &configModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + + deploymentConfig.Config = workflowtemplaterevisions.DeploymentPlatformConfigConfig{ + IntegrationId: configModel.IntegrationId.ValueString(), + ProfileName: configModel.ProfileName.ValueStringPointer(), + } + } + + return deploymentConfig, nil +} + +func convertMountPointsListToAPI(ctx context.Context, mountPointsList types.List) ([]sgsdkgo.MountPoint, diag.Diagnostics) { + var mountPointModels []MountPointModel + diags := mountPointsList.ElementsAs(ctx, &mountPointModels, false) + if diags.HasError() { + return nil, diags + } + + mountPoints := make([]sgsdkgo.MountPoint, len(mountPointModels)) + for i, mp := range mountPointModels { + mountPoints[i] = sgsdkgo.MountPoint{ + Source: mp.Source.ValueString(), + Target: mp.Target.ValueString(), + ReadOnly: mp.ReadOnly.ValueBoolPointer(), + } + } + return mountPoints, nil +} + +func convertWfStepsConfigListToAPI(ctx context.Context, wfStepsConfigList types.List) ([]sgsdkgo.WfStepsConfig, diag.Diagnostics) { + var wfStepModels []WfStepsConfigModel + diags := wfStepsConfigList.ElementsAs(ctx, &wfStepModels, false) + if diags.HasError() { + return nil, diags + } + + wfStepsConfigs := make([]sgsdkgo.WfStepsConfig, len(wfStepModels)) + for i, wfStep := range wfStepModels { + wfStepsConfigs[i] = sgsdkgo.WfStepsConfig{ + Name: wfStep.Name.ValueString(), + Approval: wfStep.Approval.ValueBoolPointer(), + Timeout: expanders.IntPtr(wfStep.Timeout.ValueInt64Pointer()), + WfStepTemplateId: wfStep.WfStepTemplateId.ValueStringPointer(), + CmdOverride: wfStep.CmdOverride.ValueStringPointer(), + } + + // Handle EnvironmentVariables + // TODO: replace this with the convertEnvironmentVariablesToAPI + if !wfStep.EnvironmentVariables.IsNull() && !wfStep.EnvironmentVariables.IsUnknown() { + var envVarModels []EnvironmentVariableModel + diags := wfStep.EnvironmentVariables.ElementsAs(ctx, &envVarModels, false) + if diags.HasError() { + return nil, diags + } + + envVars := make([]sgsdkgo.EnvVars, len(envVarModels)) + for j, envVar := range envVarModels { + var configModel EnvironmentVariableConfigModel + diags := envVar.Config.As(ctx, &configModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + envVars[j] = sgsdkgo.EnvVars{ + Kind: sgsdkgo.EnvVarsKindEnum(envVar.Kind.ValueString()), + Config: &sgsdkgo.EnvVarConfig{ + VarName: configModel.VarName.ValueString(), + SecretId: configModel.SecretId.ValueStringPointer(), + TextValue: configModel.TextValue.ValueStringPointer(), + }, + } + } + wfStepsConfigs[i].EnvironmentVariables = envVars + } + + // Handle MountPoints + if !wfStep.MountPoints.IsNull() && !wfStep.MountPoints.IsUnknown() { + mountPoints, diags := convertMountPointsListToAPI(ctx, wfStep.MountPoints) + if diags.HasError() { + return nil, diags + } + wfStepsConfigs[i].MountPoints = mountPoints + } + + // Handle WfStepInputData + if !wfStep.WfStepInputData.IsNull() && !wfStep.WfStepInputData.IsUnknown() { + var inputDataModel WfStepInputDataModel + diags := wfStep.WfStepInputData.As(ctx, &inputDataModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + wfStepsConfigs[i].WfStepInputData = &sgsdkgo.WfStepInputData{ + SchemaType: sgsdkgo.WfStepInputDataSchemaTypeEnum(inputDataModel.SchemaType.ValueString()), + Data: parseJSONToMap(inputDataModel.Data.ValueString()), + } + } + } + + return wfStepsConfigs, nil +} + +// convertEnvironmentVariablesToAPI converts a list of terraform EnvironmentVariableModel to API EnvVars +func convertEnvironmentVariablesToAPI(ctx context.Context, envVarsList types.List) ([]sgsdkgo.EnvVars, diag.Diagnostics) { + if envVarsList.IsNull() || envVarsList.IsUnknown() { + return nil, nil + } + + var envVarModels []EnvironmentVariableModel + diags := envVarsList.ElementsAs(ctx, &envVarModels, false) + if diags.HasError() { + return nil, diags + } + + envVars := make([]sgsdkgo.EnvVars, len(envVarModels)) + for i, envVar := range envVarModels { + var configModel EnvironmentVariableConfigModel + configDiags := envVar.Config.As(ctx, &configModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if configDiags.HasError() { + return nil, configDiags + } + + envVars[i] = sgsdkgo.EnvVars{ + Kind: sgsdkgo.EnvVarsKindEnum(envVar.Kind.ValueString()), + Config: &sgsdkgo.EnvVarConfig{ + VarName: configModel.VarName.ValueString(), + SecretId: configModel.SecretId.ValueStringPointer(), + TextValue: configModel.TextValue.ValueStringPointer(), + }, + } + } + + return envVars, nil +} + +// convertWfStepInputDataToAPI converts terraform WfStepInputDataModel to API WfStepInputData +func convertWfStepInputDataToAPI(ctx context.Context, inputDataObj types.Object) (*sgsdkgo.WfStepInputData, diag.Diagnostics) { + if inputDataObj.IsNull() || inputDataObj.IsUnknown() { + return nil, nil + } + + var inputDataModel WfStepInputDataModel + diags := inputDataObj.As(ctx, &inputDataModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + + return &sgsdkgo.WfStepInputData{ + SchemaType: sgsdkgo.WfStepInputDataSchemaTypeEnum(inputDataModel.SchemaType.ValueString()), + Data: parseJSONToMap(inputDataModel.Data.ValueString()), + }, nil +} + +// convertWfStepsConfigToAPI converts a single terraform WfStepsConfigModel to API WfStepsConfig +func convertWfStepsConfigToAPI(ctx context.Context, wfStepModel WfStepsConfigModel) (*sgsdkgo.WfStepsConfig, diag.Diagnostics) { + diags := diag.Diagnostics{} + + wfStepConfig := &sgsdkgo.WfStepsConfig{ + Name: wfStepModel.Name.ValueString(), + Approval: wfStepModel.Approval.ValueBoolPointer(), + Timeout: expanders.IntPtr(wfStepModel.Timeout.ValueInt64Pointer()), + WfStepTemplateId: wfStepModel.WfStepTemplateId.ValueStringPointer(), + CmdOverride: wfStepModel.CmdOverride.ValueStringPointer(), + } + + // Handle EnvironmentVariables + if !wfStepModel.EnvironmentVariables.IsNull() && !wfStepModel.EnvironmentVariables.IsUnknown() { + envVars, envDiags := convertEnvironmentVariablesToAPI(ctx, wfStepModel.EnvironmentVariables) + if envDiags.HasError() { + return nil, envDiags + } + wfStepConfig.EnvironmentVariables = envVars + } + + // Handle MountPoints + if !wfStepModel.MountPoints.IsNull() && !wfStepModel.MountPoints.IsUnknown() { + mountPoints, mountDiags := convertMountPointsListToAPI(ctx, wfStepModel.MountPoints) + if mountDiags.HasError() { + return nil, mountDiags + } + wfStepConfig.MountPoints = mountPoints + } + + // Handle WfStepInputData + if !wfStepModel.WfStepInputData.IsNull() && !wfStepModel.WfStepInputData.IsUnknown() { + inputData, inputDiags := convertWfStepInputDataToAPI(ctx, wfStepModel.WfStepInputData) + if inputDiags.HasError() { + return nil, inputDiags + } + wfStepConfig.WfStepInputData = inputData + } + + return wfStepConfig, diags +} + +// convertWfStepInputDataFromAPI converts API WfStepInputData to terraform WfStepInputDataModel +func convertWfStepInputDataFromAPI(ctx context.Context, inputData *sgsdkgo.WfStepInputData) (types.Object, diag.Diagnostics) { + nullObject := types.ObjectNull(WfStepInputDataModel{}.AttributeTypes()) + if inputData == nil { + return nullObject, nil + } + + // Convert map back to JSON string + dataJSON, err := json.Marshal(inputData.Data) + if err != nil { + dataJSON = []byte("{}") + } + + inputDataModel := WfStepInputDataModel{ + SchemaType: flatteners.String((string)(inputData.SchemaType)), + Data: types.StringValue(string(dataJSON)), + } + + inputDataObj, diags := types.ObjectValueFrom(ctx, WfStepInputDataModel{}.AttributeTypes(), inputDataModel) + return inputDataObj, diags +} + +// convertWfStepsConfigFromAPI converts API WfStepsConfig to terraform WfStepsConfigModel +func convertWfStepsConfigFromAPI(ctx context.Context, wfStepConfig *sgsdkgo.WfStepsConfig) (types.Object, diag.Diagnostics) { + nullObject := types.ObjectNull(WfStepsConfigModel{}.AttributeTypes()) + if wfStepConfig == nil { + return nullObject, nil + } + + wfStepModel := WfStepsConfigModel{ + Name: flatteners.String(wfStepConfig.Name), + Approval: flatteners.BoolPtr(wfStepConfig.Approval), + Timeout: flatteners.Int64Ptr(wfStepConfig.Timeout), + WfStepTemplateId: flatteners.StringPtr(wfStepConfig.WfStepTemplateId), + CmdOverride: flatteners.StringPtr(wfStepConfig.CmdOverride), + } + + // Handle EnvironmentVariables + envVarsList, envDiags := convertEnvironmentVariablesFromAPI(ctx, wfStepConfig.EnvironmentVariables) + if envDiags.HasError() { + return nullObject, envDiags + } + wfStepModel.EnvironmentVariables = envVarsList + + // Handle MountPoints + mountPoints, mountDiags := convertMountPointListFromAPI(ctx, wfStepConfig.MountPoints) + if mountDiags.HasError() { + return nullObject, mountDiags + } + wfStepModel.MountPoints = mountPoints + + // Handle WfStepInputData + inputData, inputDiags := convertWfStepInputDataFromAPI(ctx, wfStepConfig.WfStepInputData) + if inputDiags.HasError() { + return nullObject, inputDiags + } + wfStepModel.WfStepInputData = inputData + + wfStepObj, diags := types.ObjectValueFrom(ctx, WfStepsConfigModel{}.AttributeTypes(), wfStepModel) + return wfStepObj, diags +} + +// convertWfStepsConfigListFromAPI converts API WfStepsConfig list to terraform WfStepsConfigModel list +func convertWfStepsConfigListFromAPI(ctx context.Context, wfStepsConfigList []sgsdkgo.WfStepsConfig) (types.List, diag.Diagnostics) { + if wfStepsConfigList == nil { + return types.ListNull(types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}), nil + } + + wfStepModels := make([]WfStepsConfigModel, len(wfStepsConfigList)) + for i, wfStep := range wfStepsConfigList { + wfStepObj, diags := convertWfStepsConfigFromAPI(ctx, &wfStep) + if diags.HasError() { + return types.ListNull(types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}), diags + } + + // Extract the WfStepsConfigModel from the object + var wfStepModel WfStepsConfigModel + objDiags := wfStepObj.As(ctx, &wfStepModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if objDiags.HasError() { + return types.ListNull(types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}), objDiags + } + + wfStepModels[i] = wfStepModel + } + + wfStepsList, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: WfStepsConfigModel{}.AttributeTypes()}, wfStepModels) + return wfStepsList, diags +} + +// convertRunnerConstraintsFromAPI converts API RunnerConstraints to terraform types.Object +func convertRunnerConstraintsFromAPI(ctx context.Context, runnerConstraints *sgsdkgo.RunnerConstraints) (types.Object, diag.Diagnostics) { + nullObject := types.ObjectNull(RunnerConstraintsModel{}.AttributeTypes()) + if runnerConstraints == nil { + return nullObject, nil + } + + namesList, diags := flatteners.ListOfStringToTerraformList(runnerConstraints.Names) + if diags.HasError() { + return nullObject, diags + } + + model := RunnerConstraintsModel{ + Type: flatteners.String(string(runnerConstraints.Type)), + Names: namesList, + } + + obj, diags := types.ObjectValueFrom(ctx, RunnerConstraintsModel{}.AttributeTypes(), model) + if diags.HasError() { + return nullObject, diags + } + + return obj, nil +} + +// convertUserSchedulesFromAPI converts API UserSchedules to terraform types.List +func convertUserSchedulesFromAPI(ctx context.Context, userSchedules []workflowtemplaterevisions.UserSchedules) (types.List, diag.Diagnostics) { + nullList := types.ListNull(types.ObjectType{AttrTypes: UserSchedulesModel{}.AttributeTypes()}) + if userSchedules == nil { + return nullList, nil + } + + models := make([]UserSchedulesModel, len(userSchedules)) + for i, us := range userSchedules { + models[i] = UserSchedulesModel{ + Cron: flatteners.String(us.Cron), + State: flatteners.String(string(us.State)), + Desc: flatteners.StringPtr(us.Desc), + Name: flatteners.StringPtr(us.Name), + } + } + + list, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: UserSchedulesModel{}.AttributeTypes()}, models) + return list, diags +} + +// BuildAPIModelToWorkflowTemplateRevisionModel converts API response to Terraform model +func BuildAPIModelToWorkflowTemplateRevisionModel(ctx context.Context, apiResponse *workflowtemplaterevisions.ReadWorkflowTemplateRevisionModel) (*WorkflowTemplateRevisionResourceModel, diag.Diagnostics) { + if apiResponse == nil { + return nil, nil + } + + model := &WorkflowTemplateRevisionResourceModel{ + Id: flatteners.StringPtr(apiResponse.Id), + LongDescription: flatteners.StringPtr(apiResponse.LongDescription), + Alias: flatteners.String(apiResponse.Alias), + Notes: flatteners.String(apiResponse.Notes), + SourceConfigKind: flatteners.StringPtr((*string)(apiResponse.SourceConfigKind)), + IsPublic: flatteners.StringPtr((*string)(apiResponse.IsPublic)), + } + + // handle deprecation + deprecationTerraType, diags := convertDeprecationFromAPI(ctx, apiResponse.Deprecation) + if diags.HasError() { + return nil, diags + } + model.Deprecation = deprecationTerraType + + // Handle Tags + tagsTerraType, diags := flatteners.ListOfStringToTerraformList(apiResponse.Tags) + if diags.HasError() { + return nil, diags + } + model.Tags = tagsTerraType + + // Handle ContextTags + if apiResponse.ContextTags != nil { + contextTagsValue, diags := types.MapValueFrom(ctx, types.StringType, apiResponse.ContextTags) + if diags.HasError() { + return nil, diags + } + model.ContextTags = contextTagsValue + } else { + model.ContextTags = types.MapNull(types.StringType) + } + + // Handle Approvers + approverstTerraType, diags := flatteners.ListOfStringToTerraformList(apiResponse.Approvers) + if diags.HasError() { + return nil, diags + } + model.Approvers = approverstTerraType + + // Handle NumberOfApprovalsRequired + if apiResponse.NumberOfApprovalsRequired != nil { + model.NumberOfApprovalsRequired = flatteners.Int64Ptr(apiResponse.NumberOfApprovalsRequired) + } + + // Handle UserJobCPU and UserJobMemory + if apiResponse.UserJobCPU != nil { + model.UserJobCPU = flatteners.Int64Ptr(apiResponse.UserJobCPU) + } + + if apiResponse.UserJobMemory != nil { + model.UserJobMemory = flatteners.Int64Ptr(apiResponse.UserJobMemory) + } + + // Handle RuntimeSource + runtimeSource, diags := convertRuntimeSourceFromAPI(ctx, apiResponse.RuntimeSource) + if diags.HasError() { + return nil, diags + } + model.RuntimeSource = runtimeSource + + // Handle TerraformConfig + terraformConfig, diags := convertTerraformConfigFromAPI(ctx, apiResponse.TerraformConfig) + if diags.HasError() { + return nil, diags + } + model.TerraformConfig = terraformConfig + + // Handle DeploymentPlatformConfig + deploymentConfig, diags := convertDeploymentPlatformConfigFromAPI(ctx, apiResponse.DeploymentPlatformConfig) + if diags.HasError() { + return nil, diags + } + model.DeploymentPlatformConfig = deploymentConfig + + // Handle EnvironmentVariables + envVars, diags := convertEnvironmentVariablesFromAPI(ctx, apiResponse.EnvironmentVariables) + if diags.HasError() { + return nil, diags + } + model.EnvironmentVariables = envVars + + // Handle InputSchemas + inputSchemas, diags := convertInputSchemasFromAPI(ctx, apiResponse.InputSchemas) + if diags.HasError() { + return nil, diags + } + model.InputSchemas = inputSchemas + + // Handle MiniSteps + miniSteps, diags := convertMinistepsFromAPI(ctx, apiResponse.Ministeps) + if diags.HasError() { + return nil, diags + } + model.MiniSteps = miniSteps + + // Handle RunnerConstraints + runnerConstraints, diags := convertRunnerConstraintsFromAPI(ctx, apiResponse.RunnerConstraints) + if diags.HasError() { + return nil, diags + } + model.RunnerConstraints = runnerConstraints + + // Handle UserSchedules + userSchedules, diags := convertUserSchedulesFromAPI(ctx, apiResponse.UserSchedules) + if diags.HasError() { + return nil, diags + } + model.UserSchedules = userSchedules + + // Handle WfStepsConfig + wfStepsConfig, diags := convertWfStepsConfigListFromAPI(ctx, apiResponse.WfStepsConfig) + if diags.HasError() { + return nil, diags + } + model.WfStepsConfig = wfStepsConfig + + return model, nil +} + +// Helper functions for reverse conversion (API to Terraform) + +func convertDeprecationFromAPI(ctx context.Context, deprecationAPIModle *workflowtemplaterevisions.Deprecation) (types.Object, diag.Diagnostics) { + nullObject := types.ObjectNull(DeprecationModel{}.AttributeTypes()) + if deprecationAPIModle == nil { + return nullObject, nil + } + + deprecationTerraModel := DeprecationModel{ + EffectiveDate: flatteners.StringPtr(deprecationAPIModle.EffectiveDate), + Message: flatteners.StringPtr(deprecationAPIModle.Message), + } + + deprecationTerraType, diags := types.ObjectValueFrom(ctx, DeprecationModel{}.AttributeTypes(), deprecationTerraModel) + if diags.HasError() { + return nullObject, diags + } + + return deprecationTerraType, nil +} + +func convertRuntimeSourceFromAPI(ctx context.Context, runtimeSource *workflowtemplates.RuntimeSource) (types.Object, diag.Diagnostics) { + nullObject := types.ObjectNull(RuntimeSourceModel{}.AttributeTypes()) + if runtimeSource == nil { + return nullObject, nil + } + + runtimeSourceModel := RuntimeSourceModel{ + SourceConfigDestKind: flatteners.StringPtr((*string)(runtimeSource.SourceConfigDestKind)), + } + + if runtimeSource.Config != nil { + configModel := RuntimeSourceConfigModel{ + IsPrivate: flatteners.BoolPtr(runtimeSource.Config.IsPrivate), + Auth: flatteners.StringPtr(runtimeSource.Config.Auth), + GitCoreAutoCrlf: flatteners.BoolPtr(runtimeSource.Config.GitCoreAutoCRLF), + GitSparseCheckoutConfig: flatteners.StringPtr(runtimeSource.Config.GitSparseCheckoutConfig), + IncludeSubModule: flatteners.BoolPtr(runtimeSource.Config.IncludeSubModule), + Ref: flatteners.StringPtr(runtimeSource.Config.Ref), + Repo: flatteners.String(runtimeSource.Config.Repo), + WorkingDir: flatteners.StringPtr(runtimeSource.Config.WorkingDir), + } + + configObj, diags := types.ObjectValueFrom(ctx, RuntimeSourceConfigModel{}.AttributeTypes(), configModel) + if diags.HasError() { + return nullObject, diags + } + + runtimeSourceModel.Config = configObj + } else { + runtimeSourceModel.Config = types.ObjectNull(RuntimeSourceConfigModel{}.AttributeTypes()) + } + + runtimeSourceObj, diags := types.ObjectValueFrom(ctx, RuntimeSourceModel{}.AttributeTypes(), runtimeSourceModel) + if diags.HasError() { + return nullObject, diags + } + + return runtimeSourceObj, nil +} + +func convertMountPointListFromAPI(ctx context.Context, mountPointListAPI []sgsdkgo.MountPoint) (types.List, diag.Diagnostics) { + nullObject := types.ListNull(types.ObjectType{AttrTypes: MountPointModel{}.AttributeTypes()}) + if mountPointListAPI == nil { + return nullObject, nil + } + + mountPointListTerraModel := []MountPointModel{} + for _, mountPointAPIModel := range mountPointListAPI { + mountPointTerraModel := MountPointModel{ + Source: flatteners.String(mountPointAPIModel.Source), + Target: flatteners.String(mountPointAPIModel.Target), + ReadOnly: flatteners.BoolPtr(mountPointAPIModel.ReadOnly), + } + mountPointListTerraModel = append(mountPointListTerraModel, mountPointTerraModel) + } + + mountPointTerraType, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: MountPointModel{}.AttributeTypes()}, &mountPointListTerraModel) + if diags.HasError() { + return nullObject, diags + } + + return mountPointTerraType, nil +} + +func convertTerraformConfigFromAPI(ctx context.Context, terraformConfig *sgsdkgo.TerraformConfig) (types.Object, diag.Diagnostics) { + nullObject := types.ObjectNull(TerraformConfigModel{}.AttributeTypes()) + if terraformConfig == nil { + return nullObject, nil + } + + terraformConfigModel := TerraformConfigModel{ + TerraformVersion: flatteners.StringPtr(terraformConfig.TerraformVersion), + DriftCheck: flatteners.BoolPtr(terraformConfig.DriftCheck), + DriftCron: flatteners.StringPtr(terraformConfig.DriftCron), + ManagedTerraformState: flatteners.BoolPtr(terraformConfig.ManagedTerraformState), + ApprovalPreApply: flatteners.BoolPtr(terraformConfig.ApprovalPreApply), + TerraformPlanOptions: flatteners.StringPtr(terraformConfig.TerraformPlanOptions), + TerraformInitOptions: flatteners.StringPtr(terraformConfig.TerraformInitOptions), + Timeout: flatteners.Int64Ptr(terraformConfig.Timeout), + RunPreInitHooksOnDrift: flatteners.BoolPtr(terraformConfig.RunPreInitHooksOnDrift), + } + + // TODO: Convert nested fields (TerraformBinPath, WfStepsConfigs, Hooks, Providers) + + // terraform bin path + terraformBinTerraType, diags := convertMountPointListFromAPI(ctx, terraformConfig.TerraformBinPath) + if diags.HasError() { + return nullObject, diags + } + terraformConfigModel.TerraformBinPath = terraformBinTerraType + + // post apply wf steps config + postApplyWfStepsConfig, diags := convertWfStepsConfigListFromAPI(ctx, terraformConfig.PostApplyWfStepsConfig) + if diags.HasError() { + return nullObject, diags + } + terraformConfigModel.PostApplyWfStepsConfig = postApplyWfStepsConfig + + // pre apply wf steps config + preApplyWfStepsConfig, diags := convertWfStepsConfigListFromAPI(ctx, terraformConfig.PreApplyWfStepsConfig) + if diags.HasError() { + return nullObject, diags + } + terraformConfigModel.PreApplyWfStepsConfig = preApplyWfStepsConfig + + // pre plan wf steps config + prePlanWfStepsConfig, diags := convertWfStepsConfigListFromAPI(ctx, terraformConfig.PrePlanWfStepsConfig) + if diags.HasError() { + return nullObject, diags + } + terraformConfigModel.PrePlanWfStepsConfig = prePlanWfStepsConfig + + // post plan wf steps config + postPlanWfStepsConfig, diags := convertWfStepsConfigListFromAPI(ctx, terraformConfig.PostPlanWfStepsConfig) + if diags.HasError() { + return nullObject, diags + } + terraformConfigModel.PostPlanWfStepsConfig = postPlanWfStepsConfig + + // pre init hooks + preInitHooks, diags := flatteners.ListOfStringToTerraformList(terraformConfig.PreInitHooks) + if diags.HasError() { + return nullObject, diags + } + terraformConfigModel.PreInitHooks = preInitHooks + + // pre plan hooks + prePlanHooks, diags := flatteners.ListOfStringToTerraformList(terraformConfig.PrePlanHooks) + if diags.HasError() { + return nullObject, diags + } + terraformConfigModel.PrePlanHooks = prePlanHooks + + // post plan hooks + postPlanHooks, diags := flatteners.ListOfStringToTerraformList(terraformConfig.PostPlanHooks) + if diags.HasError() { + return nullObject, diags + } + terraformConfigModel.PostPlanHooks = postPlanHooks + + // pre apply hooks + preApplyHooks, diags := flatteners.ListOfStringToTerraformList(terraformConfig.PreApplyHooks) + if diags.HasError() { + return nullObject, diags + } + terraformConfigModel.PreApplyHooks = preApplyHooks + + // post apply hooks + postApplyHooks, diags := flatteners.ListOfStringToTerraformList(terraformConfig.PostApplyHooks) + if diags.HasError() { + return nullObject, diags + } + terraformConfigModel.PostApplyHooks = postApplyHooks + + terraformConfigObj, diags := types.ObjectValueFrom(ctx, TerraformConfigModel{}.AttributeTypes(), terraformConfigModel) + if diags.HasError() { + return nullObject, diags + } + + return terraformConfigObj, nil +} + +func convertDeploymentPlatformConfigFromAPI(ctx context.Context, deploymentConfig *workflowtemplaterevisions.DeploymentPlatformConfig) (types.Object, diag.Diagnostics) { + nullObject := types.ObjectNull(DeploymentPlatformConfigModel{}.AttributeTypes()) + if deploymentConfig == nil { + return nullObject, nil + } + + configModel := DeploymentPlatformConfigConfigModel{ + IntegrationId: flatteners.String(deploymentConfig.Config.IntegrationId), + ProfileName: flatteners.StringPtr(deploymentConfig.Config.ProfileName), + } + + configObj, diags := types.ObjectValueFrom(ctx, DeploymentPlatformConfigConfigModel{}.AttributeTypes(), configModel) + if diags.HasError() { + return nullObject, diags + } + + deploymentConfigModel := DeploymentPlatformConfigModel{ + Kind: flatteners.String(string(deploymentConfig.Kind)), + Config: configObj, + } + + deploymentConfigObj, diags := types.ObjectValueFrom(ctx, DeploymentPlatformConfigModel{}.AttributeTypes(), deploymentConfigModel) + if diags.HasError() { + return nullObject, diags + } + + return deploymentConfigObj, nil +} + +// Conversion helpers for EnvironmentVariables (Flatteners - API to Terraform) +func convertEnvironmentVariablesFromAPI(ctx context.Context, envVars []sgsdkgo.EnvVars) (types.List, diag.Diagnostics) { + nullObject := types.ListNull(types.ObjectType{AttrTypes: EnvironmentVariableModel{}.AttributeTypes()}) + + if envVars == nil { + return nullObject, nil + } + + envVarModels := make([]EnvironmentVariableModel, len(envVars)) + for i, envVar := range envVars { + configModel := EnvironmentVariableConfigModel{ + VarName: flatteners.String(envVar.Config.VarName), + SecretId: flatteners.StringPtr(envVar.Config.SecretId), + TextValue: flatteners.StringPtr(envVar.Config.TextValue), + } + + configObj, diags := types.ObjectValueFrom(ctx, EnvironmentVariableConfigModel{}.AttributeTypes(), configModel) + if diags.HasError() { + return types.ListNull(types.ObjectType{AttrTypes: EnvironmentVariableModel{}.AttributeTypes()}), diags + } + + envVarModels[i] = EnvironmentVariableModel{ + Config: configObj, + Kind: flatteners.String(string(envVar.Kind)), + } + } + + envVarList, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: EnvironmentVariableModel{}.AttributeTypes()}, envVarModels) + if diags.HasError() { + return types.ListNull(types.ObjectType{AttrTypes: EnvironmentVariableModel{}.AttributeTypes()}), diags + } + + return envVarList, nil +} + +// Conversion helpers for InputSchemas (Flatteners - API to Terraform) +func convertInputSchemasFromAPI(ctx context.Context, inputSchemas []sgsdkgo.InputSchemas) (types.List, diag.Diagnostics) { + if inputSchemas == nil || len(inputSchemas) == 0 { + return types.ListNull(types.ObjectType{AttrTypes: InputSchemaModel{}.AttributeTypes()}), nil + } + + inputSchemaModels := make([]InputSchemaModel, len(inputSchemas)) + for i, schema := range inputSchemas { + inputSchemaModels[i] = InputSchemaModel{ + Type: flatteners.String(string(schema.Type)), + EncodedData: flatteners.StringPtr(schema.EncodedData), + UISchemaData: flatteners.StringPtr(schema.UiSchemaData), + } + } + + inputSchemaList, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: InputSchemaModel{}.AttributeTypes()}, inputSchemaModels) + if diags.HasError() { + return types.ListNull(types.ObjectType{AttrTypes: InputSchemaModel{}.AttributeTypes()}), diags + } + + return inputSchemaList, nil +} + +// Conversion helpers for MiniSteps (Flatteners - API to Terraform) +func convertMinistepsFromAPI(ctx context.Context, ministeps *workflowtemplaterevisions.Ministeps) (types.Object, diag.Diagnostics) { + var diags diag.Diagnostics + nullObject := types.ObjectNull(MinistepsModel{}.AttributeTypes()) + if ministeps == nil { + return nullObject, nil + } + + ministepsModel := MinistepsModel{} + + // Convert Notifications + if ministeps.Notifications != nil { + notificationsModel := MinistepsNotificationsModel{} + + if ministeps.Notifications.Email != nil { + emailModel := MinistepsEmailModel{} + emailModel.ApprovalRequired, diags = convertNotificationRecipientsFromAPI(ctx, ministeps.Notifications.Email.APPROVAL_REQUIRED) + if diags.HasError() { + return nullObject, diags + } + + emailModel.Cancelled, diags = convertNotificationRecipientsFromAPI(ctx, ministeps.Notifications.Email.CANCELLED) + if diags.HasError() { + return nullObject, diags + } + + emailModel.Completed, diags = convertNotificationRecipientsFromAPI(ctx, ministeps.Notifications.Email.COMPLETED) + if diags.HasError() { + return nullObject, diags + } + + emailModel.DriftDetected, diags = convertNotificationRecipientsFromAPI(ctx, ministeps.Notifications.Email.DRIFT_DETECTED) + if diags.HasError() { + return nullObject, diags + } + + emailModel.Errored, diags = convertNotificationRecipientsFromAPI(ctx, ministeps.Notifications.Email.ERRORED) + if diags.HasError() { + return nullObject, diags + } + + emailObj, diags := types.ObjectValueFrom(ctx, MinistepsEmailModel{}.AttributeTypes(), emailModel) + if diags.HasError() { + return nullObject, diags + } + notificationsModel.Email = emailObj + } else { + notificationsModel.Email = types.ObjectNull(MinistepsEmailModel{}.AttributeTypes()) + } + + notificationsObj, diags := types.ObjectValueFrom(ctx, MinistepsNotificationsModel{}.AttributeTypes(), notificationsModel) + if diags.HasError() { + return nullObject, diags + } + ministepsModel.Notifications = notificationsObj + } else { + ministepsModel.Notifications = types.ObjectNull(MinistepsNotificationsModel{}.AttributeTypes()) + } + + // Convert Webhooks + if ministeps.Webhooks != nil { + webhooksModel := MinistepsWebhooksContainerModel{} + webhooksModel.ApprovalRequired, diags = convertWebhookFromAPI(ctx, ministeps.Webhooks.APPROVAL_REQUIRED) + if diags.HasError() { + return nullObject, diags + } + + webhooksModel.Cancelled, diags = convertWebhookFromAPI(ctx, ministeps.Webhooks.CANCELLED) + if diags.HasError() { + return nullObject, diags + } + + webhooksModel.Completed, diags = convertWebhookFromAPI(ctx, ministeps.Webhooks.COMPLETED) + if diags.HasError() { + return nullObject, diags + } + + webhooksModel.DriftDetected, diags = convertWebhookFromAPI(ctx, ministeps.Webhooks.DRIFT_DETECTED) + if diags.HasError() { + return nullObject, diags + } + + webhooksModel.Errored, diags = convertWebhookFromAPI(ctx, ministeps.Webhooks.ERRORED) + if diags.HasError() { + return nullObject, diags + } + + webhooksObj, diags := types.ObjectValueFrom(ctx, MinistepsWebhooksContainerModel{}.AttributeTypes(), webhooksModel) + if diags.HasError() { + return nullObject, diags + } + ministepsModel.Webhooks = webhooksObj + } else { + ministepsModel.Webhooks = types.ObjectNull(MinistepsWebhooksContainerModel{}.AttributeTypes()) + } + + // Convert WfChaining + if ministeps.WfChaining != nil { + wfChainingModel := MinistepsWfChainingContainerModel{} + + wfChainingModel.Completed, diags = convertWorkflowChainingFromAPI(ctx, ministeps.WfChaining.COMPLETED) + if diags.HasError() { + return nullObject, diags + } + wfChainingModel.Errored, diags = convertWorkflowChainingFromAPI(ctx, ministeps.WfChaining.ERRORED) + + wfChainingObj, diags := types.ObjectValueFrom(ctx, MinistepsWfChainingContainerModel{}.AttributeTypes(), wfChainingModel) + if diags.HasError() { + return nullObject, diags + } + ministepsModel.WfChaining = wfChainingObj + } else { + ministepsModel.WfChaining = types.ObjectNull(MinistepsWfChainingContainerModel{}.AttributeTypes()) + } + + ministepsObj, diags := types.ObjectValueFrom(ctx, MinistepsModel{}.AttributeTypes(), ministepsModel) + if diags.HasError() { + return nullObject, diags + } + + return ministepsObj, nil +} + +// Helper function to convert notification recipients +func convertNotificationRecipientsFromAPI(ctx context.Context, recipients []workflowtemplaterevisions.MinistepsNotificationRecepients) (types.List, diag.Diagnostics) { + nullObj := types.ListNull(types.ObjectType{AttrTypes: MinistepsNotificationRecipientsModel{}.AttributeTypes()}) + if recipients == nil { + return nullObj, nil + } + + recepientsListTerraModel := []MinistepsNotificationRecipientsModel{} + for _, recepientList := range recipients { + recipients, diags := types.ListValueFrom(ctx, types.StringType, recepientList.Recipients) + if diags.HasError() { + return nullObj, diags + } + + recepientsTerraModel := MinistepsNotificationRecipientsModel{ + Recipients: recipients, + } + + recepientsListTerraModel = append(recepientsListTerraModel, recepientsTerraModel) + } + + obj, diags := types.ListValueFrom(ctx, types.ListType{ElemType: types.StringType}, recepientsListTerraModel) + if diags.HasError() { + return nullObj, diags + } + + return obj, nil +} + +// Helper function to convert webhooks +func convertWebhookFromAPI(ctx context.Context, webhooks []workflowtemplaterevisions.MinistepsWebhooksSchema) (types.List, diag.Diagnostics) { + nullObj := types.ListNull(types.ObjectType{AttrTypes: MinistepsWebhooksModel{}.AttributeTypes()}) + if webhooks == nil { + return nullObj, nil + } + + ministepsWebhookTerraModel := []MinistepsWebhooksModel{} + for _, webhook := range webhooks { + ministepsWebhookSchemaModel := MinistepsWebhooksModel{ + WebhookName: flatteners.String(webhook.WebhookName), + WebhookUrl: flatteners.String(webhook.WebhookUrl), + WebhookSecret: flatteners.StringPtr(webhook.WebhookSecret), + } + + ministepsWebhookTerraModel = append(ministepsWebhookTerraModel, ministepsWebhookSchemaModel) + } + + ministepsWebhookTerraType, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: MinistepsWebhooksModel{}.AttributeTypes()}, ministepsWebhookTerraModel) + if diags.HasError() { + return nullObj, diags + } + + return ministepsWebhookTerraType, nil +} + +// Helper function to convert workflow chaining +func convertWorkflowChainingFromAPI(ctx context.Context, wfChainingList []workflowtemplaterevisions.MinistepsWfChainingSchema) (types.List, diag.Diagnostics) { + nullObj := types.ListNull(types.ObjectType{AttrTypes: MinistepsWorkflowChainingModel{}.AttributeTypes()}) + if wfChainingList == nil { + return nullObj, nil + } + + workflowChainingListTerraModel := []MinistepsWorkflowChainingModel{} + for _, wfChaining := range wfChainingList { + model := MinistepsWorkflowChainingModel{ + WorkflowGroupId: flatteners.String(wfChaining.WorkflowGroupId), + StackId: flatteners.StringPtr(wfChaining.StackId), + StackRunPayload: flatteners.StringPtr(wfChaining.StackRunPayload), + WorkflowId: flatteners.StringPtr(wfChaining.WorkflowId), + WorkflowRunPayload: flatteners.StringPtr(wfChaining.WorkflowRunPayload), + } + workflowChainingListTerraModel = append(workflowChainingListTerraModel, model) + } + + obj, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: MinistepsWorkflowChainingModel{}.AttributeTypes()}, workflowChainingListTerraModel) + if diags.HasError() { + return nullObj, diags + } + + return obj, nil +} + +// Conversion helpers for EnvironmentVariables (Expanders - Terraform to API) +// Conversion helpers for InputSchemas (Expanders - Terraform to API) +func convertInputSchemasToAPI(ctx context.Context, inputSchemasList types.List) ([]sgsdkgo.InputSchemas, diag.Diagnostics) { + if inputSchemasList.IsNull() || inputSchemasList.IsUnknown() { + return nil, nil + } + + var inputSchemaModels []InputSchemaModel + diags := inputSchemasList.ElementsAs(ctx, &inputSchemaModels, false) + if diags.HasError() { + return nil, diags + } + + inputSchemas := make([]sgsdkgo.InputSchemas, len(inputSchemaModels)) + for i, schema := range inputSchemaModels { + inputSchemas[i] = sgsdkgo.InputSchemas{ + Type: sgsdkgo.InputSchemasTypeEnum(schema.Type.ValueString()), + EncodedData: schema.EncodedData.ValueStringPointer(), + UiSchemaData: schema.UISchemaData.ValueStringPointer(), + } + } + + return inputSchemas, nil +} + +func convertMinistepsToAPI(ctx context.Context, ministepsObj types.Object) (*workflowtemplaterevisions.Ministeps, diag.Diagnostics) { + if ministepsObj.IsNull() || ministepsObj.IsUnknown() { + return nil, nil + } + + var ministepsModel MinistepsModel + diags := ministepsObj.As(ctx, &ministepsModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + + miniSteps := &workflowtemplaterevisions.Ministeps{} + + // Convert Notifications + if !ministepsModel.Notifications.IsNull() && !ministepsModel.Notifications.IsUnknown() { + var notificationsModel MinistepsNotificationsModel + diags := ministepsModel.Notifications.As(ctx, ¬ificationsModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + + miniSteps.Notifications = &workflowtemplaterevisions.MinistepsNotifications{} + + // Convert Email notifications + if !notificationsModel.Email.IsNull() && !notificationsModel.Email.IsUnknown() { + var emailModel MinistepsEmailModel + diags := notificationsModel.Email.As(ctx, &emailModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + + miniSteps.Notifications.Email = &workflowtemplaterevisions.MinistepsNotificationsEmail{} + miniSteps.Notifications.Email.APPROVAL_REQUIRED, diags = convertNotificationRecipientsToAPI(ctx, emailModel.ApprovalRequired) + if diags.HasError() { + return nil, diags + } + + miniSteps.Notifications.Email.CANCELLED, diags = convertNotificationRecipientsToAPI(ctx, emailModel.Cancelled) + if diags.HasError() { + return nil, diags + } + + miniSteps.Notifications.Email.COMPLETED, diags = convertNotificationRecipientsToAPI(ctx, emailModel.Completed) + if diags.HasError() { + return nil, diags + } + + miniSteps.Notifications.Email.DRIFT_DETECTED, diags = convertNotificationRecipientsToAPI(ctx, emailModel.DriftDetected) + if diags.HasError() { + return nil, diags + } + + miniSteps.Notifications.Email.ERRORED, diags = convertNotificationRecipientsToAPI(ctx, emailModel.Errored) + if diags.HasError() { + return nil, diags + } + } + } + + // Convert Webhooks + if !ministepsModel.Webhooks.IsNull() && !ministepsModel.Webhooks.IsUnknown() { + var webhooksModel MinistepsWebhooksContainerModel + diags := ministepsModel.Webhooks.As(ctx, &webhooksModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + + miniSteps.Webhooks = &workflowtemplaterevisions.MinistepsWebhooks{} + + miniSteps.Webhooks.APPROVAL_REQUIRED, diags = convertWebhookToAPI(ctx, webhooksModel.ApprovalRequired) + if diags.HasError() { + return nil, diags + } + miniSteps.Webhooks.CANCELLED, diags = convertWebhookToAPI(ctx, webhooksModel.Cancelled) + if diags.HasError() { + return nil, diags + } + miniSteps.Webhooks.COMPLETED, diags = convertWebhookToAPI(ctx, webhooksModel.Completed) + if diags.HasError() { + return nil, diags + } + miniSteps.Webhooks.DRIFT_DETECTED, diags = convertWebhookToAPI(ctx, webhooksModel.DriftDetected) + if diags.HasError() { + return nil, diags + } + miniSteps.Webhooks.ERRORED, diags = convertWebhookToAPI(ctx, webhooksModel.Errored) + if diags.HasError() { + return nil, diags + } + } + + // Convert WfChaining + if !ministepsModel.WfChaining.IsNull() && !ministepsModel.WfChaining.IsUnknown() { + var wfChainingModel MinistepsWfChainingContainerModel + diags := ministepsModel.WfChaining.As(ctx, &wfChainingModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return nil, diags + } + + miniSteps.WfChaining = &workflowtemplaterevisions.MinistepsWorkflowChaining{} + + miniSteps.WfChaining.COMPLETED, diags = convertWorkflowChainingToAPI(ctx, wfChainingModel.Completed) + if diags.HasError() { + return nil, diags + } + miniSteps.WfChaining.ERRORED, diags = convertWorkflowChainingToAPI(ctx, wfChainingModel.Errored) + if diags.HasError() { + return nil, diags + } + } + + return miniSteps, nil +} + +// Helper function to convert notification recipients to API +func convertNotificationRecipientsToAPI(ctx context.Context, recepientsObj types.List) ([]workflowtemplaterevisions.MinistepsNotificationRecepients, diag.Diagnostics) { + if recepientsObj.IsNull() || recepientsObj.IsUnknown() { + return nil, nil + } + + var recepientsListModel []MinistepsNotificationRecipientsModel + diags := recepientsObj.ElementsAs(ctx, &recepientsListModel, true) + if diags.HasError() { + return nil, diags + } + + notificationRecepients := []workflowtemplaterevisions.MinistepsNotificationRecepients{} + for _, recepientsModel := range recepientsListModel { + recepients, diags := expanders.StringList(ctx, recepientsModel.Recipients) + if diags.HasError() { + return nil, diags + } + + notificationRecepients = append(notificationRecepients, workflowtemplaterevisions.MinistepsNotificationRecepients{Recipients: recepients}) + } + + return notificationRecepients, nil +} + +// Helper function to convert webhook to API +func convertWebhookToAPI(ctx context.Context, webhookObj types.List) ([]workflowtemplaterevisions.MinistepsWebhooksSchema, diag.Diagnostics) { + if webhookObj.IsNull() || webhookObj.IsUnknown() { + return nil, nil + } + + var webhooksModel []MinistepsWebhooksModel + diags := webhookObj.ElementsAs(ctx, &webhooksModel, true) + if diags.HasError() { + return nil, diags + } + + var ministepsWebhooksList []workflowtemplaterevisions.MinistepsWebhooksSchema + for _, webhooksList := range webhooksModel { + webhookAPIModel := workflowtemplaterevisions.MinistepsWebhooksSchema{ + WebhookName: webhooksList.WebhookName.ValueString(), + WebhookUrl: webhooksList.WebhookUrl.ValueString(), + WebhookSecret: webhooksList.WebhookSecret.ValueStringPointer(), + } + + ministepsWebhooksList = append(ministepsWebhooksList, webhookAPIModel) + } + + return ministepsWebhooksList, nil +} + +// Helper function to convert workflow chaining to API +func convertWorkflowChainingToAPI(ctx context.Context, chainingObj types.List) ([]workflowtemplaterevisions.MinistepsWfChainingSchema, diag.Diagnostics) { + if chainingObj.IsNull() || chainingObj.IsUnknown() { + return nil, nil + } + + var chainingListModel []MinistepsWorkflowChainingModel + diags := chainingObj.ElementsAs(ctx, &chainingListModel, true) + if diags.HasError() { + return nil, diags + } + + var wfChainingAPIModel []workflowtemplaterevisions.MinistepsWfChainingSchema + for _, chainingModel := range chainingListModel { + wfChainingAPIModel = append(wfChainingAPIModel, workflowtemplaterevisions.MinistepsWfChainingSchema{ + WorkflowGroupId: chainingModel.WorkflowGroupId.ValueString(), + StackId: chainingModel.StackId.ValueStringPointer(), + StackRunPayload: chainingModel.StackRunPayload.ValueStringPointer(), + WorkflowId: chainingModel.WorkflowId.ValueStringPointer(), + WorkflowRunPayload: chainingModel.WorkflowRunPayload.ValueStringPointer(), + }) + } + + return wfChainingAPIModel, nil +} + +func parseJSONToMap(jsonStr string) map[string]interface{} { + var result map[string]interface{} + if jsonStr == "" { + return result + } + if err := json.Unmarshal([]byte(jsonStr), &result); err != nil { + return make(map[string]interface{}) + } + return result +} diff --git a/internal/resource/workflow_template_revision/resource.go b/internal/resource/workflow_template_revision/resource.go new file mode 100644 index 0000000..49c8fdc --- /dev/null +++ b/internal/resource/workflow_template_revision/resource.go @@ -0,0 +1,222 @@ +package workflowtemplaterevision + +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" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var ( + _ resource.Resource = &workflowTemplateRevisionResource{} + _ resource.ResourceWithConfigure = &workflowTemplateRevisionResource{} + _ resource.ResourceWithImportState = &workflowTemplateRevisionResource{} +) + +type workflowTemplateRevisionResource struct { + client *sgclient.Client + org_name string +} + +// NewResource is a helper function to simplify the provider implementation. +func NewResource() resource.Resource { + return &workflowTemplateRevisionResource{} +} + +// Metadata returns the resource type name. +func (r *workflowTemplateRevisionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_workflow_template_revision" +} + +// Configure adds the provider configured client to the resource. +func (r *workflowTemplateRevisionResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Add a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + 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 workflow template revision using its ID. +func (r *workflowTemplateRevisionResource) 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 *workflowTemplateRevisionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan WorkflowTemplateRevisionResourceModel + + 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 + } + + templateID := plan.TemplateId.ValueString() + + payload.OwnerOrg = fmt.Sprintf("/orgs/%s", r.org_name) + + createResp, err := r.client.WorkflowTemplatesRevisions.CreateWorkflowTemplateRevision(ctx, r.org_name, templateID, payload) + if err != nil { + resp.Diagnostics.AddError("failed to create template revision", err.Error()) + return + } + + // Set the ID from the create response + revisionID := createResp.Data.Revision.Id + + // Call read to get the full state since create response doesn't return all values + readResp, err := r.client.WorkflowTemplatesRevisions.ReadWorkflowTemplateRevision(ctx, r.org_name, revisionID) + if err != nil { + resp.Diagnostics.AddError("Error reading created workflow template revision", "Could not read the created workflow template revision: "+err.Error()) + return + } + + revisionModel, diags := BuildAPIModelToWorkflowTemplateRevisionModel(ctx, &readResp.Msg) + revisionModel.TemplateId = plan.TemplateId + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, &revisionModel)...) +} + +// Read refreshes the Terraform state with the latest data. +func (r *workflowTemplateRevisionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state WorkflowTemplateRevisionResourceModel + + 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 workflow template revision", "Revision ID is empty") + return + } + + readResp, err := r.client.WorkflowTemplatesRevisions.ReadWorkflowTemplateRevision(ctx, r.org_name, revisionID) + if err != nil { + resp.Diagnostics.AddError("Error reading workflow template revision", "Error in reading workflow template revision API call: "+err.Error()) + return + } + + if readResp == nil { + resp.Diagnostics.AddError("Error reading workflow template revision", "API response is empty") + return + } + + revisionModel, diags := BuildAPIModelToWorkflowTemplateRevisionModel(ctx, &readResp.Msg) + revisionModel.TemplateId = state.TemplateId + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &revisionModel)...) +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *workflowTemplateRevisionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan WorkflowTemplateRevisionResourceModel + var state WorkflowTemplateRevisionResourceModel + + 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() + + payload, diags := plan.ToUpdateAPIModel(ctx) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + ownerOrg := fmt.Sprintf("/orgs/%v", r.org_name) + payload.OwnerOrg = sgsdkgo.Optional(ownerOrg) + + _, err := r.client.WorkflowTemplatesRevisions.UpdateWorkflowTemplateRevision(ctx, r.org_name, revisionID, payload) + if err != nil { + tflog.Error(ctx, err.Error()) + resp.Diagnostics.AddError("Error updating workflow template revision", "Error in updating workflow 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.WorkflowTemplatesRevisions.ReadWorkflowTemplateRevision(ctx, r.org_name, revisionID) + if err != nil { + resp.Diagnostics.AddError("Error reading updated workflow template revision", "Could not read the updated workflow template revision: "+err.Error()) + return + } + + revisionModel, diags := BuildAPIModelToWorkflowTemplateRevisionModel(ctx, &readResp.Msg) + revisionModel.TemplateId = state.TemplateId + if resp.Diagnostics.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &revisionModel)...) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *workflowTemplateRevisionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state WorkflowTemplateRevisionResourceModel + + 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 workflow template revision", "Revision ID is empty") + return + } + + err := r.client.WorkflowTemplatesRevisions.DeleteWorkflowTemplateRevision(ctx, r.org_name, revisionID, true) + if err != nil { + resp.Diagnostics.AddError("Error deleting workflow template revision", "Error in deleting workflow template revision API call: "+err.Error()) + return + } +} diff --git a/internal/resource/workflow_template_revision/resource_test.go b/internal/resource/workflow_template_revision/resource_test.go new file mode 100644 index 0000000..2d087cc --- /dev/null +++ b/internal/resource/workflow_template_revision/resource_test.go @@ -0,0 +1,187 @@ +package workflowtemplaterevision_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/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") + + client := sgclient.NewClient(sgoption.WithApiKey(fmt.Sprintf("apikey %s", os.Getenv("STACKGUARDIAN_API_KEY"))), sgoption.WithBaseURL(os.Getenv("STACKGUARDIAN_API_URI")), sgoption.WithHTTPHeader(customHeader)) + + return client +} + +func SampleCreateWorkflowPayload(templateName, sourceConfigKind string) *workflowtemplates.CreateWorkflowTemplateRequest { + var sampleCreateWorkflowTemplatePayload = workflowtemplates.CreateWorkflowTemplateRequest{ + Id: &templateName, + TemplateName: templateName, + SourceConfigKind: (*workflowtemplates.WorkflowTemplateSourceConfigKindEnum)(&sourceConfigKind), + TemplateType: sgsdkgo.TemplateTypeEnum("IAC"), + IsPublic: sgsdkgo.IsPublicEnumZero.Ptr(), + OwnerOrg: fmt.Sprintf("/orgs/%s", org), + } + return &sampleCreateWorkflowTemplatePayload +} + +func createWorkflowTemplateFixture(templateName, sourceConfigKind string) error { + client := GetClient() + + templatePayload := SampleCreateWorkflowPayload(templateName, sourceConfigKind) + + _, err := client.WorkflowTemplates.CreateWorkflowTemplate(context.TODO(), org, false, templatePayload) + if err != nil { + return err + } + return nil +} + +func deleteWorkflowTemplateFixture(templateId string) { + client := GetClient() + + client.WorkflowTemplates.DeleteWorkflowTemplate(context.TODO(), org, templateId) +} + +func deleteWorkflowTemplateRevisionFixture(revisionId string) { + client := GetClient() + + client.WorkflowTemplatesRevisions.DeleteWorkflowTemplateRevision(context.TODO(), org, revisionId, true) +} + +func TestAccWorkflowTemplateRevision_Basic(t *testing.T) { + templateID := "tf-provider-workflow-template-revision-1" + alias := "revision1" + + err := createWorkflowTemplateFixture(templateID, "TERRAFORM") + if err != nil { + t.Fatalf(err.Error()) + } + defer deleteWorkflowTemplateFixture(templateID) + defer deleteWorkflowTemplateRevisionFixture(fmt.Sprintf("%s:1", templateID)) + + 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: testAccWorkflowTemplateRevisionConfig(templateID, alias), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_workflow_template_revision.test", "template_id", templateID), + resource.TestCheckResourceAttr("stackguardian_workflow_template_revision.test", "alias", alias), + resource.TestCheckResourceAttr("stackguardian_workflow_template_revision.test", "is_public", "0"), + resource.TestCheckResourceAttrSet("stackguardian_workflow_template_revision.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccWorkflowTemplateRevisionConfigUpdated(templateID, alias), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_workflow_template_revision.test", "alias", alias), + resource.TestCheckResourceAttr("stackguardian_workflow_template_revision.test", "notes", "Updated revision notes"), + ), + }, + // Delete testing automatically occurs + }, + }) +} + +func TestAccWorkflowTemplateRevision_WithConfig(t *testing.T) { + templateID := "test-workflow-template-revision" + alias := "revision2" + + err := createWorkflowTemplateFixture(templateID, "TERRAFORM") + if err != nil { + t.Fatalf(err.Error()) + } + defer deleteWorkflowTemplateFixture(templateID) + defer deleteWorkflowTemplateRevisionFixture(fmt.Sprintf("%s:1", templateID)) + + 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 with full configuration + { + Config: testAccWorkflowTemplateRevisionConfigWithDetails(templateID, alias), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_workflow_template_revision.test", "template_id", templateID), + resource.TestCheckResourceAttr("stackguardian_workflow_template_revision.test", "alias", alias), + resource.TestCheckResourceAttr("stackguardian_workflow_template_revision.test", "user_job_cpu", "2"), + resource.TestCheckResourceAttr("stackguardian_workflow_template_revision.test", "user_job_memory", "4096"), + resource.TestCheckResourceAttr("stackguardian_workflow_template_revision.test", "number_of_approvals_required", "1"), + ), + }, + // Delete testing automatically occurs + }, + }) +} + +func testAccWorkflowTemplateRevisionConfig(templateID, alias string) string { + return fmt.Sprintf(` +resource "stackguardian_workflow_template_revision" "test" { + template_id = "%s" + alias = "%s" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["test", "terraform"] +} +`, templateID, alias) +} + +func testAccWorkflowTemplateRevisionConfigUpdated(templateID, alias string) string { + return fmt.Sprintf(` +resource "stackguardian_workflow_template_revision" "test" { + template_id = "%s" + alias = "%s" + source_config_kind = "TERRAFORM" + is_public = "0" + notes = "Updated revision notes" + tags = ["test", "terraform", "updated"] +} +`, templateID, alias) +} + +func testAccWorkflowTemplateRevisionConfigWithDetails(templateID, alias string) string { + return fmt.Sprintf(` +resource "stackguardian_workflow_template_revision" "test" { + template_id = "%s" + alias = "%s" + source_config_kind = "TERRAFORM" + is_public = "0" + notes = "Revision with detailed configuration" + user_job_cpu = 2 + user_job_memory = 4096 + number_of_approvals_required = 1 + tags = ["test", "terraform", "detailed"] + approvers = ["approver1", "approver2"] +} +`, templateID, alias) +} diff --git a/internal/resource/workflow_template_revision/schema.go b/internal/resource/workflow_template_revision/schema.go new file mode 100644 index 0000000..130109b --- /dev/null +++ b/internal/resource/workflow_template_revision/schema.go @@ -0,0 +1,522 @@ +package workflowtemplaterevision + +import ( + "context" + "fmt" + + "github.com/StackGuardian/terraform-provider-stackguardian/internal/constants" + workflowtemplate "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_template" + "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" +) + +var ministepsNotificationRecepients = 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 terraformConfigSchema = schema.SingleNestedAttribute{ + MarkdownDescription: constants.TerraformConfig, + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "terraform_version": schema.StringAttribute{ + MarkdownDescription: constants.TerraformVersion, + Optional: true, + Computed: true, + }, + "drift_check": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformDriftCheck, + Optional: true, + Computed: true, + }, + "drift_cron": schema.StringAttribute{ + MarkdownDescription: constants.TerraformDriftCron, + Optional: true, + Computed: true, + }, + "managed_terraform_state": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformManagedState, + Optional: true, + Computed: true, + }, + "approval_pre_apply": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformApprovalPreApply, + Optional: true, + Computed: true, + }, + "terraform_plan_options": schema.StringAttribute{ + MarkdownDescription: constants.TerraformPlanOptions, + Optional: true, + Computed: true, + }, + "terraform_init_options": schema.StringAttribute{ + MarkdownDescription: constants.TerraformInitOptions, + Optional: true, + Computed: true, + }, + "terraform_bin_path": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformBinPath, + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: mount_point, + }, + }, + // TODO: confirm the description + "timeout": schema.Int64Attribute{ + MarkdownDescription: constants.TerraformTimeout, + Optional: true, + Computed: true, + }, + "post_apply_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPostApplyWfSteps, + Optional: true, + Computed: true, + NestedObject: wfStepsConfig, + }, + "pre_apply_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPreApplyWfSteps, + Optional: true, + Computed: true, + NestedObject: wfStepsConfig, + }, + "pre_plan_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPrePlanWfSteps, + Optional: true, + Computed: true, + NestedObject: wfStepsConfig, + }, + "post_plan_wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.TerraformPostPlanWfSteps, + Optional: true, + Computed: true, + NestedObject: wfStepsConfig, + }, + "pre_init_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPreInitHooks, + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + "pre_plan_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPrePlanHooks, + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + "post_plan_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPostPlanHooks, + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + "pre_apply_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPreApplyHooks, + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + "post_apply_hooks": schema.ListAttribute{ + MarkdownDescription: constants.TerraformPostApplyHooks, + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + "run_pre_init_hooks_on_drift": schema.BoolAttribute{ + MarkdownDescription: constants.TerraformRunPreInitHooksOnDrift, + Optional: true, + Computed: true, + }, + }, +} + +var environmentVariables = 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 mount_point = map[string]schema.Attribute{ + "source": schema.StringAttribute{ + MarkdownDescription: constants.MountPointSource, + Optional: true, + }, + "target": schema.StringAttribute{ + MarkdownDescription: constants.MountPointTarget, + Optional: true, + }, + // TODO: confirm the description + "read_only": schema.BoolAttribute{ + MarkdownDescription: constants.MountPointReadOnly, + Optional: true, + }, +} +var wfStepsConfig = 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: environmentVariables, + }, + }, + "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: mount_point, + }, + }, + "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, + }, + }, + }, + }, +} + +// Schema defines the schema for the resource. +func (r *workflowTemplateRevisionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Manages a workflow template revision resource.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: constants.Id, + Computed: true, + }, + "template_id": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowTemplateRevisionTemplateId, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "description": schema.StringAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Description, "workflow template revision"), + Optional: true, + Computed: true, + }, + "alias": schema.StringAttribute{ + MarkdownDescription: constants.TemplateRevisionAlias, + Optional: true, + }, + "notes": schema.StringAttribute{ + MarkdownDescription: constants.TemplateRevisionNotes, + Optional: true, + Computed: true, + }, + "source_config_kind": schema.StringAttribute{ + MarkdownDescription: constants.SourceConfigKind, + Required: true, + }, + "is_public": schema.StringAttribute{ + MarkdownDescription: constants.TemplateRevisionIsPublic, + Optional: true, + Computed: true, + }, + "deprecation": schema.SingleNestedAttribute{ + MarkdownDescription: constants.TemplateRevisionDeprecation, + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "effective_date": schema.StringAttribute{ + MarkdownDescription: constants.TemplateRevisionDeprecationEffectiveDate, + Optional: true, + }, + "message": schema.StringAttribute{ + MarkdownDescription: constants.DeprecationMessage, + Optional: true, + }, + }, + }, + "environment_variables": schema.ListNestedAttribute{ + MarkdownDescription: constants.WorkflowTemplateRevisionEnvironmentVariables, + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: environmentVariables, + }, + }, + // TODO: Update descriptions for encoded_data and ui_schema_data + "input_schemas": schema.ListNestedAttribute{ + MarkdownDescription: constants.WorkflowTemplateRevisionInputSchemas, + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + MarkdownDescription: constants.InputSchemaType, + Optional: true, + }, + "encoded_data": schema.StringAttribute{ + MarkdownDescription: constants.InputSchemaEncodedData, + Optional: true, + }, + "ui_schema_data": schema.StringAttribute{ + MarkdownDescription: constants.InputSchemaUISchemaData, + Optional: 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": ministepsNotificationRecepients, + "cancelled": ministepsNotificationRecepients, + "completed": ministepsNotificationRecepients, + "drift_detected": ministepsNotificationRecepients, + "errored": ministepsNotificationRecepients, + }, + }, + }, + }, + "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, + }, + }, + }, + }, + "runner_constraints": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + MarkdownDescription: constants.RunnerConstraintsType, + Required: true, + }, + "names": schema.ListAttribute{ + MarkdownDescription: constants.RunnerConstraintsNames, + Optional: true, + ElementType: types.StringType, + }, + }, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: fmt.Sprintf(constants.Tags, "workflow template revision"), + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "user_schedules": schema.ListNestedAttribute{ + MarkdownDescription: constants.WorkflowTemplateRevisionUserSchedules, + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "cron": schema.StringAttribute{ + MarkdownDescription: constants.UserScheduleCron, + Required: true, + }, + "state": schema.StringAttribute{ + MarkdownDescription: constants.UserScheduleState, + Optional: true, + }, + "desc": schema.StringAttribute{ + MarkdownDescription: constants.UserScheduleDesc, + Optional: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: constants.UserScheduleName, + Optional: true, + }, + }, + }, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: fmt.Sprintf(constants.ContextTags, "workflow template revision"), + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "approvers": schema.ListAttribute{ + MarkdownDescription: constants.WorkflowTemplateRevisionApprovers, + ElementType: types.StringType, + Optional: true, + }, + "number_of_approvals_required": schema.Int64Attribute{ + MarkdownDescription: constants.WorkflowTemplateRevisionNumberOfApprovals, + Optional: true, + Computed: true, + }, + "user_job_cpu": schema.Int64Attribute{ + MarkdownDescription: constants.WorkflowTemplateRevisionUserJobCPU, + Optional: true, + Computed: true, + }, + "user_job_memory": schema.Int64Attribute{ + MarkdownDescription: constants.WorkflowTemplateRevisionUserJobMemory, + Optional: true, + Computed: true, + }, + "runtime_source": schema.SingleNestedAttribute{ + MarkdownDescription: fmt.Sprintf(constants.RuntimeSource, "revision"), + Optional: true, + Computed: true, + Attributes: workflowtemplate.WorkflowTemplateRuntimeSourceConfig(), + }, + "terraform_config": terraformConfigSchema, + "deployment_platform_config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WorkflowTemplateRevisionDeploymentPlatformConfig, + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "kind": schema.StringAttribute{ + MarkdownDescription: constants.DeploymentPlatformKind, + Required: true, + }, + "config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.DeploymentPlatformConfigDetails, + Required: true, + Attributes: map[string]schema.Attribute{ + "integration_id": schema.StringAttribute{ + MarkdownDescription: constants.DeploymentPlatformIntegrationId, + Required: true, + }, + "profile_name": schema.StringAttribute{ + MarkdownDescription: constants.DeploymentPlatformProfileName, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + "wf_steps_config": schema.ListNestedAttribute{ + MarkdownDescription: constants.WorkflowTemplateRevisionWfStepsConfig, + Optional: true, + Computed: true, + NestedObject: wfStepsConfig, + }, + }, + } +} diff --git a/main.go b/main.go index 11c3dda..9d705c5 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "context" "flag" "log" + "net/http" sgprovider "github.com/StackGuardian/terraform-provider-stackguardian/internal/provider" "github.com/hashicorp/terraform-plugin-framework/providerserver" @@ -27,7 +28,8 @@ func main() { Debug: debug, } - err := providerserver.Serve(context.Background(), sgprovider.New(Version), opts) + httpHeader := http.Header{} + err := providerserver.Serve(context.Background(), sgprovider.New(Version, httpHeader), opts) if err != nil { log.Fatal(err.Error()) From 7e18070a20d73f831363236d8745318a5e9edfe8 Mon Sep 17 00:00:00 2001 From: Taher Kathanawala <28436188+taherkk@users.noreply.github.com> Date: Sun, 1 Mar 2026 22:05:42 +0530 Subject: [PATCH 2/6] Feat/workflow step templates provider (#91) * Add workflow step template resource and revision resources and datasources --- __debug_bin4148613812 | Bin 0 -> 26787746 bytes .../workflow_step_template/datasource.tf | 12 + .../datasource.tf | 12 + .../workflow_step_template/resource.tf | 18 + .../resource.tf | 34 ++ .../workflow_step_template.md.tmpl | 15 + .../workflow_step_template_revision.md.tmpl | 15 + .../resources/workflow_step_template.md.tmpl | 32 ++ .../workflow_step_template_revision.md.tmpl | 32 ++ docs/data-sources/workflow_step_template.md | 86 ++++ .../workflow_step_template_revision.md | 95 +++++ docs/resources/workflow_step_template.md | 118 ++++++ .../workflow_step_template_revision.md | 143 +++++++ go.mod | 2 - go.sum | 2 - internal/constants/docs.go | 3 + internal/constants/workflow_step_template.go | 139 ++++++ .../workflow_step_template/datasource.go | 79 ++++ .../workflow_step_template/schema.go | 113 +++++ .../datasource.go | 92 ++++ .../workflow_step_template_revision/schema.go | 121 ++++++ internal/provider/provider.go | 8 + .../resource/workflow_step_template/model.go | 395 ++++++++++++++++++ .../workflow_step_template/resource.go | 250 +++++++++++ .../workflow_step_template/resource_test.go | 102 +++++ .../resource/workflow_step_template/schema.go | 132 ++++++ .../workflow_step_template_revision/model.go | 312 ++++++++++++++ .../resource.go | 252 +++++++++++ .../resource_test.go | 74 ++++ .../workflow_step_template_revision/schema.go | 118 ++++++ main.go | 3 +- 31 files changed, 2803 insertions(+), 6 deletions(-) create mode 100755 __debug_bin4148613812 create mode 100644 docs-examples/datasources/workflow_step_template/datasource.tf create mode 100644 docs-examples/datasources/workflow_step_template_revision/datasource.tf create mode 100644 docs-examples/resources/workflow_step_template/resource.tf create mode 100644 docs-examples/resources/workflow_step_template_revision/resource.tf create mode 100644 docs-templates/data-sources/workflow_step_template.md.tmpl create mode 100644 docs-templates/data-sources/workflow_step_template_revision.md.tmpl create mode 100644 docs-templates/resources/workflow_step_template.md.tmpl create mode 100644 docs-templates/resources/workflow_step_template_revision.md.tmpl create mode 100644 docs/data-sources/workflow_step_template.md create mode 100644 docs/data-sources/workflow_step_template_revision.md create mode 100644 docs/resources/workflow_step_template.md create mode 100644 docs/resources/workflow_step_template_revision.md create mode 100644 internal/constants/workflow_step_template.go create mode 100644 internal/datasources/workflow_step_template/datasource.go create mode 100644 internal/datasources/workflow_step_template/schema.go create mode 100644 internal/datasources/workflow_step_template_revision/datasource.go create mode 100644 internal/datasources/workflow_step_template_revision/schema.go create mode 100644 internal/resource/workflow_step_template/model.go create mode 100644 internal/resource/workflow_step_template/resource.go create mode 100644 internal/resource/workflow_step_template/resource_test.go create mode 100644 internal/resource/workflow_step_template/schema.go create mode 100644 internal/resource/workflow_step_template_revision/model.go create mode 100644 internal/resource/workflow_step_template_revision/resource.go create mode 100644 internal/resource/workflow_step_template_revision/resource_test.go create mode 100644 internal/resource/workflow_step_template_revision/schema.go diff --git a/__debug_bin4148613812 b/__debug_bin4148613812 new file mode 100755 index 0000000000000000000000000000000000000000..809c2c0e32db4db1ac2eeedcbf0e5e46274b50a1 GIT binary patch literal 26787746 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

3Js(J8 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