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/__debug_bin4148613812 b/__debug_bin4148613812 new file mode 100755 index 0000000..809c2c0 Binary files /dev/null and b/__debug_bin4148613812 differ diff --git a/docs-examples/datasources/workflow_step_template/datasource.tf b/docs-examples/datasources/workflow_step_template/datasource.tf new file mode 100644 index 0000000..c612686 --- /dev/null +++ b/docs-examples/datasources/workflow_step_template/datasource.tf @@ -0,0 +1,12 @@ +data "stackguardian_workflow_step_template" "example" { + id = "12345678901234567890" +} + +output "workflow_step_template_info" { + value = { + name = data.stackguardian_workflow_step_template.example.template_name + type = data.stackguardian_workflow_step_template.example.template_type + description = data.stackguardian_workflow_step_template.example.description + tags = data.stackguardian_workflow_step_template.example.tags + } +} diff --git a/docs-examples/datasources/workflow_step_template_revision/datasource.tf b/docs-examples/datasources/workflow_step_template_revision/datasource.tf new file mode 100644 index 0000000..129dfc6 --- /dev/null +++ b/docs-examples/datasources/workflow_step_template_revision/datasource.tf @@ -0,0 +1,12 @@ +data "stackguardian_workflow_step_template_revision" "example" { + id = "12345678901234567890:1" +} + +output "workflow_step_template_revision_info" { + value = { + template_id = data.stackguardian_workflow_step_template_revision.example.template_id + alias = data.stackguardian_workflow_step_template_revision.example.alias + notes = data.stackguardian_workflow_step_template_revision.example.notes + tags = data.stackguardian_workflow_step_template_revision.example.tags + } +} 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_step_template/resource.tf b/docs-examples/resources/workflow_step_template/resource.tf new file mode 100644 index 0000000..03eca0f --- /dev/null +++ b/docs-examples/resources/workflow_step_template/resource.tf @@ -0,0 +1,18 @@ +resource "stackguardian_workflow_step_template" "example" { + template_name = "example-workflow-step-template" + is_active = "1" + is_public = "0" + description = "Example workflow step template with runtime source" + + source_config_kind = "DOCKER_IMAGE" + + runtime_source = { + source_config_dest_kind = "CONTAINER_REGISTRY" + config = { + docker_image = "ubuntu:latest" + is_private = false + } + } + + tags = ["terraform", "example"] +} diff --git a/docs-examples/resources/workflow_step_template_revision/resource.tf b/docs-examples/resources/workflow_step_template_revision/resource.tf new file mode 100644 index 0000000..905b58c --- /dev/null +++ b/docs-examples/resources/workflow_step_template_revision/resource.tf @@ -0,0 +1,34 @@ +resource "stackguardian_workflow_step_template" "example" { + template_name = "example-workflow-step-template" + is_active = "1" + is_public = "0" + description = "Example workflow step template" + + source_config_kind = "DOCKER_IMAGE" + + runtime_source = { + source_config_dest_kind = "CONTAINER_REGISTRY" + config = { + docker_image = "ubuntu:latest" + is_private = false + } + } +} + +resource "stackguardian_workflow_step_template_revision" "example" { + template_id = stackguardian_workflow_step_template.example.id + alias = "v1" + notes = "Initial revision" + + source_config_kind = "DOCKER_IMAGE" + + runtime_source = { + source_config_dest_kind = "CONTAINER_REGISTRY" + config = { + docker_image = "ubuntu:20.04" + is_private = false + } + } + + tags = ["terraform", "example"] +} 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..60d9cb1 --- /dev/null +++ b/docs-examples/resources/workflow_template_revision/resource.tf @@ -0,0 +1,29 @@ +resource "stackguardian_workflow_template" "example" { + template_name = "my-terraform-template" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "production"] +} + +# Example 1: Basic workflow template revision +resource "stackguardian_workflow_template_revision" "basic" { + template_id = stackguardian_workflow_template.example.id + 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 = stackguardian_workflow_template.example.id + 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_step_template.md.tmpl b/docs-templates/data-sources/workflow_step_template.md.tmpl new file mode 100644 index 0000000..0a4f5a5 --- /dev/null +++ b/docs-templates/data-sources/workflow_step_template.md.tmpl @@ -0,0 +1,19 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_step_template Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_step_template (Data Source) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +{{tffile "docs-examples/datasources/workflow_step_template/datasource.tf"}} + +{{ .SchemaMarkdown }} diff --git a/docs-templates/data-sources/workflow_step_template_revision.md.tmpl b/docs-templates/data-sources/workflow_step_template_revision.md.tmpl new file mode 100644 index 0000000..cfc1196 --- /dev/null +++ b/docs-templates/data-sources/workflow_step_template_revision.md.tmpl @@ -0,0 +1,19 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_step_template_revision Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_step_template_revision (Data Source) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +{{tffile "docs-examples/datasources/workflow_step_template_revision/datasource.tf"}} + +{{ .SchemaMarkdown }} 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..f916e11 --- /dev/null +++ b/docs-templates/data-sources/workflow_template.md.tmpl @@ -0,0 +1,19 @@ +--- +# 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) + +
+⚠️ This feature is currently in BETA. +
+ +## 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..b2442ba --- /dev/null +++ b/docs-templates/data-sources/workflow_template_revision.md.tmpl @@ -0,0 +1,19 @@ +--- +# 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) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +{{tffile "docs-examples/datasources/workflow_template_revision/datasource.tf"}} + +{{ .SchemaMarkdown }} diff --git a/docs-templates/resources/workflow_step_template.md.tmpl b/docs-templates/resources/workflow_step_template.md.tmpl new file mode 100644 index 0000000..0c1309e --- /dev/null +++ b/docs-templates/resources/workflow_step_template.md.tmpl @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_step_template Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_step_template (Resource) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +{{tffile "docs-examples/resources/workflow_step_template/resource.tf"}} + +{{ .SchemaMarkdown }} + +## Import + +Import existing workflow step template + +### Using Import block (terraform v1.5.0 and later) +```terraform +import { + to = stackguardian_workflow_step_template.example + id = "template-id" +} +``` + +### Using CLI +```bash +terraform import stackguardian_workflow_step_template.example template-id +``` diff --git a/docs-templates/resources/workflow_step_template_revision.md.tmpl b/docs-templates/resources/workflow_step_template_revision.md.tmpl new file mode 100644 index 0000000..c919939 --- /dev/null +++ b/docs-templates/resources/workflow_step_template_revision.md.tmpl @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_step_template_revision Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_step_template_revision (Resource) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +{{tffile "docs-examples/resources/workflow_step_template_revision/resource.tf"}} + +{{ .SchemaMarkdown }} + +## Import + +Import existing workflow step template revision + +### Using Import block (terraform v1.5.0 and later) +```terraform +import { + to = stackguardian_workflow_step_template_revision.example + id = "template-id:revision-number" +} +``` + +### Using CLI +```bash +terraform import stackguardian_workflow_step_template_revision.example "template-id:revision-number" +``` diff --git a/docs-templates/resources/workflow_template.md.tmpl b/docs-templates/resources/workflow_template.md.tmpl new file mode 100644 index 0000000..bdf9433 --- /dev/null +++ b/docs-templates/resources/workflow_template.md.tmpl @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_template Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_template (Resource) + +
+⚠️ This feature is currently in BETA. +
+ +## 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..25a8329 --- /dev/null +++ b/docs-templates/resources/workflow_template_revision.md.tmpl @@ -0,0 +1,36 @@ +--- +# 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) + +
+⚠️ This feature is currently in BETA. +
+ +## 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_step_template.md b/docs/data-sources/workflow_step_template.md new file mode 100644 index 0000000..a913dcf --- /dev/null +++ b/docs/data-sources/workflow_step_template.md @@ -0,0 +1,90 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_step_template Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_step_template (Data Source) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +```terraform +data "stackguardian_workflow_step_template" "example" { + id = "12345678901234567890" +} + +output "workflow_step_template_info" { + value = { + name = data.stackguardian_workflow_step_template.example.template_name + type = data.stackguardian_workflow_step_template.example.template_type + description = data.stackguardian_workflow_step_template.example.description + tags = data.stackguardian_workflow_step_template.example.tags + } +} +``` + + +## Schema + +### Required + +- `id` (String) ID of the resource. Should be used to import the resource. + +### Read-Only + +- `context_tags` (Map of String) Contextual key-value tags that provide additional context to the main tags. +- `description` (String) A brief description of the workflow step template. Must be less than 256 characters. +- `is_active` (String) Whether the workflow step template is active. Valid values: + 0 (false), + 1 (true) +- `is_public` (String) Whether the workflow step template is publicly available. Valid values: + 0 (false), + 1 (true) +- `latest_revision` (Number) Latest revision number of the template. +- `next_revision` (Number) Next revision number that will be used for the template. +- `runtime_source` (Attributes) Runtime source configuration that defines where and how the template code is stored and executed. (see [below for nested schema](#nestedatt--runtime_source)) +- `shared_orgs_list` (List of String) List of organization IDs with which this template is shared. +- `source_config_kind` (String) Source configuration kind that defines how the template is deployed. Valid values: + DOCKER_IMAGE, + GIT_REPO, + S3 +- `tags` (List of String) A list of tags associated with the workflow step template. A maximum of 10 tags are allowed. +- `template_name` (String) Name of the workflow step template. Must be less than 100 characters. +- `template_type` (String) Type of the template. Valid values: + WORKFLOW_STEP, + IAC, + IAC_GROUP, + IAC_POLICY + + +### Nested Schema for `runtime_source` + +Read-Only: + +- `additional_config` (Map of String) Additional configuration settings for the runtime source as key-value pairs. +- `config` (Attributes) Specific configuration settings for the runtime source. (see [below for nested schema](#nestedatt--runtime_source--config)) +- `source_config_dest_kind` (String) Destination kind for the runtime source configuration. Examples: +CONTAINER_REGISTRY, +GIT, +S3 + + +### Nested Schema for `runtime_source.config` + +Read-Only: + +- `auth` (String, Sensitive) Authentication credentials or method for accessing the private registry or repository. (Sensitive) +- `docker_image` (String) Docker image URI to be used for template execution. Example: `ubuntu:latest`, `myregistry.azurecr.io/myapp:v1.0` +- `docker_registry_username` (String) Username for authentication with the Docker registry (if using private registries). +- `is_private` (Boolean) Indicates whether the container registry or repository is private. +- `local_workspace_dir` (String) Workfing directory path. + + + + diff --git a/docs/data-sources/workflow_step_template_revision.md b/docs/data-sources/workflow_step_template_revision.md new file mode 100644 index 0000000..6b1b7a2 --- /dev/null +++ b/docs/data-sources/workflow_step_template_revision.md @@ -0,0 +1,99 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_step_template_revision Data Source - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_step_template_revision (Data Source) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +```terraform +data "stackguardian_workflow_step_template_revision" "example" { + id = "12345678901234567890:1" +} + +output "workflow_step_template_revision_info" { + value = { + template_id = data.stackguardian_workflow_step_template_revision.example.template_id + alias = data.stackguardian_workflow_step_template_revision.example.alias + notes = data.stackguardian_workflow_step_template_revision.example.notes + tags = data.stackguardian_workflow_step_template_revision.example.tags + } +} +``` + + +## Schema + +### Required + +- `id` (String) ID of the revision in the format `templateId:revisionNumber`. + +### Read-Only + +- `alias` (String) Alias for the revision to easily identify it. +- `context_tags` (Map of String) Contextual key-value tags that provide additional context to the main tags. +- `deprecation` (Attributes) Deprecation information for this resource. (see [below for nested schema](#nestedatt--deprecation)) +- `is_active` (String) Whether the workflow step template is active. Valid values: + 0 (false), + 1 (true) +- `is_public` (String) Whether the workflow step template is publicly available. Valid values: + 0 (false), + 1 (true) +- `long_description` (String) A brief description of the workflow step template revision. Must be less than 256 characters. +- `notes` (String) Notes or changelog information for this revision. +- `runtime_source` (Attributes) Runtime source configuration for the revision. (see [below for nested schema](#nestedatt--runtime_source)) +- `source_config_kind` (String) Source configuration kind that defines how the template is deployed. Valid values: + DOCKER_IMAGE, + GIT_REPO, + S3 +- `tags` (List of String) A list of tags associated with the revision. A maximum of 10 tags are allowed. +- `template_id` (String) ID of the parent workflow step template. +- `template_type` (String) Type of the template. Valid values: + WORKFLOW_STEP, + IAC, + IAC_GROUP, + IAC_POLICY + + +### Nested Schema for `deprecation` + +Read-Only: + +- `effective_date` (String) Effective date when this resource will be deprecated and no longer available for use. +- `message` (String) Deprecation message + + + +### Nested Schema for `runtime_source` + +Read-Only: + +- `additional_config` (Map of String) Additional configuration settings for the runtime source as key-value pairs. +- `config` (Attributes) Specific configuration settings for the runtime source. (see [below for nested schema](#nestedatt--runtime_source--config)) +- `source_config_dest_kind` (String) Destination kind for the runtime source configuration. Examples: +CONTAINER_REGISTRY, +GIT, +S3 + + +### Nested Schema for `runtime_source.config` + +Read-Only: + +- `auth` (String, Sensitive) Authentication credentials or method for accessing the private registry or repository. (Sensitive) +- `docker_image` (String) Docker image URI to be used for template execution. Example: `ubuntu:latest`, `myregistry.azurecr.io/myapp:v1.0` +- `docker_registry_username` (String) Username for authentication with the Docker registry (if using private registries). +- `is_private` (Boolean) Indicates whether the container registry or repository is private. +- `local_workspace_dir` (String) Workfing directory path. + + + + diff --git a/docs/data-sources/workflow_template.md b/docs/data-sources/workflow_template.md new file mode 100644 index 0000000..37e25e4 --- /dev/null +++ b/docs/data-sources/workflow_template.md @@ -0,0 +1,97 @@ +--- +# 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) + +
+⚠️ This feature is currently in BETA. +
+ +## 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..d85e8ae --- /dev/null +++ b/docs/data-sources/workflow_template_revision.md @@ -0,0 +1,628 @@ +--- +# 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) + +
+⚠️ This feature is currently in BETA. +
+ +## 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_step_template.md b/docs/resources/workflow_step_template.md new file mode 100644 index 0000000..929467d --- /dev/null +++ b/docs/resources/workflow_step_template.md @@ -0,0 +1,122 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_step_template Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_step_template (Resource) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +```terraform +resource "stackguardian_workflow_step_template" "example" { + template_name = "example-workflow-step-template" + is_active = "1" + is_public = "0" + description = "Example workflow step template with runtime source" + + source_config_kind = "DOCKER_IMAGE" + + runtime_source = { + source_config_dest_kind = "CONTAINER_REGISTRY" + config = { + docker_image = "ubuntu:latest" + is_private = false + } + } + + tags = ["terraform", "example"] +} +``` + + +## Schema + +### Required + +- `runtime_source` (Attributes) Runtime source configuration that defines where and how the template code is stored and executed. (see [below for nested schema](#nestedatt--runtime_source)) +- `source_config_kind` (String) Source configuration kind that defines how the template is deployed. Valid values: + DOCKER_IMAGE, + GIT_REPO, + S3 +- `template_name` (String) Name of the workflow step template. Must be less than 100 characters. + +### Optional + +- `context_tags` (Map of String) Contextual key-value tags that provide additional context to the main tags. +- `description` (String) A brief description of the workflow step 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_active` (String) Whether the workflow step template is active. Valid values: + 0 (false), + 1 (true) +- `is_public` (String) Whether the workflow step template is publicly available. Valid values: + 0 (false), + 1 (true) +- `shared_orgs_list` (List of String) List of organization IDs with which this template is shared. +- `tags` (List of String) A list of tags associated with the workflow step template. A maximum of 10 tags are allowed. + +### Read-Only + +- `latest_revision` (Number) Latest revision number of the template. +- `next_revision` (Number) Next revision number that will be used for the template. +- `template_type` (String) Type of the template. Valid values: + WORKFLOW_STEP, + IAC, + IAC_GROUP, + IAC_POLICY + + +### Nested Schema for `runtime_source` + +Required: + +- `config` (Attributes) Specific configuration settings for the runtime source. (see [below for nested schema](#nestedatt--runtime_source--config)) + +Optional: + +- `additional_config` (Map of String) Additional configuration settings for the runtime source as key-value pairs. +- `source_config_dest_kind` (String) Destination kind for the runtime source configuration. Examples: +CONTAINER_REGISTRY, +GIT, +S3 + + +### Nested Schema for `runtime_source.config` + +Required: + +- `docker_image` (String) Docker image URI to be used for template execution. Example: `ubuntu:latest`, `myregistry.azurecr.io/myapp:v1.0` + +Optional: + +- `auth` (String, Sensitive) Authentication credentials or method for accessing the private registry or repository. (Sensitive) +- `docker_registry_username` (String) Username for authentication with the Docker registry (if using private registries). +- `is_private` (Boolean) Indicates whether the container registry or repository is private. +- `local_workspace_dir` (String) Workfing directory path. + + + + + +## Import + +Import existing workflow step template + +### Using Import block (terraform v1.5.0 and later) +```terraform +import { + to = stackguardian_workflow_step_template.example + id = "template-id" +} +``` + +### Using CLI +```bash +terraform import stackguardian_workflow_step_template.example template-id +``` diff --git a/docs/resources/workflow_step_template_revision.md b/docs/resources/workflow_step_template_revision.md new file mode 100644 index 0000000..84c6449 --- /dev/null +++ b/docs/resources/workflow_step_template_revision.md @@ -0,0 +1,147 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_step_template_revision Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_step_template_revision (Resource) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +```terraform +resource "stackguardian_workflow_step_template" "example" { + template_name = "example-workflow-step-template" + is_active = "1" + is_public = "0" + description = "Example workflow step template" + + source_config_kind = "DOCKER_IMAGE" + + runtime_source = { + source_config_dest_kind = "CONTAINER_REGISTRY" + config = { + docker_image = "ubuntu:latest" + is_private = false + } + } +} + +resource "stackguardian_workflow_step_template_revision" "example" { + template_id = stackguardian_workflow_step_template.example.id + alias = "v1" + notes = "Initial revision" + + source_config_kind = "DOCKER_IMAGE" + + runtime_source = { + source_config_dest_kind = "CONTAINER_REGISTRY" + config = { + docker_image = "ubuntu:20.04" + is_private = false + } + } + + tags = ["terraform", "example"] +} +``` + + +## Schema + +### Required + +- `runtime_source` (Attributes) Runtime source configuration for the revision. (see [below for nested schema](#nestedatt--runtime_source)) +- `source_config_kind` (String) Source configuration kind that defines how the template is deployed. Valid values: + DOCKER_IMAGE, + GIT_REPO, + S3 +- `template_id` (String) ID of the parent workflow step template. + +### Optional + +- `alias` (String) Alias for the revision to easily identify it. +- `context_tags` (Map of String) Contextual key-value tags that provide additional context to the main tags. +- `deprecation` (Attributes) Deprecation information for this resource. (see [below for nested schema](#nestedatt--deprecation)) +- `description` (String) A brief description of the workflow step template revision. Must be less than 256 characters. +- `is_active` (String) Whether the workflow step template is active. Valid values: + 0 (false), + 1 (true) +- `is_public` (String) Whether the workflow step template is publicly available. Valid values: + 0 (false), + 1 (true) +- `notes` (String) Notes or changelog information for this revision. +- `tags` (List of String) A list of tags associated with the revision. A maximum of 10 tags are allowed. + +### Read-Only + +- `id` (String) ID of the revision in the format `templateId:revisionNumber`. +- `template_type` (String) Type of the template. Valid values: + WORKFLOW_STEP, + IAC, + IAC_GROUP, + IAC_POLICY + + +### Nested Schema for `runtime_source` + +Required: + +- `config` (Attributes) Specific configuration settings for the runtime source. (see [below for nested schema](#nestedatt--runtime_source--config)) +- `source_config_dest_kind` (String) Destination kind for the runtime source configuration. Examples: +CONTAINER_REGISTRY, +GIT, +S3 + +Optional: + +- `additional_config` (Map of String) Additional configuration settings for the runtime source as key-value pairs. + + +### Nested Schema for `runtime_source.config` + +Required: + +- `docker_image` (String) Docker image URI to be used for template execution. Example: `ubuntu:latest`, `myregistry.azurecr.io/myapp:v1.0` + +Optional: + +- `auth` (String, Sensitive) Authentication credentials or method for accessing the private registry or repository. (Sensitive) +- `docker_registry_username` (String) Username for authentication with the Docker registry (if using private registries). +- `is_private` (Boolean) Indicates whether the container registry or repository is private. +- `local_workspace_dir` (String) Workfing directory path. + + + + +### Nested Schema for `deprecation` + +Optional: + +- `effective_date` (String) Effective date when this resource will be deprecated and no longer available for use. +- `message` (String) Deprecation message + + + + +## Import + +Import existing workflow step template revision + +### Using Import block (terraform v1.5.0 and later) +```terraform +import { + to = stackguardian_workflow_step_template_revision.example + id = "template-id:revision-number" +} +``` + +### Using CLI +```bash +terraform import stackguardian_workflow_step_template_revision.example "template-id:revision-number" +``` diff --git a/docs/resources/workflow_template.md b/docs/resources/workflow_template.md new file mode 100644 index 0000000..b657e66 --- /dev/null +++ b/docs/resources/workflow_template.md @@ -0,0 +1,135 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackguardian_workflow_template Resource - terraform-provider-stackguardian" +subcategory: "" +description: |- + +--- + +# stackguardian_workflow_template (Resource) + +
+⚠️ This feature is currently in BETA. +
+ +## 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..0b24218 --- /dev/null +++ b/docs/resources/workflow_template_revision.md @@ -0,0 +1,733 @@ +--- +# 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) + +
+⚠️ This feature is currently in BETA. +
+ +## Example Usage + +```terraform +resource "stackguardian_workflow_template" "example" { + template_name = "my-terraform-template" + source_config_kind = "TERRAFORM" + is_public = "0" + tags = ["terraform", "production"] +} + +# Example 1: Basic workflow template revision +resource "stackguardian_workflow_template_revision" "basic" { + template_id = stackguardian_workflow_template.example.id + 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 = stackguardian_workflow_template.example.id + 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..217d31b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/StackGuardian/terraform-provider-stackguardian go 1.21.4 require ( - github.com/StackGuardian/sg-sdk-go v1.2.1 + github.com/StackGuardian/sg-sdk-go v1.3.1 github.com/hashicorp/terraform-plugin-framework v1.11.0 github.com/hashicorp/terraform-plugin-go v0.23.0 github.com/hashicorp/terraform-plugin-log v0.9.0 diff --git a/go.sum b/go.sum index adce48d..d290eea 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/StackGuardian/sg-sdk-go v1.2.1 h1:zn0Dlra97zbcsbMg5VUOcfYD0h3EaXv6l+BFtYunFx0= -github.com/StackGuardian/sg-sdk-go v1.2.1/go.mod h1:iCrgHE0WbxEaaLx6ATsRe81Qjp82kahCwLGJ2t66mZ0= +github.com/StackGuardian/sg-sdk-go v1.3.1 h1:WmuLhw1S98SJBepPS1Na+Gs9JBasdvrtFabSUudM9io= +github.com/StackGuardian/sg-sdk-go v1.3.1/go.mod h1:iCrgHE0WbxEaaLx6ATsRe81Qjp82kahCwLGJ2t66mZ0= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= diff --git a/internal/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/docs.go b/internal/constants/docs.go index 2b4de61..be3c004 100644 --- a/internal/constants/docs.go +++ b/internal/constants/docs.go @@ -213,6 +213,8 @@ const ( AWSRegion = "AWS region where the bucket is placed" Auth = "Authentication required by the runner to access the backend storage. Required only for type \"aws_s3\"" IntegrationId = "SG Connector Id. Required only for type \"aws_s3\" eg: /integrations/test-connector" + Deprecation = "Deprecation information for this resource." + DeprecationEffectiveDate = "Effective date when this resource will be deprecated and no longer available for use." ) ////////////// Data Source 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/constants/workflow_step_template.go b/internal/constants/workflow_step_template.go new file mode 100644 index 0000000..898fad8 --- /dev/null +++ b/internal/constants/workflow_step_template.go @@ -0,0 +1,139 @@ +package constants + +// Workflow Step Template - Common documentation +const ( + WorkflowStepTemplateSourceConfigKindCommon = `Source configuration kind that defines how the template is deployed. Valid values: + DOCKER_IMAGE, + GIT_REPO, + S3` + + WorkflowStepTemplateIsActiveCommon = `Whether the workflow step template is active. Valid values: + 0 (false), + 1 (true)` + + WorkflowStepTemplateIsPublicCommon = `Whether the workflow step template is publicly available. Valid values: + 0 (false), + 1 (true)` + + WorkflowStepTemplateRuntimeSourceDestKindCommon = "Destination kind for the runtime source configuration. Examples:" + + "\nCONTAINER_REGISTRY," + + "\nGIT," + + "\nS3" + + WorkflowStepTemplateRuntimeSourceConfigIsPrivateCommon = "Indicates whether the container registry or repository is private." + + WorkflowStepTemplateRuntimeSourceConfigAuthCommon = "Authentication credentials or method for accessing the private registry or repository. (Sensitive)" + + WorkflowStepTemplateRuntimeSourceConfigDockerImageCommon = "Docker image URI to be used for template execution. Example: `ubuntu:latest`, `myregistry.azurecr.io/myapp:v1.0`" + + WorkflowStepTemplateRuntimeSourceConfigDockerRegistryUsernameCommon = "Username for authentication with the Docker registry (if using private registries)." + + WorkflowStepTemplateRuntimeSourceConfigLocalWorkspaceDirCommon = "Workfing directory path." +) + +// Workflow Step Template Resource documentation +const ( + WorkflowStepTemplateName = "Name of the workflow step template. Must be less than 100 characters." + + WorkflowStepTemplateDescription = "A brief description of the workflow step template. Must be less than 256 characters." + + WorkflowStepTemplateType = `Type of the template. Valid values: + WORKFLOW_STEP, + IAC, + IAC_GROUP, + IAC_POLICY` + + WorkflowStepTemplateIsActive = `Whether the workflow step template is active. Valid values: + 0 (false), + 1 (true)` + + WorkflowStepTemplateIsPublic = `Whether the workflow step template is publicly available. Valid values: + 0 (false), + 1 (true)` + + WorkflowStepTemplateTags = "A list of tags associated with the workflow step template. A maximum of 10 tags are allowed." + + WorkflowStepTemplateContextTags = "Contextual key-value tags that provide additional context to the main tags." + + WorkflowStepTemplateSharedOrgsList = "List of organization IDs with which this template is shared." + + WorkflowStepTemplateSourceConfigKind = `Source configuration kind that defines how the template is deployed. Valid values: + DOCKER_IMAGE, + GIT_REPO, + S3` + + WorkflowStepTemplateLatestRevision = "Latest revision number of the template." + + WorkflowStepTemplateNextRevision = "Next revision number that will be used for the template." + + WorkflowStepTemplateRuntimeSource = "Runtime source configuration that defines where and how the template code is stored and executed." + + WorkflowStepTemplateRuntimeSourceDestKind = "Destination kind for the runtime source configuration. Examples:" + + "\nCONTAINER_REGISTRY," + + "\nGIT," + + "\nS3" + + WorkflowStepTemplateRuntimeSourceAdditionalConfig = "Additional configuration settings for the runtime source as key-value pairs." + + WorkflowStepTemplateRuntimeSourceConfig = "Specific configuration settings for the runtime source." + + WorkflowStepTemplateRuntimeSourceConfigIsPrivate = "Indicates whether the container registry or repository is private." + + WorkflowStepTemplateRuntimeSourceConfigAuth = "Authentication credentials or method for accessing the private registry or repository. (Sensitive)" + + WorkflowStepTemplateRuntimeSourceConfigDockerImage = "Docker image URI to be used for template execution. Example: `ubuntu:latest`, `myregistry.azurecr.io/myapp:v1.0`" + + WorkflowStepTemplateRuntimeSourceConfigDockerRegistryUsername = "Username for authentication with the Docker registry (if using private registries)." +) + +// Workflow Step Template Revision Resource documentation +const ( + WorkflowStepTemplateRevisionId = "ID of the revision in the format `templateId:revisionNumber`." + + WorkflowStepTemplateRevisionTemplateId = "ID of the parent workflow step template." + + WorkflowStepTemplateRevisionAlias = "Alias for the revision to easily identify it." + + WorkflowStepTemplateRevisionNotes = "Notes or changelog information for this revision." + + WorkflowStepTemplateRevisionDescription = "A brief description of the workflow step template revision. Must be less than 256 characters." + + WorkflowStepTemplateRevisionType = `Type of the template. Valid values: + WORKFLOW_STEP, + IAC, + IAC_GROUP, + IAC_POLICY` + + WorkflowStepTemplateRevisionSourceConfigKind = `Source configuration kind. Valid values: + DOCKER_IMAGE, + GIT_REPO, + S3` + + WorkflowStepTemplateRevisionIsActive = `Whether the revision is active. Valid values: + 0 (false), + 1 (true)` + + WorkflowStepTemplateRevisionIsPublic = `Whether the revision is publicly available. Valid values: + 0 (false), + 1 (true)` + + WorkflowStepTemplateRevisionTags = "A list of tags associated with the revision. A maximum of 10 tags are allowed." + + WorkflowStepTemplateRevisionContextTags = "Contextual key-value tags that provide additional context to the main tags." + + WorkflowStepTemplateRevisionRuntimeSource = "Runtime source configuration for the revision." + + WorkflowStepTemplateRevisionRuntimeSourceDestKind = "Destination kind for the runtime source configuration." + + WorkflowStepTemplateRevisionRuntimeSourceAdditionalConfig = "Additional configuration settings for the runtime source as key-value pairs." + + WorkflowStepTemplateRevisionRuntimeSourceConfig = "Specific configuration settings for the runtime source." + + WorkflowStepTemplateRevisionRuntimeSourceConfigIsPrivate = "Indicates whether the container registry or repository is private." + + WorkflowStepTemplateRevisionRuntimeSourceConfigAuth = "Authentication credentials or method for accessing the private registry or repository. (Sensitive)" + + WorkflowStepTemplateRevisionRuntimeSourceConfigDockerImage = "Docker image URI to be used for revision execution." + + WorkflowStepTemplateRevisionRuntimeSourceConfigDockerRegistryUsername = "Username for authentication with the Docker registry (if using private registries)." +) 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_step_template/datasource.go b/internal/datasources/workflow_step_template/datasource.go new file mode 100644 index 0000000..5db54b1 --- /dev/null +++ b/internal/datasources/workflow_step_template/datasource.go @@ -0,0 +1,79 @@ +package workflowsteptemplate + +import ( + "context" + "fmt" + + sgclient "github.com/StackGuardian/sg-sdk-go/client" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/customTypes" + workflowsteptemplateresource "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_step_template" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +var ( + _ datasource.DataSource = &workflowStepTemplateDatasource{} + _ datasource.DataSourceWithConfigure = &workflowStepTemplateDatasource{} +) + +// NewDataSource is a helper function to simplify the provider implementation. +func NewDataSource() datasource.DataSource { + return &workflowStepTemplateDatasource{} +} + +type workflowStepTemplateDatasource struct { + client *sgclient.Client + orgName string +} + +func (d *workflowStepTemplateDatasource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_workflow_step_template" +} + +func (d *workflowStepTemplateDatasource) 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 *workflowStepTemplateDatasource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config workflowsteptemplateresource.WorkflowStepTemplateResourceModel + + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + id := config.Id.ValueString() + if id == "" { + resp.Diagnostics.AddError("id is required", "The id attribute must be provided to look up a workflow step template.") + return + } + + readResp, err := d.client.WorkflowStepTemplate.ReadWorkflowStepTemplate(ctx, d.orgName, id) + if err != nil { + resp.Diagnostics.AddError("Unable to read workflow step template.", err.Error()) + return + } + + templateModel, diags := workflowsteptemplateresource.BuildAPIModelToWorkflowStepTemplateModel(&readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, templateModel)...) +} diff --git a/internal/datasources/workflow_step_template/schema.go b/internal/datasources/workflow_step_template/schema.go new file mode 100644 index 0000000..656d3ea --- /dev/null +++ b/internal/datasources/workflow_step_template/schema.go @@ -0,0 +1,113 @@ +package workflowsteptemplate + +import ( + "context" + + "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" +) + +// Schema defines the schema for the data source. +func (d *workflowStepTemplateDatasource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Use this data source to read a workflow step template.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: constants.DatasourceId, + Required: true, + }, + "template_name": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateName, + Computed: true, + }, + "template_type": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateType, + Computed: true, + }, + "is_active": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateIsActiveCommon, + Computed: true, + }, + "is_public": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateIsPublicCommon, + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateDescription, + Computed: true, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateTags, + ElementType: types.StringType, + Computed: true, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateContextTags, + ElementType: types.StringType, + Computed: true, + }, + "shared_orgs_list": schema.ListAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateSharedOrgsList, + ElementType: types.StringType, + Computed: true, + }, + "source_config_kind": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateSourceConfigKindCommon, + Computed: true, + }, + "latest_revision": schema.Int32Attribute{ + MarkdownDescription: constants.WorkflowStepTemplateLatestRevision, + Computed: true, + }, + "next_revision": schema.Int32Attribute{ + MarkdownDescription: constants.WorkflowStepTemplateNextRevision, + Computed: true, + }, + "runtime_source": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSource, + Computed: true, + Attributes: map[string]schema.Attribute{ + "source_config_dest_kind": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceDestKindCommon, + Computed: true, + }, + "additional_config": schema.MapAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceAdditionalConfig, + ElementType: types.StringType, + Computed: true, + }, + "config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfig, + Computed: true, + Attributes: map[string]schema.Attribute{ + "is_private": schema.BoolAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigIsPrivateCommon, + Computed: true, + }, + "auth": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigAuthCommon, + Computed: true, + Sensitive: true, + }, + "docker_image": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigDockerImageCommon, + Computed: true, + }, + "docker_registry_username": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigDockerRegistryUsernameCommon, + Computed: true, + }, + "local_workspace_dir": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigLocalWorkspaceDirCommon, + Computed: true, + }, + }, + }, + }, + }, + }, + } + +} diff --git a/internal/datasources/workflow_step_template_revision/datasource.go b/internal/datasources/workflow_step_template_revision/datasource.go new file mode 100644 index 0000000..d91441c --- /dev/null +++ b/internal/datasources/workflow_step_template_revision/datasource.go @@ -0,0 +1,92 @@ +package workflowsteptemplaterevision + +import ( + "context" + "fmt" + "strings" + + sgclient "github.com/StackGuardian/sg-sdk-go/client" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/customTypes" + workflowsteptemplaterevisionresource "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_step_template_revision" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +var ( + _ datasource.DataSource = &workflowStepTemplateRevisionDatasource{} + _ datasource.DataSourceWithConfigure = &workflowStepTemplateRevisionDatasource{} +) + +// NewDataSource is a helper function to simplify the provider implementation. +func NewDataSource() datasource.DataSource { + return &workflowStepTemplateRevisionDatasource{} +} + +type workflowStepTemplateRevisionDatasource struct { + client *sgclient.Client + orgName string +} + +func (d *workflowStepTemplateRevisionDatasource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_workflow_step_template_revision" +} + +func (d *workflowStepTemplateRevisionDatasource) 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 *workflowStepTemplateRevisionDatasource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config workflowsteptemplaterevisionresource.WorkflowStepTemplateRevisionResourceModel + + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + id := config.Id.ValueString() + if id == "" { + resp.Diagnostics.AddError("id is required", "The id attribute must be provided in the format `templateId:revisionNumber`.") + return + } + + // Extract templateId from the ID (format: templateId:revisionNumber) + templateId := "" + parts := strings.SplitN(id, ":", 2) + if len(parts) == 2 { + templateId = parts[0] + } + + readResp, err := d.client.WorkflowStepTemplateRevision.ReadWorkflowStepTemplateRevision(ctx, d.orgName, id) + if err != nil { + resp.Diagnostics.AddError("Unable to read workflow step template revision.", err.Error()) + return + } + + if readResp == nil || readResp.Msg == nil { + resp.Diagnostics.AddError("Unable to read workflow step template revision.", "API response is empty") + return + } + + revisionModel, diags := workflowsteptemplaterevisionresource.BuildAPIModelToRevisionModel(readResp.Msg, id, templateId) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, revisionModel)...) +} diff --git a/internal/datasources/workflow_step_template_revision/schema.go b/internal/datasources/workflow_step_template_revision/schema.go new file mode 100644 index 0000000..8f04a82 --- /dev/null +++ b/internal/datasources/workflow_step_template_revision/schema.go @@ -0,0 +1,121 @@ +package workflowsteptemplaterevision + +import ( + "context" + + "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" +) + +// Schema defines the schema for the data source. +func (d *workflowStepTemplateRevisionDatasource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Use this data source to read a workflow step template revision.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionId, + Required: true, + }, + "template_id": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionTemplateId, + Computed: true, + }, + "alias": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionAlias, + Computed: true, + }, + "notes": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionNotes, + Computed: true, + }, + "long_description": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionDescription, + Computed: true, + }, + "template_type": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionType, + Computed: true, + }, + "source_config_kind": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateSourceConfigKindCommon, + Computed: true, + }, + "is_active": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateIsActiveCommon, + Computed: true, + }, + "is_public": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateIsPublicCommon, + Computed: true, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionTags, + ElementType: types.StringType, + Computed: true, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionContextTags, + ElementType: types.StringType, + Computed: true, + }, + "runtime_source": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionRuntimeSource, + Computed: true, + Attributes: map[string]schema.Attribute{ + "source_config_dest_kind": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceDestKindCommon, + Computed: true, + }, + "additional_config": schema.MapAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionRuntimeSourceAdditionalConfig, + ElementType: types.StringType, + Computed: true, + }, + "config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionRuntimeSourceConfig, + Computed: true, + Attributes: map[string]schema.Attribute{ + "is_private": schema.BoolAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigIsPrivateCommon, + Computed: true, + }, + "auth": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigAuthCommon, + Computed: true, + Sensitive: true, + }, + "docker_image": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigDockerImageCommon, + Computed: true, + }, + "docker_registry_username": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigDockerRegistryUsernameCommon, + Computed: true, + }, + "local_workspace_dir": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigLocalWorkspaceDirCommon, + Computed: true, + }, + }, + }, + }, + }, + "deprecation": schema.SingleNestedAttribute{ + MarkdownDescription: constants.Deprecation, + Computed: true, + Attributes: map[string]schema.Attribute{ + "effective_date": schema.StringAttribute{ + MarkdownDescription: constants.DeprecationEffectiveDate, + Computed: true, + }, + "message": schema.StringAttribute{ + MarkdownDescription: constants.DeprecationMessage, + Computed: true, + }, + }, + }, + }, + } +} 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..1377588 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,10 @@ 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" + workflowsteptemplatedatasource "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/workflow_step_template" + workflowsteptemplaterevisiondatasource "github.com/StackGuardian/terraform-provider-stackguardian/internal/datasources/workflow_step_template_revision" + 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 +30,10 @@ 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" + workflowsteptemplate "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_step_template" + workflowsteptemplaterevision "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_step_template_revision" + 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 +49,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 +63,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 +187,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 +224,10 @@ func (p *stackguardianProvider) DataSources(_ context.Context) []func() datasour policydatasource.NewDataSource, runnergroupdatasource.NewDataSource, runnergrouptoken.NewDataSource, + workflowsteptemplatedatasource.NewDataSource, + workflowsteptemplaterevisiondatasource.NewDataSource, + workflowtemplatedatasource.NewDataSource, + workflowtemplaterevisiondatasource.NewDataSource, } } @@ -220,10 +236,14 @@ func (p *stackguardianProvider) Resources(_ context.Context) []func() resource.R return []func() resource.Resource{ connector.NewResource, workflowgroup.NewResource, + workflowsteptemplate.NewResource, + workflowsteptemplaterevision.NewResource, role.NewResource, roleassignment.NewResource, 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_step_template/model.go b/internal/resource/workflow_step_template/model.go new file mode 100644 index 0000000..19fa26b --- /dev/null +++ b/internal/resource/workflow_step_template/model.go @@ -0,0 +1,395 @@ +package workflowsteptemplate + +import ( + "context" + + sgsdkgo "github.com/StackGuardian/sg-sdk-go" + "github.com/StackGuardian/sg-sdk-go/workflowsteptemplate" + "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" +) + +// WorkflowStepTemplateResourceModel represents the Terraform resource model +type WorkflowStepTemplateResourceModel struct { + Id types.String `tfsdk:"id"` + TemplateName types.String `tfsdk:"template_name"` + TemplateType types.String `tfsdk:"template_type"` + IsActive types.String `tfsdk:"is_active"` + IsPublic types.String `tfsdk:"is_public"` + Description types.String `tfsdk:"description"` + Tags types.List `tfsdk:"tags"` + ContextTags types.Map `tfsdk:"context_tags"` + SharedOrgsList types.List `tfsdk:"shared_orgs_list"` + RuntimeSource types.Object `tfsdk:"runtime_source"` + SourceConfigKind types.String `tfsdk:"source_config_kind"` + LatestRevision types.Int32 `tfsdk:"latest_revision"` + NextRevision types.Int32 `tfsdk:"next_revision"` +} + +// RuntimeSourceModel represents the runtime source nested object +type RuntimeSourceModel struct { + SourceConfigDestKind types.String `tfsdk:"source_config_dest_kind"` + Config types.Object `tfsdk:"config"` + AdditionalConfig types.Map `tfsdk:"additional_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()}, + "additional_config": types.MapType{ElemType: types.StringType}, + } +} + +// RuntimeSourceConfigModel represents the config nested within runtime source +type RuntimeSourceConfigModel struct { + IsPrivate types.Bool `tfsdk:"is_private"` + Auth types.String `tfsdk:"auth"` + DockerImage types.String `tfsdk:"docker_image"` + DockerRegistryUsername types.String `tfsdk:"docker_registry_username"` + LocalWorkspaceDir types.String `tfsdk:"local_workspace_dir"` +} + +func (RuntimeSourceConfigModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "is_private": types.BoolType, + "auth": types.StringType, + "docker_image": types.StringType, + "docker_registry_username": types.StringType, + "local_workspace_dir": types.StringType, + } +} + +// ToAPIModel converts the RuntimeSourceConfigModel to the SDK config model +func (m *RuntimeSourceConfigModel) ToAPIModel() *workflowsteptemplate.WorkflowStepRuntimeSourceConfig { + return &workflowsteptemplate.WorkflowStepRuntimeSourceConfig{ + DockerImage: m.DockerImage.ValueString(), + IsPrivate: m.IsPrivate.ValueBoolPointer(), + Auth: m.Auth.ValueStringPointer(), + DockerRegistryUsername: m.DockerRegistryUsername.ValueStringPointer(), + LocalWorkspaceDir: m.LocalWorkspaceDir.ValueStringPointer(), + } +} + +// ToAPIModel converts the RuntimeSourceModel to the SDK runtime source model +func (m *RuntimeSourceModel) ToAPIModel(ctx context.Context) (*workflowsteptemplate.WorkflowStepRuntimeSource, diag.Diagnostics) { + apiRuntimeSource := &workflowsteptemplate.WorkflowStepRuntimeSource{ + SourceConfigDestKind: workflowsteptemplate.SourceConfigDestKindContainerRegistryEnum, + } + + if !m.Config.IsUnknown() && !m.Config.IsNull() { + var configModel RuntimeSourceConfigModel + diags := m.Config.As(ctx, &configModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: false, + UnhandledUnknownAsEmpty: false, + }) + if diags.HasError() { + return nil, diags + } + apiRuntimeSource.Config = configModel.ToAPIModel() + } + + if !m.AdditionalConfig.IsUnknown() && !m.AdditionalConfig.IsNull() { + var additionalConfig map[string]interface{} + diags := m.AdditionalConfig.ElementsAs(ctx, &additionalConfig, false) + if diags.HasError() { + return nil, diags + } + apiRuntimeSource.AdditionalConfig = additionalConfig + } + + return apiRuntimeSource, nil +} + +// RuntimeSourceConfigToTerraType converts the SDK config to a Terraform object +func RuntimeSourceConfigToTerraType(config *workflowsteptemplate.WorkflowStepRuntimeSourceConfig) (types.Object, diag.Diagnostics) { + if config == nil { + return types.ObjectNull(RuntimeSourceConfigModel{}.AttributeTypes()), nil + } + + configModel := RuntimeSourceConfigModel{ + DockerImage: flatteners.String(config.DockerImage), + IsPrivate: types.BoolValue(*config.IsPrivate), + Auth: flatteners.StringPtr(config.Auth), + DockerRegistryUsername: flatteners.StringPtr(config.DockerRegistryUsername), + LocalWorkspaceDir: flatteners.StringPtr(config.LocalWorkspaceDir), + } + + return types.ObjectValueFrom(context.Background(), RuntimeSourceConfigModel{}.AttributeTypes(), configModel) +} + +// RuntimeSourceToTerraType converts the SDK runtime source to a Terraform object +func RuntimeSourceToTerraType(runtimeSource *workflowsteptemplate.WorkflowStepRuntimeSource) (types.Object, diag.Diagnostics) { + objectNull := types.ObjectNull(RuntimeSourceModel{}.AttributeTypes()) + if runtimeSource == nil { + return objectNull, nil + } + + runtimeSourceModel := &RuntimeSourceModel{ + SourceConfigDestKind: flatteners.String(string(runtimeSource.SourceConfigDestKind)), + } + + configObj, diags := RuntimeSourceConfigToTerraType(runtimeSource.Config) + if diags.HasError() { + return objectNull, diags + } + runtimeSourceModel.Config = configObj + + if runtimeSource.AdditionalConfig != nil { + acMap, acDiags := types.MapValueFrom(context.Background(), types.StringType, runtimeSource.AdditionalConfig) + diags.Append(acDiags...) + if diags.HasError() { + return objectNull, diags + } + runtimeSourceModel.AdditionalConfig = acMap + } else { + runtimeSourceModel.AdditionalConfig = types.MapNull(types.StringType) + } + + return types.ObjectValueFrom(context.Background(), RuntimeSourceModel{}.AttributeTypes(), runtimeSourceModel) +} + +// ToAPIModel converts the Terraform model to the SDK create request model +func (m *WorkflowStepTemplateResourceModel) ToAPIModel(ctx context.Context) (*workflowsteptemplate.CreateWorkflowStepTemplate, diag.Diagnostics) { + apiModel := &workflowsteptemplate.CreateWorkflowStepTemplate{ + TemplateName: m.TemplateName.ValueString(), + TemplateType: workflowsteptemplate.TemplateTypeWorkflowStepEnum, + ShortDescription: m.Description.ValueStringPointer(), + } + + // Set optional ID + if !m.Id.IsUnknown() && !m.Id.IsNull() { + idStr := m.Id.ValueString() + apiModel.Id = &idStr + } + + // Set IsActive + if !m.IsActive.IsUnknown() && !m.IsActive.IsNull() { + apiModel.IsActive = (*workflowsteptemplate.IsPublicEnum)(m.IsActive.ValueStringPointer()) + } + + // Set IsPublic + if !m.IsPublic.IsUnknown() && !m.IsPublic.IsNull() { + apiModel.IsPublic = (*workflowsteptemplate.IsPublicEnum)(m.IsPublic.ValueStringPointer()) + } + + // Parse tags + tags, diags := expanders.StringList(ctx, m.Tags) + if diags.HasError() { + return nil, diags + } + apiModel.Tags = tags + + // Parse context tags + if !m.ContextTags.IsUnknown() && !m.ContextTags.IsNull() { + var contextTags map[string]string + ctDiags := m.ContextTags.ElementsAs(ctx, &contextTags, false) + diags.Append(ctDiags...) + if diags.HasError() { + return nil, diags + } + apiModel.ContextTags = contextTags + } + + // Parse shared orgs list + sharedOrgs, diags := expanders.StringList(ctx, m.SharedOrgsList) + if diags.HasError() { + return nil, diags + } + apiModel.SharedOrgsList = sharedOrgs + + // Set source config kind + if !m.SourceConfigKind.IsUnknown() && !m.SourceConfigKind.IsNull() { + apiModel.SourceConfigKind = workflowsteptemplate.WorkflowStepTemplateSourceConfigKindDockerImageEnum + } + + // Parse runtime source + if !m.RuntimeSource.IsUnknown() && !m.RuntimeSource.IsNull() { + var runtimeSourceModel RuntimeSourceModel + rsDiags := m.RuntimeSource.As(ctx, &runtimeSourceModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: false, + UnhandledUnknownAsEmpty: false, + }) + diags.Append(rsDiags...) + if diags.HasError() { + return nil, diags + } + + runtimeSource, rsApiDiags := runtimeSourceModel.ToAPIModel(ctx) + diags.Append(rsApiDiags...) + if diags.HasError() { + return nil, diags + } + apiModel.RuntimeSource = runtimeSource + } + + return apiModel, diags +} + +// ToUpdateAPIModel converts the Terraform model to the SDK update request model +func (m *WorkflowStepTemplateResourceModel) ToPatchedAPIModel(ctx context.Context) (*workflowsteptemplate.UpdateWorkflowStepTemplateRequestModel, diag.Diagnostics) { + diags := diag.Diagnostics{} + + apiModel := &workflowsteptemplate.UpdateWorkflowStepTemplateRequestModel{} + + // Set template name + if !m.TemplateName.IsUnknown() && !m.TemplateName.IsNull() { + templateName := m.TemplateName.ValueString() + // Using core.Optional pattern from the sgsdkgo package + apiModel.TemplateName = sgsdkgo.Optional(templateName) + } + + // Set description + if !m.Description.IsUnknown() && !m.Description.IsNull() { + apiModel.ShortDescription = sgsdkgo.Optional(m.Description.ValueString()) + } + + // Set IsActive + if !m.IsActive.IsUnknown() && !m.IsActive.IsNull() { + apiModel.IsActive = sgsdkgo.Optional(workflowsteptemplate.IsPublicEnum(m.IsActive.ValueString())) + } + + // Set IsPublic + if !m.IsPublic.IsUnknown() && !m.IsPublic.IsNull() { + apiModel.IsPublic = sgsdkgo.Optional(workflowsteptemplate.IsPublicEnum(m.IsPublic.ValueString())) + } + + // Parse tags + if !m.Tags.IsUnknown() && !m.Tags.IsNull() { + tags, tagDiags := expanders.StringList(ctx, m.Tags) + diags.Append(tagDiags...) + if diags.HasError() { + return nil, diags + } + apiModel.Tags = sgsdkgo.Optional(tags) + } + + // Parse context tags + if !m.ContextTags.IsUnknown() && !m.ContextTags.IsNull() { + var contextTags map[string]string + ctDiags := m.ContextTags.ElementsAs(ctx, &contextTags, false) + diags.Append(ctDiags...) + if diags.HasError() { + return nil, diags + } + apiModel.ContextTags = sgsdkgo.Optional(contextTags) + } + + // Parse shared orgs list + if !m.SharedOrgsList.IsUnknown() && !m.SharedOrgsList.IsNull() { + sharedOrgs, soDiags := expanders.StringList(ctx, m.SharedOrgsList) + diags.Append(soDiags...) + if diags.HasError() { + return nil, diags + } + apiModel.SharedOrgsList = sgsdkgo.Optional(sharedOrgs) + } + + // Parse runtime source + if !m.RuntimeSource.IsUnknown() && !m.RuntimeSource.IsNull() { + var runtimeSourceModel RuntimeSourceModel + diags := m.RuntimeSource.As(ctx, &runtimeSourceModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: false, + UnhandledUnknownAsEmpty: false, + }) + if diags.HasError() { + return nil, diags + } + + runtimeSource, diags := runtimeSourceModel.ToAPIModel(ctx) + if diags.HasError() { + return nil, diags + } + apiModel.RuntimeSource = sgsdkgo.Optional(*runtimeSource) + } else { + apiModel.RuntimeSource = sgsdkgo.Null[workflowsteptemplate.WorkflowStepRuntimeSource]() + } + + return apiModel, diags +} + +// BuildAPIModelToWorkflowStepTemplateModel converts the SDK response to the Terraform model +func BuildAPIModelToWorkflowStepTemplateModel(apiResponse *workflowsteptemplate.UpdateWorkflowStepTemplateResponse) (*WorkflowStepTemplateResourceModel, diag.Diagnostics) { + diags := diag.Diagnostics{} + + model := &WorkflowStepTemplateResourceModel{ + Id: flatteners.String(apiResponse.Id), + TemplateName: flatteners.String(apiResponse.TemplateName), + TemplateType: flatteners.String(string(apiResponse.TemplateType)), + Description: flatteners.StringPtr(apiResponse.ShortDescription), + SourceConfigKind: flatteners.String(string(apiResponse.SourceConfigKind)), + IsActive: flatteners.StringPtr((*string)(apiResponse.IsActive)), + IsPublic: flatteners.StringPtr((*string)(apiResponse.IsPublic)), + } + + // Handle tags + if apiResponse.Tags != nil { + var tags []types.String + for _, tag := range apiResponse.Tags { + tags = append(tags, flatteners.String(tag)) + } + tagsList, tagDiags := types.ListValueFrom(context.Background(), types.StringType, tags) + diags.Append(tagDiags...) + if diags.HasError() { + return nil, diags + } + model.Tags = tagsList + } else { + model.Tags = types.ListNull(types.StringType) + } + + // Handle context tags + if apiResponse.ContextTags != nil { + contextTagsMap, ctDiags := types.MapValueFrom(context.Background(), types.StringType, apiResponse.ContextTags) + diags.Append(ctDiags...) + if diags.HasError() { + return nil, diags + } + model.ContextTags = contextTagsMap + } else { + model.ContextTags = types.MapNull(types.StringType) + } + + // Handle shared orgs list + if apiResponse.SharedOrgsList != nil { + var orgs []types.String + for _, org := range apiResponse.SharedOrgsList { + orgs = append(orgs, flatteners.String(org)) + } + orgsList, oDiags := types.ListValueFrom(context.Background(), types.StringType, orgs) + diags.Append(oDiags...) + if diags.HasError() { + return nil, diags + } + model.SharedOrgsList = orgsList + } else { + model.SharedOrgsList = types.ListNull(types.StringType) + } + + // Handle runtime source + runtimeSourceObj, rsDiags := RuntimeSourceToTerraType(apiResponse.RuntimeSource) + diags.Append(rsDiags...) + if diags.HasError() { + return nil, diags + } + model.RuntimeSource = runtimeSourceObj + + // Handle revisions + if apiResponse.LatestRevision != nil { + model.LatestRevision = flatteners.Int32(int(*apiResponse.LatestRevision)) + } else { + model.LatestRevision = types.Int32Null() + } + + if apiResponse.NextRevision != nil { + model.NextRevision = flatteners.Int32(int(*apiResponse.NextRevision)) + } else { + model.NextRevision = types.Int32Null() + } + + return model, diags +} diff --git a/internal/resource/workflow_step_template/resource.go b/internal/resource/workflow_step_template/resource.go new file mode 100644 index 0000000..10bf736 --- /dev/null +++ b/internal/resource/workflow_step_template/resource.go @@ -0,0 +1,250 @@ +package workflowsteptemplate + +import ( + "context" + "fmt" + + sgsdkgo "github.com/StackGuardian/sg-sdk-go" + sgclient "github.com/StackGuardian/sg-sdk-go/client" + core "github.com/StackGuardian/sg-sdk-go/core" + "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 = &workflowStepTemplateResource{} + _ resource.ResourceWithConfigure = &workflowStepTemplateResource{} + _ resource.ResourceWithImportState = &workflowStepTemplateResource{} +) + +type workflowStepTemplateResource struct { + client *sgclient.Client + org_name string +} + +// NewResource is a helper function to simplify the provider implementation. +func NewResource() resource.Resource { + return &workflowStepTemplateResource{} +} + +// Metadata returns the resource type name. +func (r *workflowStepTemplateResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_workflow_step_template" +} + +// Configure adds the provider configured client to the resource. +func (r *workflowStepTemplateResource) 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 step template using its ID. +func (r *workflowStepTemplateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...) +} + +func (r *workflowStepTemplateResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + if !req.State.Raw.IsNull() && !req.Plan.Raw.IsNull() { + var state WorkflowStepTemplateResourceModel + var plan WorkflowStepTemplateResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + if !plan.Id.Equal(state.Id) { + resp.RequiresReplace = append(resp.RequiresReplace, path.Root("template_name")) + } + } +} + +// Create creates the resource and sets the initial Terraform state. +func (r *workflowStepTemplateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan WorkflowStepTemplateResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + payload, diags := plan.ToAPIModel(ctx) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + payload.OwnerOrg = fmt.Sprintf("/orgs/%v", r.org_name) + + createResp, err := r.client.WorkflowStepTemplate.CreateWorkflowStepTemplate(ctx, r.org_name, false, payload) + if err != nil { + resp.Diagnostics.AddError("Error creating workflow step template", "Error in creating workflow step template API call: "+err.Error()) + return + } + + readResp, err := r.client.WorkflowStepTemplate.ReadWorkflowStepTemplate(ctx, r.org_name, createResp.Data.Parent.Id) + if err != nil { + resp.Diagnostics.AddError("Error reading workflow step template after create", "Error in reading workflow step template API call: "+err.Error()) + return + } + + templateModel, diags := BuildAPIModelToWorkflowStepTemplateModel(&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 *workflowStepTemplateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state WorkflowStepTemplateResourceModel + + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + templateId := state.Id.ValueString() + if templateId == "" { + resp.Diagnostics.AddError("Error reading workflow step template", "Template ID is empty") + return + } + + readResp, err := r.client.WorkflowStepTemplate.ReadWorkflowStepTemplate(ctx, r.org_name, templateId) + if err != nil { + if apiErr, ok := err.(*core.APIError); ok { + if apiErr.StatusCode == 404 { + tflog.Warn(ctx, "Workflow step template not found, removing from state") + resp.State.RemoveResource(ctx) + return + } + } + + tflog.Error(ctx, err.Error()) + resp.Diagnostics.AddError("Error reading workflow step template", "Error in reading workflow step template API call: "+err.Error()) + return + } + + if readResp == nil { + resp.Diagnostics.AddError("Error reading workflow step template", "API response is empty") + return + } + + templateModel, diags := BuildAPIModelToWorkflowStepTemplateModel(&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 *workflowStepTemplateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan WorkflowStepTemplateResourceModel + var state WorkflowStepTemplateResourceModel + + 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() + if templateId == "" { + resp.Diagnostics.AddError("Error updating workflow step template", "Template ID is empty") + return + } + + payload, diags := plan.ToPatchedAPIModel(ctx) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + payload.OwnerOrg = sgsdkgo.Optional(fmt.Sprintf("/orgs/%v", r.org_name)) + + _, err := r.client.WorkflowStepTemplate.UpdateWorkflowStepTemplate(ctx, r.org_name, templateId, payload) + if err != nil { + tflog.Error(ctx, err.Error()) + resp.Diagnostics.AddError("Error updating workflow step template", "Error in updating workflow step template API call: "+err.Error()) + return + } + + // Read back the template to get all attributes + readResp, err := r.client.WorkflowStepTemplate.ReadWorkflowStepTemplate(ctx, r.org_name, templateId) + if err != nil { + resp.Diagnostics.AddError("Error reading workflow step template after update", "Error in reading workflow step template API call: "+err.Error()) + return + } + + templateModel, diags := BuildAPIModelToWorkflowStepTemplateModel(&readResp.Msg) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &templateModel)...) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *workflowStepTemplateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state WorkflowStepTemplateResourceModel + + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + templateId := state.Id.ValueString() + if templateId == "" { + resp.Diagnostics.AddError("Error deleting workflow step template", "Template ID is empty") + return + } + + err := r.client.WorkflowStepTemplate.DeleteWorkflowStepTemplate(ctx, r.org_name, templateId) + if err != nil { + if apiErr, ok := err.(*core.APIError); ok { + if apiErr.StatusCode == 404 { + // Resource already deleted + return + } + } + + tflog.Error(ctx, err.Error()) + resp.Diagnostics.AddError("Error deleting workflow step template", "Error in deleting workflow step template API call: "+err.Error()) + return + } +} diff --git a/internal/resource/workflow_step_template/resource_test.go b/internal/resource/workflow_step_template/resource_test.go new file mode 100644 index 0000000..954f32a --- /dev/null +++ b/internal/resource/workflow_step_template/resource_test.go @@ -0,0 +1,102 @@ +package workflowsteptemplate_test + +import ( + "fmt" + "net/http" + "testing" + + "github.com/StackGuardian/terraform-provider-stackguardian/internal/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func testAccWorkflowStepTemplateConfigWithRuntime(name string) string { + return fmt.Sprintf(` +resource "stackguardian_workflow_step_template" "test" { + template_name = "%s" + is_active = "0" + is_public = "0" + description = "Test with runtime source" + + source_config_kind = "DOCKER_IMAGE" + + runtime_source = { + source_config_dest_kind = "CONTAINER_REGISTRY" + config = { + docker_image = "ubuntu:latest" + is_private = false + } + } +} +`, name) +} + +func TestAccWorkflowStepTemplate_Basic(t *testing.T) { + name := "example-workflow-step-template1" + var testAccResource = fmt.Sprintf(` +resource "stackguardian_workflow_step_template" "test" { + template_name = "%s" + is_active = "0" + is_public = "0" + description = "Test with runtime source" + + source_config_kind = "DOCKER_IMAGE" + + runtime_source = { + source_config_dest_kind = "CONTAINER_REGISTRY" + config = { + docker_image = "ubuntu:latest" + is_private = false + } + } +} +`, name) + customHeader := http.Header{} + customHeader.Set("x-sg-internal-auth-orgid", "sg-provider-test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProviderFactories(customHeader), + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccResource, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_workflow_step_template.test", "template_name", name), + resource.TestCheckResourceAttr("stackguardian_workflow_step_template.test", "is_public", "0"), + resource.TestCheckResourceAttrSet("stackguardian_workflow_step_template.test", "id"), + ), + }, + // Update and Read testing + { + Config: testAccResource, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_workflow_step_template.test", "template_name", name), + resource.TestCheckResourceAttr("stackguardian_workflow_step_template.test", "is_active", "0"), + ), + }, + // Delete testing automatically occurs + }, + }) +} + +func TestAccWorkflowStepTemplate_WithRuntime(t *testing.T) { + name := "example-workflow-step-template2" + + customHeader := http.Header{} + customHeader.Set("x-sg-internal-auth-orgid", "sg-provider-test") + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProviderFactories(customHeader), + Steps: []resource.TestStep{ + // Create and Read testing with runtime source + { + Config: testAccWorkflowStepTemplateConfigWithRuntime(name), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_workflow_step_template.test", "template_name", name), + resource.TestCheckResourceAttr("stackguardian_workflow_step_template.test", "runtime_source.source_config_dest_kind", "CONTAINER_REGISTRY"), + resource.TestCheckResourceAttr("stackguardian_workflow_step_template.test", "runtime_source.config.docker_image", "ubuntu:latest"), + ), + }, + // Delete testing automatically occurs + }, + }) +} diff --git a/internal/resource/workflow_step_template/schema.go b/internal/resource/workflow_step_template/schema.go new file mode 100644 index 0000000..ed439ae --- /dev/null +++ b/internal/resource/workflow_step_template/schema.go @@ -0,0 +1,132 @@ +package workflowsteptemplate + +import ( + "context" + + "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" +) + +// RuntimeSourceConfigSchemaAttributes returns the common schema attributes for runtime_source.config +// shared between workflow_step_template and workflow_step_template_revision resources. +func RuntimeSourceConfigSchemaAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "is_private": schema.BoolAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigIsPrivateCommon, + Optional: true, + Computed: true, + }, + "auth": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigAuthCommon, + Optional: true, + Sensitive: true, + }, + "docker_image": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigDockerImageCommon, + Required: true, + }, + "docker_registry_username": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigDockerRegistryUsernameCommon, + Optional: true, + }, + "local_workspace_dir": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfigLocalWorkspaceDirCommon, + Optional: true, + }, + } +} + +// Schema defines the schema for the resource. +func (r *workflowStepTemplateResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Manages a workflow step template resource.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: constants.Id, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "template_name": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateName, + Required: true, + }, + "template_type": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateType, + Computed: true, + }, + "is_active": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateIsActiveCommon, + Optional: true, + Computed: true, + }, + "is_public": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateIsPublicCommon, + Optional: true, + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateDescription, + Optional: true, + Computed: true, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateTags, + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateContextTags, + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "shared_orgs_list": schema.ListAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateSharedOrgsList, + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "source_config_kind": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateSourceConfigKindCommon, + Required: true, + }, + "latest_revision": schema.Int32Attribute{ + MarkdownDescription: constants.WorkflowStepTemplateLatestRevision, + Computed: true, + }, + "next_revision": schema.Int32Attribute{ + MarkdownDescription: constants.WorkflowStepTemplateNextRevision, + Computed: true, + }, + "runtime_source": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSource, + Required: true, + Attributes: map[string]schema.Attribute{ + "source_config_dest_kind": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceDestKindCommon, + Optional: true, + Computed: true, + }, + "additional_config": schema.MapAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceAdditionalConfig, + ElementType: types.StringType, + Optional: true, + }, + "config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceConfig, + Required: true, + Attributes: RuntimeSourceConfigSchemaAttributes(), + }, + }, + }, + }, + } +} diff --git a/internal/resource/workflow_step_template_revision/model.go b/internal/resource/workflow_step_template_revision/model.go new file mode 100644 index 0000000..01bbf53 --- /dev/null +++ b/internal/resource/workflow_step_template_revision/model.go @@ -0,0 +1,312 @@ +package workflowsteptemplaterevision + +import ( + "context" + + sgsdkgo "github.com/StackGuardian/sg-sdk-go" + "github.com/StackGuardian/sg-sdk-go/workflowsteptemplate" + "github.com/StackGuardian/sg-sdk-go/workflowsteptemplaterevision" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/expanders" + "github.com/StackGuardian/terraform-provider-stackguardian/internal/flatteners" + workflowsteptemplateresource "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_step_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" +) + +// WorkflowStepTemplateRevisionResourceModel represents the Terraform resource model +type WorkflowStepTemplateRevisionResourceModel struct { + Id types.String `tfsdk:"id"` + TemplateId types.String `tfsdk:"template_id"` + Alias types.String `tfsdk:"alias"` + Notes types.String `tfsdk:"notes"` + LongDescription types.String `tfsdk:"description"` + TemplateType types.String `tfsdk:"template_type"` + SourceConfigKind types.String `tfsdk:"source_config_kind"` + IsActive types.String `tfsdk:"is_active"` + IsPublic types.String `tfsdk:"is_public"` + Tags types.List `tfsdk:"tags"` + ContextTags types.Map `tfsdk:"context_tags"` + RuntimeSource types.Object `tfsdk:"runtime_source"` + Deprecation types.Object `tfsdk:"deprecation"` +} + +// DeprecationModel represents the deprecation nested object +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, + } +} + +// ToAPIModel converts the Terraform model to the SDK create request model +func (m *WorkflowStepTemplateRevisionResourceModel) ToAPIModel(ctx context.Context) (*workflowsteptemplaterevision.CreateWorkflowStepTemplateRevisionModel, diag.Diagnostics) { + apiModel := &workflowsteptemplaterevision.CreateWorkflowStepTemplateRevisionModel{ + TemplateType: workflowsteptemplate.TemplateTypeWorkflowStepEnum, + } + + var diags diag.Diagnostics + + // Set Alias + if !m.Alias.IsUnknown() && !m.Alias.IsNull() { + apiModel.Alias = m.Alias.ValueStringPointer() + } + + // Set Notes + if !m.Notes.IsUnknown() && !m.Notes.IsNull() { + apiModel.Notes = m.Notes.ValueStringPointer() + } + + // Set LongDescription + if !m.LongDescription.IsUnknown() && !m.LongDescription.IsNull() { + apiModel.LongDescription = m.LongDescription.ValueStringPointer() + } + + // Set SourceConfigKind + if !m.SourceConfigKind.IsUnknown() && !m.SourceConfigKind.IsNull() { + apiModel.SourceConfigKind = workflowsteptemplate.WorkflowStepTemplateSourceConfigKindDockerImageEnum + } + + // Set IsActive + if !m.IsActive.IsUnknown() && !m.IsActive.IsNull() { + apiModel.IsActive = (*workflowsteptemplate.IsPublicEnum)(m.IsActive.ValueStringPointer()) + } + + // Set IsPublic + if !m.IsPublic.IsUnknown() && !m.IsPublic.IsNull() { + apiModel.IsPublic = (*workflowsteptemplate.IsPublicEnum)(m.IsPublic.ValueStringPointer()) + } + + // Parse tags + tags, tagDiags := expanders.StringList(ctx, m.Tags) + diags.Append(tagDiags...) + if diags.HasError() { + return nil, diags + } + apiModel.Tags = tags + + // Parse context tags + if !m.ContextTags.IsUnknown() && !m.ContextTags.IsNull() { + var contextTags map[string]string + ctDiags := m.ContextTags.ElementsAs(ctx, &contextTags, false) + diags.Append(ctDiags...) + if diags.HasError() { + return nil, diags + } + apiModel.ContextTags = contextTags + } + + // Parse runtime source + if !m.RuntimeSource.IsUnknown() && !m.RuntimeSource.IsNull() { + var runtimeSourceModel workflowsteptemplateresource.RuntimeSourceModel + diags := m.RuntimeSource.As(ctx, &runtimeSourceModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: false, + UnhandledUnknownAsEmpty: false, + }) + if diags.HasError() { + return nil, diags + } + + runtimeSource, diags := runtimeSourceModel.ToAPIModel(ctx) + if diags.HasError() { + return nil, diags + } + apiModel.RuntimeSource = runtimeSource + } + + // Parse deprecation + if !m.Deprecation.IsUnknown() && !m.Deprecation.IsNull() { + var deprecationModel DeprecationModel + depDiags := m.Deprecation.As(ctx, &deprecationModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: false, + UnhandledUnknownAsEmpty: false, + }) + diags.Append(depDiags...) + if diags.HasError() { + return nil, diags + } + + apiModel.Deprecation = &workflowsteptemplaterevision.Deprecation{ + EffectiveDate: deprecationModel.EffectiveDate.ValueStringPointer(), + Message: deprecationModel.Message.ValueStringPointer(), + } + } + + return apiModel, diags +} + +// ToPatchedAPIModel converts the Terraform model to the SDK update request model +func (m *WorkflowStepTemplateRevisionResourceModel) ToPatchedAPIModel(ctx context.Context) (*workflowsteptemplaterevision.UpdateWorkflowStepTemplateRevisionModel, diag.Diagnostics) { + diags := diag.Diagnostics{} + + apiModel := &workflowsteptemplaterevision.UpdateWorkflowStepTemplateRevisionModel{} + + // Set Alias + if !m.Alias.IsUnknown() && !m.Alias.IsNull() { + apiModel.Alias = sgsdkgo.Optional(m.Alias.ValueString()) + } + + // Set Notes + if !m.Notes.IsUnknown() && !m.Notes.IsNull() { + apiModel.Notes = sgsdkgo.Optional(m.Notes.ValueString()) + } + + // Set LongDescription + if !m.LongDescription.IsUnknown() && !m.LongDescription.IsNull() { + apiModel.LongDescription = sgsdkgo.Optional(m.LongDescription.ValueString()) + } + + // Set SourceConfigKind + if !m.SourceConfigKind.IsUnknown() && !m.SourceConfigKind.IsNull() { + apiModel.SourceConfigKind = sgsdkgo.Optional(workflowsteptemplate.WorkflowStepTemplateSourceConfigKindEnum(m.SourceConfigKind.ValueString())) + } + + // Set IsActive + if !m.IsActive.IsUnknown() && !m.IsActive.IsNull() { + apiModel.IsActive = sgsdkgo.Optional(workflowsteptemplate.IsPublicEnum(m.IsActive.ValueString())) + } + + // Set IsPublic + if !m.IsPublic.IsUnknown() && !m.IsPublic.IsNull() { + apiModel.IsPublic = sgsdkgo.Optional(workflowsteptemplate.IsPublicEnum(m.IsPublic.ValueString())) + } + + // Parse tags + if !m.Tags.IsUnknown() && !m.Tags.IsNull() { + tags, tagDiags := expanders.StringList(ctx, m.Tags) + diags.Append(tagDiags...) + if diags.HasError() { + return nil, diags + } + apiModel.Tags = sgsdkgo.Optional(tags) + } + + // Parse context tags + if !m.ContextTags.IsUnknown() && !m.ContextTags.IsNull() { + var contextTags map[string]string + ctDiags := m.ContextTags.ElementsAs(ctx, &contextTags, false) + diags.Append(ctDiags...) + if diags.HasError() { + return nil, diags + } + apiModel.ContextTags = sgsdkgo.Optional(contextTags) + } + + // Parse runtime source + if !m.RuntimeSource.IsUnknown() && !m.RuntimeSource.IsNull() { + var runtimeSourceModel workflowsteptemplateresource.RuntimeSourceModel + diags := m.RuntimeSource.As(ctx, &runtimeSourceModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: false, + UnhandledUnknownAsEmpty: false, + }) + if diags.HasError() { + return nil, diags + } + + runtimeSource, diags := runtimeSourceModel.ToAPIModel(ctx) + if diags.HasError() { + return nil, diags + } + apiModel.RuntimeSource = sgsdkgo.Optional(*runtimeSource) + } else { + apiModel.RuntimeSource = sgsdkgo.Null[workflowsteptemplate.WorkflowStepRuntimeSource]() + } + + // Parse deprecation + if !m.Deprecation.IsUnknown() && !m.Deprecation.IsNull() { + var deprecationModel DeprecationModel + depDiags := m.Deprecation.As(ctx, &deprecationModel, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: false, + UnhandledUnknownAsEmpty: false, + }) + diags.Append(depDiags...) + if diags.HasError() { + return nil, diags + } + + apiModel.Deprecation = sgsdkgo.Optional(workflowsteptemplaterevision.Deprecation{ + EffectiveDate: deprecationModel.EffectiveDate.ValueStringPointer(), + Message: deprecationModel.Message.ValueStringPointer(), + }) + } + + return apiModel, diags +} + +// BuildAPIModelToRevisionModel converts the SDK response to the Terraform model +func BuildAPIModelToRevisionModel(apiResponse *workflowsteptemplaterevision.WorkflowStepTemplateRevisionResponseData, id string, templateId string) (*WorkflowStepTemplateRevisionResourceModel, diag.Diagnostics) { + diags := diag.Diagnostics{} + + model := &WorkflowStepTemplateRevisionResourceModel{ + Id: flatteners.String(id), + TemplateId: flatteners.String(templateId), + Alias: flatteners.StringPtr(apiResponse.Alias), + Notes: flatteners.StringPtr(apiResponse.Notes), + LongDescription: flatteners.StringPtr(apiResponse.LongDescription), + TemplateType: flatteners.String(string(apiResponse.TemplateType)), + SourceConfigKind: flatteners.String(string(apiResponse.SourceConfigKind)), + IsActive: flatteners.StringPtr((*string)(apiResponse.IsActive)), + IsPublic: flatteners.StringPtr((*string)(apiResponse.IsPublic)), + } + + // Handle tags + if apiResponse.Tags != nil { + var tags []types.String + for _, tag := range apiResponse.Tags { + tags = append(tags, flatteners.String(tag)) + } + tagsList, tagDiags := types.ListValueFrom(context.Background(), types.StringType, tags) + diags.Append(tagDiags...) + if diags.HasError() { + return nil, diags + } + model.Tags = tagsList + } else { + model.Tags = types.ListNull(types.StringType) + } + + // Handle context tags + if apiResponse.ContextTags != nil { + contextTagsMap, ctDiags := types.MapValueFrom(context.Background(), types.StringType, apiResponse.ContextTags) + diags.Append(ctDiags...) + if diags.HasError() { + return nil, diags + } + model.ContextTags = contextTagsMap + } else { + model.ContextTags = types.MapNull(types.StringType) + } + + // Handle runtime source + runtimeSourceObj, rsDiags := workflowsteptemplateresource.RuntimeSourceToTerraType(apiResponse.RuntimeSource) + diags.Append(rsDiags...) + if diags.HasError() { + return nil, diags + } + model.RuntimeSource = runtimeSourceObj + + // Handle deprecation + if apiResponse.Deprecation != nil { + deprecationModel := DeprecationModel{ + EffectiveDate: flatteners.StringPtr(apiResponse.Deprecation.EffectiveDate), + Message: flatteners.StringPtr(apiResponse.Deprecation.Message), + } + + deprecationObj, depDiags := types.ObjectValueFrom(context.Background(), DeprecationModel{}.AttributeTypes(), deprecationModel) + diags.Append(depDiags...) + if diags.HasError() { + return nil, diags + } + model.Deprecation = deprecationObj + } else { + model.Deprecation = types.ObjectNull(DeprecationModel{}.AttributeTypes()) + } + + return model, diags +} diff --git a/internal/resource/workflow_step_template_revision/resource.go b/internal/resource/workflow_step_template_revision/resource.go new file mode 100644 index 0000000..c7d9dae --- /dev/null +++ b/internal/resource/workflow_step_template_revision/resource.go @@ -0,0 +1,252 @@ +package workflowsteptemplaterevision + +import ( + "context" + "fmt" + + sgclient "github.com/StackGuardian/sg-sdk-go/client" + core "github.com/StackGuardian/sg-sdk-go/core" + "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 = &workflowStepTemplateRevisionResource{} + _ resource.ResourceWithConfigure = &workflowStepTemplateRevisionResource{} + _ resource.ResourceWithImportState = &workflowStepTemplateRevisionResource{} +) + +type workflowStepTemplateRevisionResource struct { + client *sgclient.Client + org_name string +} + +// NewResource is a helper function to simplify the provider implementation. +func NewResource() resource.Resource { + return &workflowStepTemplateRevisionResource{} +} + +// Metadata returns the resource type name. +func (r *workflowStepTemplateRevisionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_workflow_step_template_revision" +} + +// Configure adds the provider configured client to the resource. +func (r *workflowStepTemplateRevisionResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + provider, ok := req.ProviderData.(*customTypes.ProviderInfo) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *customTypes.ProviderInfo, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = provider.Client + r.org_name = provider.Org_name +} + +// ImportState imports a workflow step template revision using its ID (format: templateId:revisionNumber). +func (r *workflowStepTemplateRevisionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...) +} + +func (r *workflowStepTemplateRevisionResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + if !req.State.Raw.IsNull() && !req.Plan.Raw.IsNull() { + var plan WorkflowStepTemplateRevisionResourceModel + var state WorkflowStepTemplateRevisionResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + if !plan.Id.Equal(state.Id) { + resp.RequiresReplace = append(resp.RequiresReplace, path.Root("id")) + return + } + } +} + +// Create creates the resource and sets the initial Terraform state. +func (r *workflowStepTemplateRevisionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan WorkflowStepTemplateRevisionResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + templateId := plan.TemplateId.ValueString() + if templateId == "" { + resp.Diagnostics.AddError("Error creating workflow step template revision", "Template ID is required") + return + } + + payload, diags := plan.ToAPIModel(ctx) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + payload.OwnerOrg = fmt.Sprintf("/orgs/%v", r.org_name) + + createResp, err := r.client.WorkflowStepTemplateRevision.CreateWorkflowStepTemplateRevision(ctx, r.org_name, templateId, payload) + if err != nil { + resp.Diagnostics.AddError("Error creating workflow step template revision", "Error in creating workflow step template revision API call: "+err.Error()) + return + } + + // Construct the revision ID from the create response + revisionId := createResp.Data.Revision.Id + + // Read back the revision to get all attributes + readResp, err := r.client.WorkflowStepTemplateRevision.ReadWorkflowStepTemplateRevision(ctx, r.org_name, revisionId) + if err != nil { + resp.Diagnostics.AddError("Error reading workflow step template revision after create", "Error in reading workflow step template revision API call: "+err.Error()) + return + } + + revisionModel, diags := BuildAPIModelToRevisionModel(readResp.Msg, revisionId, templateId) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &revisionModel)...) +} + +// Read refreshes the Terraform state with the latest data. +func (r *workflowStepTemplateRevisionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state WorkflowStepTemplateRevisionResourceModel + + 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 step template revision", "Revision ID is empty") + return + } + + templateId := state.TemplateId.ValueString() + + readResp, err := r.client.WorkflowStepTemplateRevision.ReadWorkflowStepTemplateRevision(ctx, r.org_name, revisionId) + if err != nil { + resp.Diagnostics.AddError("Error reading workflow step template revision", "Error in reading workflow step template revision API call: "+err.Error()) + return + } + + revisionModel, diags := BuildAPIModelToRevisionModel(readResp.Msg, revisionId, 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 *workflowStepTemplateRevisionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan WorkflowStepTemplateRevisionResourceModel + var state WorkflowStepTemplateRevisionResourceModel + + 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 + } + + revisionId := state.Id.ValueString() + if revisionId == "" { + resp.Diagnostics.AddError("Error updating workflow step template revision", "Revision ID is empty") + return + } + + templateId := state.TemplateId.ValueString() + + payload, diags := plan.ToPatchedAPIModel(ctx) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + payload.OwnerOrg = fmt.Sprintf("/orgs/%v", r.org_name) + + _, err := r.client.WorkflowStepTemplateRevision.UpdateWorkflowStepTemplateRevision(ctx, r.org_name, revisionId, payload) + if err != nil { + tflog.Error(ctx, err.Error()) + resp.Diagnostics.AddError("Error updating workflow step template revision", "Error in updating workflow step template revision API call: "+err.Error()) + return + } + + // Read back the revision to get all attributes + readResp, err := r.client.WorkflowStepTemplateRevision.ReadWorkflowStepTemplateRevision(ctx, r.org_name, revisionId) + if err != nil { + resp.Diagnostics.AddError("Error reading workflow step template revision after update", "Error in reading workflow step template revision API call: "+err.Error()) + return + } + + if readResp == nil || readResp.Msg == nil { + resp.Diagnostics.AddError("Error reading workflow step template revision after update", "API response is empty") + return + } + + revisionModel, diags := BuildAPIModelToRevisionModel(readResp.Msg, revisionId, templateId) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &revisionModel)...) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *workflowStepTemplateRevisionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state WorkflowStepTemplateRevisionResourceModel + + 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 step template revision", "Revision ID is empty") + return + } + + err := r.client.WorkflowStepTemplateRevision.DeleteWorkflowStepTemplateRevision(ctx, r.org_name, revisionId, true) + if err != nil { + if apiErr, ok := err.(*core.APIError); ok { + if apiErr.StatusCode == 404 { + // Resource already deleted + return + } + } + + tflog.Error(ctx, err.Error()) + resp.Diagnostics.AddError("Error deleting workflow step template revision", "Error in deleting workflow step template revision API call: "+err.Error()) + return + } +} diff --git a/internal/resource/workflow_step_template_revision/resource_test.go b/internal/resource/workflow_step_template_revision/resource_test.go new file mode 100644 index 0000000..c739f03 --- /dev/null +++ b/internal/resource/workflow_step_template_revision/resource_test.go @@ -0,0 +1,74 @@ +package workflowsteptemplaterevision_test + +import ( + "fmt" + "net/http" + "testing" + + "github.com/StackGuardian/terraform-provider-stackguardian/internal/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func testAccWorkflowStepTemplateRevisionConfig(templateName string, revisionAlias string) string { + return fmt.Sprintf(` +resource "stackguardian_workflow_step_template" "test" { + template_name = "%s" + is_active = "0" + is_public = "0" + description = "Test template for revision" + + source_config_kind = "DOCKER_IMAGE" + + runtime_source = { + source_config_dest_kind = "CONTAINER_REGISTRY" + config = { + docker_image = "ubuntu:latest" + is_private = false + } + } +} + +resource "stackguardian_workflow_step_template_revision" "test" { + template_id = stackguardian_workflow_step_template.test.id + alias = "%s" + notes = "Test revision notes" + + source_config_kind = "DOCKER_IMAGE" + + runtime_source = { + source_config_dest_kind = "CONTAINER_REGISTRY" + config = { + docker_image = "ubuntu:20.04" + is_private = false + } + } +} +`, templateName, revisionAlias) +} + +func TestAccWorkflowStepTemplateRevision_Basic(t *testing.T) { + templateName := "provider-test-workflow-step-template1" + revisionAlias := "v1" + + testAccResource := testAccWorkflowStepTemplateRevisionConfig(templateName, revisionAlias) + customHeader := http.Header{} + customHeader.Set("x-sg-internal-auth-orgid", "sg-provider-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProviderFactories(customHeader), + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccResource, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackguardian_workflow_step_template_revision.test", "alias", revisionAlias), + resource.TestCheckResourceAttr("stackguardian_workflow_step_template_revision.test", "notes", "Test revision notes"), + resource.TestCheckResourceAttr("stackguardian_workflow_step_template_revision.test", "source_config_kind", "DOCKER_IMAGE"), + resource.TestCheckResourceAttrSet("stackguardian_workflow_step_template_revision.test", "id"), + resource.TestCheckResourceAttrSet("stackguardian_workflow_step_template_revision.test", "template_id"), + ), + }, + }, + }) +} diff --git a/internal/resource/workflow_step_template_revision/schema.go b/internal/resource/workflow_step_template_revision/schema.go new file mode 100644 index 0000000..ae0178d --- /dev/null +++ b/internal/resource/workflow_step_template_revision/schema.go @@ -0,0 +1,118 @@ +package workflowsteptemplaterevision + +import ( + "context" + + "github.com/StackGuardian/terraform-provider-stackguardian/internal/constants" + workflowsteptemplateresource "github.com/StackGuardian/terraform-provider-stackguardian/internal/resource/workflow_step_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" +) + +// Schema defines the schema for the resource. +func (r *workflowStepTemplateRevisionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Manages a workflow step template revision resource.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionId, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "template_id": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionTemplateId, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "alias": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionAlias, + Optional: true, + Computed: true, + }, + "notes": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionNotes, + Optional: true, + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionDescription, + Optional: true, + Computed: true, + }, + "template_type": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionType, + Computed: true, + }, + "source_config_kind": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateSourceConfigKindCommon, + Required: true, + }, + "is_active": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateIsActiveCommon, + Optional: true, + Computed: true, + }, + "is_public": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateIsPublicCommon, + Optional: true, + Computed: true, + }, + "tags": schema.ListAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionTags, + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "context_tags": schema.MapAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionContextTags, + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + "runtime_source": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionRuntimeSource, + Required: true, + Attributes: map[string]schema.Attribute{ + "source_config_dest_kind": schema.StringAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRuntimeSourceDestKindCommon, + Required: true, + }, + "additional_config": schema.MapAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionRuntimeSourceAdditionalConfig, + ElementType: types.StringType, + Optional: true, + }, + "config": schema.SingleNestedAttribute{ + MarkdownDescription: constants.WorkflowStepTemplateRevisionRuntimeSourceConfig, + Required: true, + Attributes: workflowsteptemplateresource.RuntimeSourceConfigSchemaAttributes(), + }, + }, + }, + "deprecation": schema.SingleNestedAttribute{ + MarkdownDescription: constants.Deprecation, + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "effective_date": schema.StringAttribute{ + MarkdownDescription: constants.DeprecationEffectiveDate, + Optional: true, + Computed: true, + }, + "message": schema.StringAttribute{ + MarkdownDescription: constants.DeprecationMessage, + Optional: true, + Computed: true, + }, + }, + }, + }, + } +} 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..23092d3 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,7 @@ func main() { Debug: debug, } - err := providerserver.Serve(context.Background(), sgprovider.New(Version), opts) + err := providerserver.Serve(context.Background(), sgprovider.New(Version, http.Header{}), opts) if err != nil { log.Fatal(err.Error())