diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b7066ea..a6fae74 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,7 @@ jobs: (echo; echo "Unexpected difference in directories after code generation. Run 'make docs' command and commit."; exit 1) test-cli: - name: Tines CLI Unit Tests + name: Tines API Client Unit Tests needs: build runs-on: ubuntu-latest steps: @@ -52,7 +52,7 @@ jobs: go-version-file: "go.mod" cache: true - run: go mod download - - run: go test -v -cover ./internal/tines_cli + - run: go test -v -cover ./internal/tines_cli/ test-provider: name: Terraform Provider Acceptance Tests @@ -61,6 +61,7 @@ jobs: timeout-minutes: 15 strategy: fail-fast: false + max-parallel: 1 # Required to avoid resource conflicts in the test tenant matrix: terraform: - "1.1.*" diff --git a/Makefile b/Makefile index 478ad0e..df1fb94 100644 --- a/Makefile +++ b/Makefile @@ -19,17 +19,17 @@ lintfix: .PHONY: install install: brew install golangci-lint - brew install terraform + brew tap hashicorp/tap + brew install hashicorp/tap/terraform go install golang.org/x/tools/gopls@latest - go get github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs + go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@latest # Generate docs .PHONY: docs docs: - go get github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs - go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs + go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@latest # Run local install .PHONY: install-local -install: +install-local: go install diff --git a/docs/guides/version-0.1-upgrade.md b/docs/guides/version-0.1-upgrade.md new file mode 100644 index 0000000..75460b3 --- /dev/null +++ b/docs/guides/version-0.1-upgrade.md @@ -0,0 +1,52 @@ +--- +layout: "" +page_title: "Upgrading to version 0.1.x (from 0.0.x)" +description: Terraform Tines Provider Version 0.1 Upgrade Guide +--- + +# Terraform Tines Provider Version 0.1 Upgrade Guide +Starting with version `0.1.0`, the Tines provider on Terraform introduces a new and simplified way to manage stories. This means that any Resource and its Schema with a version below `0.1.0` will no longer be compatible with `tines` Terraform provider version `0.1.0` or higher, as there are breaking changes in this version. If you have an older Terraform state file for a version below `0.1.0`, we recommend starting fresh by initializing a new state (via `terraform init`) for version `0.1.0` or higher. + + +## Provider Version Configuration +If you are not ready to make a move to version 0.1 of the Tines provider, you may keep the 0.0.x branch active for +your Terraform project by specifying: + +```terraform +provider "tines" { + version = "~> 0.0" + # ... any other configuration +} +``` + +## Getting Started With v0.1.0 +To export your Tines Story, follow [these instructions](https://www.tines.com/docs/stories/importing-and-exporting#exporting-stories) and place the exported filed in the same directory as your `main.tf` file. After that, you can define your story as a Terraform Resource using the following syntax: + +```terraform +# provider.tf +provider "tines" {} + +# main.tf +resource "tines_story" "dev_story_name" { + data = file("${path.module}/story-example.json") + tenant_url = "https://dev-tenant.tines.com" + tines_api_token = var.dev_tines_api_token + team_id = var.team_id # optional + folder_id = var.folder_id # optional +} + +# variable.tf +variable "dev_tines_api_token" { + type = string +} + +variable "team_id" { + type = number +} + +variable "folder_id" { + type = number +} +``` + +And that's all. You don't need to import the state into Terraform either. Running a `terraform apply` will automatically perform an upsert and set the state in Terraform accordingly. \ No newline at end of file diff --git a/docs/guides/version-0.2-upgrade.md b/docs/guides/version-0.2-upgrade.md new file mode 100644 index 0000000..3844f5f --- /dev/null +++ b/docs/guides/version-0.2-upgrade.md @@ -0,0 +1,39 @@ +--- +layout: "" +page_title: "Upgrading to version 0.2.x (from 0.1.x)" +description: Terraform Tines Provider Version 0.2 Upgrade Guide +--- + +# Terraform Tines Provider Version 0.2 Upgrade Guide + +Version 0.2 has made some architectural and configurability changes under the hood in preparation for some significant new functionality, which will be coming in a future release. While all existing resources are compatible with v0.2, some attributes have been deprecated and some new configuration values are required. + +## Provider Version Configuration +If you are not ready to make a move to version 0.2 of the Tines provider, you may keep the 0.1.x branch active for +your Terraform project by specifying: + +```terraform +provider "tines" { + version = "~> 0.1" + # ... any other configuration +} +``` + +We highly recommend that you review this guide, make necessary changes and move to 0.2.x branch, as further 0.1.x releases are +unlikely to happen. + +~> Before attempting to upgrade to version 0.2, you should first upgrade to the + latest version of 0.1 to ensure any transitional updates are applied to your + existing configuration. + +## Provider Global Configuration Changes +The following changes have been made at the provider level: + +- Added a new required configuration value for `tenant` which can be set either in the provider configuration or as the `TINES_TENANT` environment variable. +- Added a new required configuration value for `api_key` which can be set either in the provider configuration or as the `TINES_API_KEY` environment variable. + +## Tines Story Configuration Changes +The following changes have been made to the `tines_story` resource: + +- The `tines_api_token` resource attribute has been marked as deprecated and will be removed in a future release. In version 0.2, any value set here will be ignored and overridden by the provider-level `api_key` attribute. +- The `tenant_url` resource attribute has been marked as deprecated and will be removed in a future release. In version 0.2, any value set here will be ignored and overriden by the provider-level `tenant` attibute. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 84f0f22..6ddbbda 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,36 @@ --- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "tines Provider" -subcategory: "" +layout: "" +page_title: "Provider: Tines" description: |- - + The Tines provider provides resources to interact with the Tines API. --- -# tines Provider - +# Tines Provider +The Tines provider is used to interact with resources supported by Tines. +The provider needs to be configured with the proper credentials before it can be used. ## Example Usage ```terraform -provider "tines" {} +terraform { + required_providers { + tines = { + source = "tines/tines" + version = "~> 0.2.0" + } + } +} + +provider "tines" { + tenant = "https://example.tines.com" + api_key = var.tines_api_key +} + +# Create a Tines Story +resource "tines_story" "example_story" { + # +} ``` @@ -22,4 +39,4 @@ provider "tines" {} ### Optional - `api_key` (String, Sensitive) If this value is not set in the configuration, you must set the TINES_API_KEY environment variable instead. -- `tenant` (String) If this value is not set in the configuration, you must set the TINES_TENANT environment variable instead. +- `tenant` (String) If this value is not set in the configuration, you must set the TINES_TENANT environment variable instead. \ No newline at end of file diff --git a/docs/resources/story.md b/docs/resources/story.md index 7938d5f..e141d02 100644 --- a/docs/resources/story.md +++ b/docs/resources/story.md @@ -1,55 +1,73 @@ --- -# generated by https://github.com/hashicorp/terraform-plugin-docs page_title: "tines_story Resource - terraform-provider-tines" subcategory: "" description: |- - Manage a Tines Story + A Tines Story resource can be managed either via a Story JSON export file, or by setting configuration values on the resource. + We recommend managing Stories via JSON export files if you rely on Terraform to manage change control for storyboard content. Otherwise, if + you use Tines' built-in Change Control feature, we recommend only enforcing configuration values via Terraform. --- # tines_story (Resource) -Manage a Tines Story +A Tines Story resource can be managed either via a Story JSON export file, or by setting configuration values on the resource. +We recommend managing Stories via JSON export files if you rely on Terraform to manage change control for storyboard content. Otherwise, if +you use Tines' built-in Change Control feature, we recommend only enforcing configuration values via Terraform. +## Example Usage +```terraform +# Manage this Story resource using a JSON story export. +resource "tines_story" "example_imported_story" { + team_id = 1 + data = file("${path.module}/story-example.json") +} +# Manage this Story resource using resource attributes. +resource "tines_story" "example_configured_story" { + team_id = 1 + name = "Example Story Name" + change_control_enabled = true +} +``` ## Schema ### Required -- `data` (String) A local JSON file containing an exported Tines story. Setting this value can only be combined with the team_id and folder_id attributes. - `team_id` (Number) The ID of the team that this story belongs to. ### Optional +- `change_control_enabled` (Boolean) Boolean flag indicating if change control is enabled. +- `data` (String) A local JSON file containing an exported Tines story. Setting this value can only be combined with the team_id and folder_id attributes. +- `description` (String) A user-defined description of the story. +- `disabled` (Boolean) Boolean flag indicating whether the story is disabled from running. +- `entry_agent_id` (Number) The ID of the entry action for this story (action must be of type Webhook). +- `exit_agents` (List of Number) An Array of IDs describing exit actions for this story (actions must be message-only mode event transformation). - `folder_id` (Number) The ID of the folder where this story should be organized. The folder ID must belong to the associated team that owns this story. +- `keep_events_for` (Number) Defined event retention period in seconds. +- `locked` (Boolean) Boolean flag indicating whether the story is locked, preventing edits. - `name` (String) The name of the Tines story. -- `tenant_url` (String, Deprecated) [DEPRECATED] Tines tenant URL +- `priority` (Boolean) Boolean flag indicating whether story runs with high priority. +- `send_to_story_access` (String) Controls who is allowed to send to this story (TEAM, GLOBAL, SPECIFIC_TEAMS). default: TEAM. +- `send_to_story_access_source` (String) Valid values are STS, STS_AND_WORKBENCH, WORKBENCH or OFF indicating where the Send to Story can be used. +- `send_to_story_enabled` (Boolean, Deprecated) Boolean flag indicating if Send to Story is enabled. If enabling Send to Story, the entry_agent_id and exit_agent_ids attributes must also be specified. +- `send_to_story_skill_use_requires_confirmation` (Boolean) Boolean flag indicating whether Workbench should ask for confirmation before running this story. +- `shared_team_slugs` (List of String) Array of team slugs that can send to this story. Required to set send_to_story_access to SPECIFIC_TEAMS. +- `tags` (List of String) An array of tag names to apply to the story. +- `tenant_url` (String, Deprecated) Tines tenant URL - `tines_api_token` (String, Sensitive, Deprecated) API token for Tines Tenant ### Read-Only -- `change_control_enabled` (Boolean) -- `created_at` (String) -- `description` (String) -- `disabled` (Boolean) -- `edited_at` (String) -- `entry_agent_id` (Number) -- `exit_agents` (List of Number) -- `guid` (String) +- `created_at` (String) ISO 8601 Timestamp representing date and time the story was created. +- `edited_at` (String) ISO 8601 Timestamp representing date and time the story was last logically updated. +- `guid` (String) The globally unique identifier of the story. - `id` (Number) The Tines-generated identifier for this story. -- `keep_events_for` (Number) - `last_updated` (String) -- `locked` (Boolean) -- `mode` (String) -- `owners` (List of Number) -- `priority` (Boolean) -- `published` (Boolean) -- `send_to_story_access` (String) -- `send_to_story_access_source` (String) -- `send_to_story_enabled` (Boolean) -- `send_to_story_skill_use_requires_confirmation` (Boolean) -- `shared_team_slugs` (List of String) -- `slug` (String) -- `tags` (List of String) -- `user_id` (Number) +- `mode` (String) The mode of the story (LIVE or TEST). +- `owners` (List of Number) List of user IDs that are listed as owners on the story. +- `published` (Boolean) Boolean flag indicating whether the story is published. +- `slug` (String) An underscored representation of the story name. +- `user_id` (Number) ID of the story creator. + diff --git a/examples/internal/main.tf b/examples/internal/main.tf deleted file mode 100644 index abc0c79..0000000 --- a/examples/internal/main.tf +++ /dev/null @@ -1,17 +0,0 @@ -terraform { - required_providers { - tines = { - source = "tines/tines" - } - } -} - -provider "tines" {} - -resource "tines_story" "dev_story_name" { - data = file("${path.module}/dev-story.json") - tenant_url = var.dev_tenant_url - tines_api_token = var.dev_tines_api_token - team_id = var.team_id # optional - # folder_id = var.folder_id # optional -} diff --git a/examples/internal/variables.tf b/examples/internal/variables.tf deleted file mode 100644 index 3dadda4..0000000 --- a/examples/internal/variables.tf +++ /dev/null @@ -1,27 +0,0 @@ -variable "dev_tenant_url" { - type = string -} - -# variable "prod_tenant_url" { -# type = string -# } - -variable "dev_tines_api_token" { - type = string -} - -# variable "prod_tines_api_token" { -# type = string -# } - -# variable "tines_api_token" { -# type = string -# } - -variable "team_id" { - type = number -} - -# variable "folder_id" { -# type = number -# } diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index 149936b..bd90323 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -1 +1,18 @@ -provider "tines" {} +terraform { + required_providers { + tines = { + source = "tines/tines" + version = "~> 0.2.0" + } + } +} + +provider "tines" { + tenant = "https://example.tines.com" + api_key = var.tines_api_key +} + +# Create a Tines Story +resource "tines_story" "example_story" { + # +} \ No newline at end of file diff --git a/examples/resources/story_example/resources.tf b/examples/resources/story_example/resources.tf deleted file mode 100644 index b12f086..0000000 --- a/examples/resources/story_example/resources.tf +++ /dev/null @@ -1,16 +0,0 @@ -resource "tines_story" "dev_story_name" { - data = file("${path.module}/story-example.json") - tenant_url = "https://dev-tenant.tines.com" - tines_api_token = var.dev_tines_api_token - team_id = var.team_id # optional - folder_id = var.folder_id # optional -} - - -resource "tines_story" "prod_story_name" { - data = file("${path.module}/story-example.json") - tenant_url = "https://prod-tenant.tines.com" - tines_api_token = var.prod_tines_api_token - team_id = var.team_id # optional - folder_id = var.folder_id # optional -} diff --git a/examples/resources/story_example/variables.tf b/examples/resources/story_example/variables.tf deleted file mode 100644 index 30def26..0000000 --- a/examples/resources/story_example/variables.tf +++ /dev/null @@ -1,15 +0,0 @@ -variable "prod_tines_api_token" { - type = string -} - -variable "dev_tines_api_token" { - type = string -} - -variable "team_id" { - type = number -} - -variable "folder_id" { - type = number -} diff --git a/examples/resources/tines_story/resource.tf b/examples/resources/tines_story/resource.tf new file mode 100644 index 0000000..bf7bd4a --- /dev/null +++ b/examples/resources/tines_story/resource.tf @@ -0,0 +1,12 @@ +# Manage this Story resource using a JSON story export. +resource "tines_story" "example_imported_story" { + team_id = 1 + data = file("${path.module}/story-example.json") +} + +# Manage this Story resource using resource attributes. +resource "tines_story" "example_configured_story" { + team_id = 1 + name = "Example Story Name" + change_control_enabled = true +} \ No newline at end of file diff --git a/examples/resources/story_example/story-example.json b/examples/resources/tines_story/story-example.json similarity index 100% rename from examples/resources/story_example/story-example.json rename to examples/resources/tines_story/story-example.json diff --git a/go.mod b/go.mod index 6ff511d..a2117f8 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.2 require ( github.com/hashicorp/terraform-plugin-framework v1.13.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 github.com/hashicorp/terraform-plugin-go v0.25.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 @@ -16,24 +17,14 @@ require ( ) require ( - github.com/BurntSushi/toml v1.2.1 // indirect - github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect - github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/ProtonMail/go-crypto v1.1.0-beta.0-proton // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/armon/go-radix v1.0.0 // indirect - github.com/bgentry/speakeasy v0.1.0 // indirect - github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect github.com/cloudflare/circl v1.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/cli v1.1.6 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -49,17 +40,12 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect github.com/hashicorp/terraform-json v0.23.0 // indirect - github.com/hashicorp/terraform-plugin-docs v0.20.0 // indirect - github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.2 // indirect - github.com/huandu/xstrings v1.3.3 // indirect - github.com/imdario/mergo v0.3.15 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect @@ -67,18 +53,11 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/posener/complete v1.2.3 // indirect - github.com/shopspring/decimal v1.3.1 // indirect - github.com/spf13/cast v1.5.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/yuin/goldmark v1.7.7 // indirect - github.com/yuin/goldmark-meta v1.1.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect - go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect golang.org/x/crypto v0.28.0 // indirect - golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.8.0 // indirect @@ -89,6 +68,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7d05ab7..dea31d8 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,5 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= -github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.0-beta.0-proton h1:ZGewsAoeSirbUS5cO8L0FMQA+iSop9xR1nmFYifDBPo= @@ -19,14 +9,6 @@ github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= -github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= -github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= -github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= @@ -61,11 +43,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8= -github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -78,7 +55,6 @@ github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUK github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= @@ -100,10 +76,6 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= -github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c= -github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= -github.com/hashicorp/terraform-plugin-docs v0.20.0 h1:ox7rm1FN0dVZaJBUzkVVh10R1r3+FeMQWL0QopQ9d7o= -github.com/hashicorp/terraform-plugin-docs v0.20.0/go.mod h1:A/+4SVMdAkQYtIBtaxV0H7AU862TxVZk/hhKaMDQB6Y= github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 h1:RXMmu7JgpFjnI1a5QjMCBb11usrW2OtAG+iOTIj5c9Y= @@ -122,11 +94,6 @@ github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= -github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= @@ -149,9 +116,6 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= @@ -160,7 +124,6 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= @@ -170,26 +133,14 @@ github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFz github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -203,25 +154,14 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= -github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark v1.7.7 h1:5m9rrB1sW3JUMToKFQfb+FGt1U7r57IHu5GrYrG2nqU= -github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= -github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= -go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= -go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= @@ -229,7 +169,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -248,19 +187,16 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -286,8 +222,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/provider/story_resource.go b/internal/provider/story_resource.go index c1d3232..b2b9714 100644 --- a/internal/provider/story_resource.go +++ b/internal/provider/story_resource.go @@ -6,7 +6,11 @@ import ( "fmt" "time" + "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -84,10 +88,16 @@ func (r *storyResource) Metadata(ctx context.Context, req resource.MetadataReque resp.TypeName = req.ProviderTypeName + "_story" } +const STORY_RESOURCE_DESCRIPTION = ` +A Tines Story resource can be managed either via a Story JSON export file, or by setting configuration values on the resource. +We recommend managing Stories via JSON export files if you rely on Terraform to manage change control for storyboard content. Otherwise, if +you use Tines' built-in Change Control feature, we recommend only enforcing configuration values via Terraform. +` + // Schema defines the schema for the resource. func (r *storyResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: "Manage a Tines Story", + Description: STORY_RESOURCE_DESCRIPTION, Version: 1, // This needs to be incremented every time we change the schema, and accompanied by a schema migration. Attributes: map[string]schema.Attribute{ "id": schema.Int64Attribute{ @@ -99,7 +109,7 @@ func (r *storyResource) Schema(ctx context.Context, _ resource.SchemaRequest, re }, "data": schema.StringAttribute{ Description: "A local JSON file containing an exported Tines story. Setting this value can only be combined with the team_id and folder_id attributes.", - Required: true, + Optional: true, }, "tines_api_token": schema.StringAttribute{ Description: "API token for Tines Tenant", @@ -108,7 +118,7 @@ func (r *storyResource) Schema(ctx context.Context, _ resource.SchemaRequest, re Sensitive: true, }, "tenant_url": schema.StringAttribute{ - Description: "[DEPRECATED] Tines tenant URL", + Description: "Tines tenant URL", Optional: true, DeprecationMessage: "Value will be overridden by the value set in the provider credentials. This field will be removed in a future version.", PlanModifiers: []planmodifier.String{ @@ -127,6 +137,7 @@ func (r *storyResource) Schema(ctx context.Context, _ resource.SchemaRequest, re Optional: true, Computed: true, PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), int64planmodifier.RequiresReplace(), }, }, @@ -139,81 +150,162 @@ func (r *storyResource) Schema(ctx context.Context, _ resource.SchemaRequest, re }, }, "user_id": schema.Int64Attribute{ - Computed: true, + Description: "ID of the story creator.", + Computed: true, }, "description": schema.StringAttribute{ - Computed: true, + Description: "A user-defined description of the story.", + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.MatchRoot("data")), + }, }, "keep_events_for": schema.Int64Attribute{ - Computed: true, + Description: "Defined event retention period in seconds.", + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.ConflictsWith(path.MatchRoot("data")), + }, }, "disabled": schema.BoolAttribute{ - Computed: true, + Description: "Boolean flag indicating whether the story is disabled from running.", + Optional: true, + Computed: true, + Validators: []validator.Bool{ + boolvalidator.ConflictsWith(path.MatchRoot("data")), + }, }, "priority": schema.BoolAttribute{ - Computed: true, + Description: "Boolean flag indicating whether story runs with high priority.", + Optional: true, + Computed: true, + Validators: []validator.Bool{ + boolvalidator.ConflictsWith(path.MatchRoot("data")), + }, }, "send_to_story_enabled": schema.BoolAttribute{ - Computed: true, + Description: "Boolean flag indicating if Send to Story is enabled. If enabling Send to Story, the entry_agent_id and exit_agent_ids attributes must also be specified.", + Optional: true, + Computed: true, + DeprecationMessage: "This attribute will be removed in a future version. Set the `send_to_story_access_source` attribute instead.", + Validators: []validator.Bool{ + boolvalidator.ConflictsWith(path.MatchRoot("data")), + boolvalidator.AlsoRequires(path.MatchRoot("entry_agent_id"), path.MatchRoot("exit_agent_ids")), + }, }, "send_to_story_access_source": schema.StringAttribute{ - Computed: true, + Description: "Valid values are STS, STS_AND_WORKBENCH, WORKBENCH or OFF indicating where the Send to Story can be used.", + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.MatchRoot("data")), + stringvalidator.OneOf("STS", "STS_AND_WORKBENCH", "WORKBENCH", "OFF"), + }, }, "send_to_story_access": schema.StringAttribute{ - Computed: true, + Description: "Controls who is allowed to send to this story (TEAM, GLOBAL, SPECIFIC_TEAMS). default: TEAM.", + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.MatchRoot("data")), + stringvalidator.OneOf("TEAM", "GLOBAL", "SPECIFIC_TEAMS"), + }, }, "send_to_story_skill_use_requires_confirmation": schema.BoolAttribute{ - Computed: true, + Description: "Boolean flag indicating whether Workbench should ask for confirmation before running this story.", + Optional: true, + Computed: true, + Validators: []validator.Bool{ + boolvalidator.ConflictsWith(path.MatchRoot("data")), + }, }, "shared_team_slugs": schema.ListAttribute{ + Description: "Array of team slugs that can send to this story. Required to set send_to_story_access to SPECIFIC_TEAMS.", ElementType: types.StringType, + Optional: true, Computed: true, + Validators: []validator.List{ + listvalidator.ConflictsWith(path.MatchRoot("data")), + listvalidator.AlsoRequires(path.MatchRoot("send_to_story_access")), + }, }, "entry_agent_id": schema.Int64Attribute{ - Computed: true, + Description: "The ID of the entry action for this story (action must be of type Webhook).", + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.ConflictsWith(path.MatchRoot("data")), + }, }, "exit_agents": schema.ListAttribute{ + Description: "An Array of IDs describing exit actions for this story (actions must be message-only mode event transformation).", ElementType: types.Int64Type, + Optional: true, Computed: true, + Validators: []validator.List{ + listvalidator.ConflictsWith(path.MatchRoot("data")), + }, }, "tags": schema.ListAttribute{ + Description: "An array of tag names to apply to the story.", ElementType: types.StringType, + Optional: true, Computed: true, + Validators: []validator.List{ + listvalidator.ConflictsWith(path.MatchRoot("data")), + }, }, "guid": schema.StringAttribute{ - Computed: true, + Description: "The globally unique identifier of the story.", + Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "slug": schema.StringAttribute{ - Computed: true, + Description: "An underscored representation of the story name.", + Computed: true, }, "created_at": schema.StringAttribute{ - Computed: true, + Description: "ISO 8601 Timestamp representing date and time the story was created.", + Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "edited_at": schema.StringAttribute{ - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, + Description: "ISO 8601 Timestamp representing date and time the story was last logically updated.", + Computed: true, }, "mode": schema.StringAttribute{ - Computed: true, + Description: "The mode of the story (LIVE or TEST).", + Computed: true, }, "published": schema.BoolAttribute{ - Computed: true, + Description: "Boolean flag indicating whether the story is published.", + Computed: true, }, "change_control_enabled": schema.BoolAttribute{ - Computed: true, + Description: "Boolean flag indicating if change control is enabled.", + Optional: true, + Computed: true, + Validators: []validator.Bool{ + boolvalidator.ConflictsWith(path.MatchRoot("data")), + }, }, "locked": schema.BoolAttribute{ - Computed: true, + Description: "Boolean flag indicating whether the story is locked, preventing edits.", + Optional: true, + Computed: true, + Validators: []validator.Bool{ + boolvalidator.ConflictsWith(path.MatchRoot("data")), + }, }, "owners": schema.ListAttribute{ + // This is currently a read-only API attribute, but may become read-write in the future. + Description: "List of user IDs that are listed as owners on the story.", ElementType: types.Int64Type, Computed: true, }, @@ -229,6 +321,7 @@ func (r *storyResource) Create(ctx context.Context, req resource.CreateRequest, tflog.Info(ctx, "Creating Story") var plan storyResourceModel + var story *tines_cli.Story diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -237,85 +330,128 @@ func (r *storyResource) Create(ctx context.Context, req resource.CreateRequest, if !plan.Data.IsNull() { tflog.Info(ctx, "Exported Story payload detected, using the Import strategy") - } - - var data map[string]interface{} - - encData := plan.Data.ValueString() - - err := json.Unmarshal([]byte(encData), &data) - if err != nil { - resp.Diagnostics.AddError("Invalid JSON in file", err.Error()) - return - } - - name, ok := data["name"].(string) - if !ok { - resp.Diagnostics.AddError("Invalid string", "The 'name' field in the imported story must be a string") - return - } - - var importRequest = tines_cli.StoryImportRequest{ - NewName: name, - Data: data, - TeamID: plan.TeamID.ValueInt64(), - FolderID: plan.FolderID.ValueInt64(), - Mode: "versionReplace", - } - - story, err := r.client.ImportStory(&importRequest) - if err != nil { - resp.Diagnostics.AddError( - "Error Importing Tines Story", - "Could not import story, unexpected error: "+err.Error(), - ) - return + story, diags = r.runImportStory(&plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } else { + tflog.Info(ctx, "No exported Story payload detected, using the Create strategy") + // Some fields cannot be set at creation time, and require a subsequent update to the Story + // in order to be set properly. + var requiresUpdate bool + var updateStory tines_cli.Story + var err error + + // Checking for both null and unknown ensures that we're only setting the value + // in the model if it has been explicitly set in the resource configuration. + if !plan.ChangeControlEnabled.IsNull() && !plan.ChangeControlEnabled.IsUnknown() { + requiresUpdate = true + updateStory.ChangeControlEnabled = plan.ChangeControlEnabled.ValueBool() + } + if !plan.STSAccess.IsNull() && !plan.STSAccess.IsUnknown() { + requiresUpdate = true + updateStory.STSAccess = plan.STSAccess.ValueString() + } + if !plan.STSAccessSource.IsNull() && !plan.STSAccessSource.IsUnknown() { + requiresUpdate = true + updateStory.STSAccessSource = plan.STSAccessSource.ValueString() + } + if !plan.STSEnabled.IsNull() && !plan.STSEnabled.IsUnknown() { + requiresUpdate = true + updateStory.STSEnabled = plan.STSEnabled.ValueBool() + } + if !plan.STSSkillConfirmation.IsNull() && !plan.STSSkillConfirmation.IsUnknown() { + requiresUpdate = true + updateStory.STSSkillConfirmation = plan.STSSkillConfirmation.ValueBool() + } + if !plan.SharedTeamSlugs.IsNull() && !plan.SharedTeamSlugs.IsUnknown() { + requiresUpdate = true + diags = plan.SharedTeamSlugs.ElementsAs(ctx, updateStory.SharedTeamSlugs, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + if !plan.Locked.IsNull() && !plan.Locked.IsUnknown() { + requiresUpdate = true + updateStory.Locked = plan.Locked.ValueBool() + } + + // Create the new story first, so we have something to update if needed. + newStory := tines_cli.Story{ + TeamID: plan.TeamID.ValueInt64(), + } + + // Iterate through our optional values to ensure we're only setting parameters + // in the API request body if they are non-default, otherwise we could unintentionally + // set a value to an unexpected default. For example, if the keep_events_for value + // is not explicitly set, the ValueInt64() function will return 0, and we obviously + // don't want to set the value to 0 by default. + if !plan.Name.IsNull() && !plan.Name.IsUnknown() { + newStory.Name = plan.Name.ValueString() + } + + if !plan.Description.IsNull() && !plan.Description.IsUnknown() { + newStory.Description = plan.Description.ValueString() + } + + if !plan.KeepEventsFor.IsNull() && !plan.KeepEventsFor.IsUnknown() { + newStory.KeepEventsFor = plan.KeepEventsFor.ValueInt64() + } + + if !plan.FolderID.IsNull() && !plan.FolderID.IsUnknown() { + newStory.FolderID = plan.FolderID.ValueInt64() + } + + if !plan.Tags.IsNull() && !plan.Tags.IsUnknown() { + diags = plan.Tags.ElementsAs(ctx, newStory.Tags, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + + if !plan.Disabled.IsNull() && !plan.Disabled.IsUnknown() { + newStory.Disabled = plan.Disabled.ValueBool() + } + + if !plan.Priority.IsNull() && !plan.Priority.IsUnknown() { + newStory.Priority = plan.Priority.ValueBool() + } + + story, err = r.client.CreateStory(&newStory) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Tines Story", + "Could not create story, unexpected error: "+err.Error(), + ) + return + } + + // If the story requires an update to set all values, we set the new fields here + // and then return the latest API response values for use in updating our Terraform plan. + // We're not worried about overwriting the story ID value here because it won't change. + if requiresUpdate { + tflog.Info(ctx, "Some fields present require an additional update to the Story for the values to be set, running Story Update.") + story, err = r.client.UpdateStory(story.ID, &updateStory) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Tines Story", + "Could not update story, unexpected error: "+err.Error(), + ) + return + } + } } // Populate all the computed values in the plan. - plan.ID = types.Int64Value(story.ID) - plan.Name = types.StringValue(story.Name) - plan.UserID = types.Int64Value(story.UserID) - plan.Description = types.StringValue(story.Description) - plan.KeepEventsFor = types.Int64Value(story.KeepEventsFor) - plan.Disabled = types.BoolValue(story.Disabled) - plan.Priority = types.BoolValue(story.Priority) - plan.STSEnabled = types.BoolValue(story.STSEnabled) - plan.STSAccessSource = types.StringValue(story.STSAccessSource) - plan.STSAccess = types.StringValue(story.STSAccess) - plan.STSSkillConfirmation = types.BoolValue(story.STSSkillConfirmation) - plan.SharedTeamSlugs, diags = types.ListValueFrom(ctx, types.StringType, story.SharedTeamSlugs) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - plan.EntryAgentID = types.Int64Value(story.EntryAgentID) - plan.ExitAgents, diags = types.ListValueFrom(ctx, types.Int64Type, story.ExitAgents) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - plan.TeamID = types.Int64Value(story.TeamID) - plan.Tags, diags = types.ListValueFrom(ctx, types.StringType, story.Tags) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - plan.Guid = types.StringValue(story.Guid) - plan.Slug = types.StringValue(story.Slug) - plan.CreatedAt = types.StringValue(story.CreatedAt) - plan.EditedAt = types.StringValue(story.EditedAt) - plan.Mode = types.StringValue(story.Mode) - plan.FolderID = types.Int64Value(story.FolderID) - plan.Published = types.BoolValue(story.Published) - plan.ChangeControlEnabled = types.BoolValue(story.ChangeControlEnabled) - plan.Locked = types.BoolValue(story.Locked) - plan.Owners, diags = types.ListValueFrom(ctx, types.Int64Type, story.Owners) + tflog.Info(ctx, "Populating new plan values") + diags = r.convertStoryToPlan(ctx, &plan, story) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) // Set state to fully populated data. diags = resp.State.Set(ctx, plan) @@ -353,8 +489,11 @@ func (r *storyResource) Read(ctx context.Context, req resource.ReadRequest, resp return } - localState.TeamID = types.Int64Value(remoteState.TeamID) - localState.FolderID = types.Int64Value(remoteState.FolderID) + diags := r.convertStoryToPlan(ctx, &localState, remoteState) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } // Set refreshed state. resp.Diagnostics.Append(resp.State.Set(ctx, &localState)...) @@ -369,6 +508,7 @@ func (r *storyResource) Update(ctx context.Context, req resource.UpdateRequest, tflog.Info(ctx, "Updating Story") var plan storyResourceModel + var story *tines_cli.Story diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -377,85 +517,35 @@ func (r *storyResource) Update(ctx context.Context, req resource.UpdateRequest, if !plan.Data.IsNull() { tflog.Info(ctx, "Exported Story payload detected, using the Import strategy") - } - - var data map[string]interface{} - - encData := plan.Data.ValueString() - - err := json.Unmarshal([]byte(encData), &data) - if err != nil { - resp.Diagnostics.AddError("Invalid JSON in file", err.Error()) - return - } - - name, ok := data["name"].(string) - if !ok { - resp.Diagnostics.AddError("Invalid string", "The 'name' field in the imported story must be a string") - return - } - - var importRequest = tines_cli.StoryImportRequest{ - NewName: name, - Data: data, - TeamID: plan.TeamID.ValueInt64(), - FolderID: plan.FolderID.ValueInt64(), - Mode: "versionReplace", - } - - story, err := r.client.ImportStory(&importRequest) - if err != nil { - resp.Diagnostics.AddError( - "Error Importing Tines Story", - "Could not import story, unexpected error: "+err.Error(), - ) - return + story, diags = r.runImportStory(&plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } else { + var storyUpdate tines_cli.Story + var err error + diags = r.convertPlanToStory(ctx, &plan, &storyUpdate) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + story, err = r.client.UpdateStory(plan.ID.ValueInt64(), &storyUpdate) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Tines Story", + "Could not delete story, unexpected error: "+err.Error(), + ) + return + } } // Populate all the computed values in the plan. - plan.ID = types.Int64Value(story.ID) - plan.Name = types.StringValue(story.Name) - plan.UserID = types.Int64Value(story.UserID) - plan.Description = types.StringValue(story.Description) - plan.KeepEventsFor = types.Int64Value(story.KeepEventsFor) - plan.Disabled = types.BoolValue(story.Disabled) - plan.Priority = types.BoolValue(story.Priority) - plan.STSEnabled = types.BoolValue(story.STSEnabled) - plan.STSAccessSource = types.StringValue(story.STSAccessSource) - plan.STSAccess = types.StringValue(story.STSAccess) - plan.STSSkillConfirmation = types.BoolValue(story.STSSkillConfirmation) - plan.SharedTeamSlugs, diags = types.ListValueFrom(ctx, types.StringType, story.SharedTeamSlugs) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - plan.EntryAgentID = types.Int64Value(story.EntryAgentID) - plan.ExitAgents, diags = types.ListValueFrom(ctx, types.Int64Type, story.ExitAgents) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - plan.TeamID = types.Int64Value(story.TeamID) - plan.Tags, diags = types.ListValueFrom(ctx, types.StringType, story.Tags) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - plan.Guid = types.StringValue(story.Guid) - plan.Slug = types.StringValue(story.Slug) - plan.CreatedAt = types.StringValue(story.CreatedAt) - plan.EditedAt = types.StringValue(story.EditedAt) - plan.Mode = types.StringValue(story.Mode) - plan.FolderID = types.Int64Value(story.FolderID) - plan.Published = types.BoolValue(story.Published) - plan.ChangeControlEnabled = types.BoolValue(story.ChangeControlEnabled) - plan.Locked = types.BoolValue(story.Locked) - plan.Owners, diags = types.ListValueFrom(ctx, types.Int64Type, story.Owners) + diags = r.convertStoryToPlan(ctx, &plan, story) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -553,7 +643,7 @@ func (r *storyResource) UpgradeState(ctx context.Context) map[int64]resource.Sta } // Configure adds the provider configured client to the resource. -func (s *storyResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func (r *storyResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { if req.ProviderData == nil { return } @@ -562,12 +652,121 @@ func (s *storyResource) Configure(_ context.Context, req resource.ConfigureReque if !ok { resp.Diagnostics.AddError( - "Unexpected Data Source Configure Type", + "Unexpected Tines Client Configure Type", fmt.Sprintf("Expected *tines_cli.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), ) return } - s.client = client + r.client = client +} + +func (r *storyResource) convertPlanToStory(ctx context.Context, plan *storyResourceModel, story *tines_cli.Story) (diags diag.Diagnostics) { + if !plan.Name.IsNull() && !plan.Name.IsUnknown() { + story.Name = plan.Name.ValueString() + } + + if !plan.Description.IsNull() && !plan.Description.IsUnknown() { + story.Description = plan.Description.ValueString() + } + + if !plan.KeepEventsFor.IsNull() && !plan.KeepEventsFor.IsUnknown() { + story.KeepEventsFor = plan.KeepEventsFor.ValueInt64() + } + + if !plan.SharedTeamSlugs.IsNull() && !plan.SharedTeamSlugs.IsUnknown() { + diags = plan.SharedTeamSlugs.ElementsAs(ctx, story.SharedTeamSlugs, false) + if diags.HasError() { + return + } + } + + return diags +} + +// This is reused in both the Create and Update methods. +func (r *storyResource) convertStoryToPlan(ctx context.Context, plan *storyResourceModel, story *tines_cli.Story) (diags diag.Diagnostics) { + // Populate all the computed values in the plan. + + plan.ID = types.Int64Value(story.ID) + plan.Name = types.StringValue(story.Name) + plan.UserID = types.Int64Value(story.UserID) + plan.Description = types.StringValue(story.Description) + plan.KeepEventsFor = types.Int64Value(story.KeepEventsFor) + plan.Disabled = types.BoolValue(story.Disabled) + plan.Priority = types.BoolValue(story.Priority) + plan.STSEnabled = types.BoolValue(story.STSEnabled) + plan.STSAccessSource = types.StringValue(story.STSAccessSource) + plan.STSAccess = types.StringValue(story.STSAccess) + plan.STSSkillConfirmation = types.BoolValue(story.STSSkillConfirmation) + plan.SharedTeamSlugs, diags = types.ListValueFrom(ctx, types.StringType, story.SharedTeamSlugs) + if diags.HasError() { + return diags + } + plan.EntryAgentID = types.Int64Value(story.EntryAgentID) + plan.ExitAgents, diags = types.ListValueFrom(ctx, types.Int64Type, story.ExitAgents) + if diags.HasError() { + return diags + } + plan.TeamID = types.Int64Value(story.TeamID) + plan.Tags, diags = types.ListValueFrom(ctx, types.StringType, story.Tags) + if diags.HasError() { + return diags + } + plan.Guid = types.StringValue(story.Guid) + plan.Slug = types.StringValue(story.Slug) + plan.CreatedAt = types.StringValue(story.CreatedAt) + plan.EditedAt = types.StringValue(story.EditedAt) + plan.Mode = types.StringValue(story.Mode) + plan.FolderID = types.Int64Value(story.FolderID) + plan.Published = types.BoolValue(story.Published) + plan.ChangeControlEnabled = types.BoolValue(story.ChangeControlEnabled) + plan.Locked = types.BoolValue(story.Locked) + plan.Owners, diags = types.ListValueFrom(ctx, types.Int64Type, story.Owners) + if diags.HasError() { + return diags + } + plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC3339)) + + return diags +} + +// This is reused in both the Create and Update methods when the resource management strategy is set to use +// imported stories to override all values. +func (r *storyResource) runImportStory(plan *storyResourceModel) (story *tines_cli.Story, diags diag.Diagnostics) { + var data map[string]interface{} + + encData := plan.Data.ValueString() + + err := json.Unmarshal([]byte(encData), &data) + if err != nil { + diags.AddError("Invalid JSON in file", err.Error()) + return + } + + name, ok := data["name"].(string) + if !ok { + diags.AddError("Invalid string", "The 'name' field in the imported story must be a string") + return + } + + var importRequest = tines_cli.StoryImportRequest{ + NewName: name, + Data: data, + TeamID: plan.TeamID.ValueInt64(), + FolderID: plan.FolderID.ValueInt64(), + Mode: "versionReplace", + } + + story, err = r.client.ImportStory(&importRequest) + if err != nil { + diags.AddError( + "Error Importing Tines Story", + "Could not import story, unexpected error: "+err.Error(), + ) + return + } + + return story, diags } diff --git a/internal/tines_cli/stories.go b/internal/tines_cli/stories.go index d291d5a..2d6b543 100644 --- a/internal/tines_cli/stories.go +++ b/internal/tines_cli/stories.go @@ -14,57 +14,68 @@ type StoryImportRequest struct { } type Story struct { - ID int64 `json:"id"` - Name string `json:"name"` - UserID int64 `json:"user_id"` - Description string `json:"description"` - KeepEventsFor int64 `json:"keep_events_for"` - Disabled bool `json:"disabled"` - Priority bool `json:"priority"` - STSEnabled bool `json:"send_to_story_enabled"` - STSAccessSource string `json:"send_to_story_access_source"` - STSAccess string `json:"send_to_story_access"` - STSSkillConfirmation bool `json:"send_to_story_skill_use_requires_confirmation"` + ID int64 `json:"id,omitempty"` + Name string `json:"name,omitempty"` + UserID int64 `json:"user_id,omitempty"` + Description string `json:"description,omitempty"` + KeepEventsFor int64 `json:"keep_events_for,omitempty"` + Disabled bool `json:"disabled,omitempty"` + Priority bool `json:"priority,omitempty"` + STSEnabled bool `json:"send_to_story_enabled,omitempty"` + STSAccessSource string `json:"send_to_story_access_source,omitempty"` + STSAccess string `json:"send_to_story_access,omitempty"` + STSSkillConfirmation bool `json:"send_to_story_skill_use_requires_confirmation,omitempty"` SharedTeamSlugs []string `json:"shared_team_slugs,omitempty"` EntryAgentID int64 `json:"entry_agent_id,omitempty"` ExitAgents []int64 `json:"exit_agents,omitempty"` - TeamID int64 `json:"team_id"` + TeamID int64 `json:"team_id,omitempty"` Tags []string `json:"tags,omitempty"` - Guid string `json:"guid"` - Slug string `json:"slug"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - EditedAt string `json:"edited_at"` - Mode string `json:"mode"` - FolderID int64 `json:"folder_id"` - Published bool `json:"published"` - ChangeControlEnabled bool `json:"change_control_enabled"` - Locked bool `json:"locked"` - Owners []int64 `json:"owners"` + Guid string `json:"guid,omitempty"` + Slug string `json:"slug,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + EditedAt string `json:"edited_at,omitempty"` + Mode string `json:"mode,omitempty"` + FolderID int64 `json:"folder_id,omitempty"` + Published bool `json:"published,omitempty"` + ChangeControlEnabled bool `json:"change_control_enabled,omitempty"` + Locked bool `json:"locked,omitempty"` + Owners []int64 `json:"owners,omitempty"` } -// Import a new story, or update an existing one. -func (c *Client) ImportStory(story *StoryImportRequest) (*Story, error) { +// Create a new story. +func (c *Client) CreateStory(new *Story) (*Story, error) { newStory := Story{} - req, err := json.Marshal(&story) + req, err := json.Marshal(&new) + fmt.Printf("REQUEST BODY: %s", string(req)) if err != nil { return &newStory, err } - _, body, err := c.doRequest("POST", "/api/v1/stories/import", req) + status, body, err := c.doRequest("POST", "/api/v1/stories", req) if err != nil { return &newStory, err } err = json.Unmarshal(body, &newStory) if err != nil { + fmt.Printf("HTTP STATUS: %d, BODY: %s", status, string(body)) return &newStory, err } return &newStory, nil } +// Delete a story. +func (c *Client) DeleteStory(id int64) error { + resource := fmt.Sprintf("/api/v1/stories/%d", id) + + _, _, err := c.doRequest("DELETE", resource, nil) + + return err +} + // Get current state for a story. func (c *Client) GetStory(id int64) (status int, story *Story, e error) { resource := fmt.Sprintf("/api/v1/stories/%d", id) @@ -82,11 +93,47 @@ func (c *Client) GetStory(id int64) (status int, story *Story, e error) { return status, story, err } -// Delete a story. -func (c *Client) DeleteStory(id int64) error { +// Import a new story, or override an existing one. +func (c *Client) ImportStory(story *StoryImportRequest) (*Story, error) { + newStory := Story{} + + req, err := json.Marshal(&story) + if err != nil { + return &newStory, err + } + + _, body, err := c.doRequest("POST", "/api/v1/stories/import", req) + if err != nil { + return &newStory, err + } + + err = json.Unmarshal(body, &newStory) + if err != nil { + return &newStory, err + } + + return &newStory, nil +} + +// Update a story. +func (c *Client) UpdateStory(id int64, values *Story) (*Story, error) { + updatedStory := Story{} resource := fmt.Sprintf("/api/v1/stories/%d", id) - _, _, err := c.doRequest("DELETE", resource, nil) + req, err := json.Marshal(&values) + if err != nil { + return &updatedStory, err + } - return err + _, body, err := c.doRequest("PUT", resource, req) + if err != nil { + return &updatedStory, err + } + + err = json.Unmarshal(body, &updatedStory) + if err != nil { + return &updatedStory, err + } + + return &updatedStory, err } diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl new file mode 100644 index 0000000..40c71ce --- /dev/null +++ b/templates/index.md.tmpl @@ -0,0 +1,24 @@ +--- +layout: "" +page_title: "Provider: Tines" +description: |- + The Tines provider provides resources to interact with the Tines API. +--- + +# Tines Provider + +The Tines provider is used to interact with resources supported by Tines. +The provider needs to be configured with the proper credentials before it can be used. + +## Upgrading Versions +Because the Tines Terraform provider is still under active development, breaking changes +may occur in minor versions for any 0.x.x release. Future stable versions (1.x.x and above) +will follow SemVer principles for backwards compatibility in minor and patch version updates. + + + +## Example Usage + +{{ tffile "examples/provider/provider.tf" }} + +{{ .SchemaMarkdown | trimspace }} \ No newline at end of file diff --git a/templates/resources.md.tmpl b/templates/resources.md.tmpl new file mode 100644 index 0000000..bb3b7f8 --- /dev/null +++ b/templates/resources.md.tmpl @@ -0,0 +1,25 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{codefile "terraform" .ExampleFile}} +{{- end }} +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{codefile "shell" .ImportFile}} +{{- end }} \ No newline at end of file