From 068a22697e08a30c09544e0e5a3715d7b744b1d7 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Thu, 11 Jan 2024 13:24:08 -0500 Subject: [PATCH] Implement `migrate` sub-command (#314) * Add migrate subcommand * Change migrated file extensions to `.md.tmpl` * Finish `migrate` subcommand implementation * Remove `-help` output check * Add changelog entry * Update changelog entry Co-authored-by: Brian Flad * fix changelog entry * Remove `--old-website-source-dir` flag and support migrating `docs/` subdirectory * Support converting files with any file extension to `.md.tmpl` * Refactor `Migrate()` method * Skip `layout` front matter * Fix linting errors * Add comment to generated templates explaining functionality * Remove `website/` directory at the end of migration * Fix `ineffassign` lint error * Update README.md Co-authored-by: Austin Valle * Resolve comments from code review --------- Co-authored-by: Brian Flad Co-authored-by: Austin Valle --- .../unreleased/FEATURES-20231220-141244.yaml | 7 + README.md | 65 +- cmd/tfplugindocs/main_test.go | 8 + .../time_provider_success_docs_website.txtar | 892 +++++++++++++++++ ...time_provider_success_legacy_website.txtar | 896 ++++++++++++++++++ go.mod | 5 + go.sum | 12 +- internal/cmd/migrate.go | 96 ++ internal/cmd/run.go | 9 + internal/provider/migrate.go | 397 ++++++++ internal/provider/template.go | 6 + internal/provider/util.go | 23 + 12 files changed, 2413 insertions(+), 3 deletions(-) create mode 100644 .changes/unreleased/FEATURES-20231220-141244.yaml create mode 100644 cmd/tfplugindocs/testdata/scripts/schema-json/migrate/time_provider_success_docs_website.txtar create mode 100644 cmd/tfplugindocs/testdata/scripts/schema-json/migrate/time_provider_success_legacy_website.txtar create mode 100644 internal/cmd/migrate.go create mode 100644 internal/provider/migrate.go diff --git a/.changes/unreleased/FEATURES-20231220-141244.yaml b/.changes/unreleased/FEATURES-20231220-141244.yaml new file mode 100644 index 00000000..ee1fb4d0 --- /dev/null +++ b/.changes/unreleased/FEATURES-20231220-141244.yaml @@ -0,0 +1,7 @@ +kind: FEATURES +body: 'migrate: Added new `migrate` subcommand that migrates existing provider docs using the + rendered website source directories (`website/docs/` or `/docs/`) to a `terraform-plugin-docs`-supported + templates directory.' +time: 2023-12-20T14:12:44.820323-05:00 +custom: + Issue: "314" diff --git a/README.md b/README.md index bf82fb63..ac96c2e7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ The primary way users will interact with this is the `tfplugindocs` CLI tool to ## `tfplugindocs` -The `tfplugindocs` CLI has two main commands, `validate` and `generate` (`generate` is the default). +The `tfplugindocs` CLI has three main commands, `migrate`, `validate` and `generate` (`generate` is the default). This tool will let you generate documentation for your provider from live example `.tf` files and markdown templates. It will also export schema information from the provider (using `terraform providers schema -json`), and sync the schema with the reference documents. @@ -28,6 +28,7 @@ Usage: tfplugindocs [--version] [--help] [] Available commands are: the generate command is run by default generate generates a plugin website from code, templates, and examples + migrate migrates website files from either the legacy rendered website directory (`website/docs/r`) or the docs rendered website directory (`docs/resources`) to the tfplugindocs supported structure (`templates/`). validate validates a plugin website for the current directory ``` @@ -59,6 +60,18 @@ $ tfplugindocs validate --help Usage: tfplugindocs validate [] ``` +`migrate` command: + +```shell +$ tfplugindocs migrate --help + +Usage: tfplugindocs migrate [] + + --examples-dir examples directory based on provider-dir (default: "examples") + --provider-dir relative or absolute path to the root provider code directory when running the command outside the root provider code directory + --templates-dir new website templates directory based on provider-dir; files will be migrated to this directory (default: "templates") +``` + ### How it Works When you run `tfplugindocs`, by default from the root directory of a provider codebase, the tool takes the following actions: @@ -100,6 +113,23 @@ Otherwise, the provider developer can set an arbitrary description like this: // ... ``` +#### Migrate subcommand + +The `migrate` subcommand can be used to migrate website files from either the legacy rendered website directory (`website/docs/r`) or the docs +rendered website directory (`docs/resources`) to the `tfplugindocs` supported structure (`templates/`). Markdown files in the rendered website +directory will be converted to `tfplugindocs` templates. The legacy `website/` directory will be removed after migration to avoid Terraform Registry +ingress issues. + +The `migrate` subcommand takes the following actions: +1. Determines the rendered website directory based on the `--provider-dir` argument +2. Copies the contents of the rendered website directory to the `--templates-dir` folder (will create this folder if it doesn't exist) +3. (if the rendered website is using legacy format) Renames `docs/d/` and `docs/r/` subdirectories to `data-sources/` and `resources/` respectively +4. Change file suffixes for Markdown files to `.md.tmpl` to create website templates +5. Extracts code blocks from website docs to create individual example files in `--examples-dir` (will create this folder if it doesn't exist) +6. Replace extracted example code in website templates with `codefile`/`tffile` template functions referencing the example files. +7. Copies non-template files to `--templates-dir` folder +8. Removes the `website/` directory + ### Conventional Paths The generation of missing documentation is based on a number of assumptions / conventional paths. @@ -130,6 +160,37 @@ For examples: | `examples/resources//resource.tf` | Resource example config | | `examples/resources//import.sh` | Resource example import command | +#### Migration + +The `migrate` subcommand assumes the following conventional paths for the rendered website directory: + +Legacy website directory structure: + +| Path | Description | +|---------------------------------------------------|-----------------------------| +| `website/` | Root of website docs | +| `website/docs/guides` | Root of guides subdirectory | +| `website/docs/index.html.markdown` | Docs index page | +| `website/docs/d/.html.markdown` | Data source page | +| `website/docs/r/.html.markdown` | Resource page | + +Docs website directory structure: + +| Path | Description | +|------------------------------------------------------|-----------------------------| +| `docs/` | Root of website docs | +| `docs/guides` | Root of guides subdirectory | +| `docs/index.html.markdown` | Docs index page | +| `docs/data-sources/.html.markdown` | Data source page | +| `docs/resources/.html.markdown` | Resource page | + +Files named `index` (before the first `.`) in the website docs root directory and files in the `website/docs/d/`, `website/docs/r/`, `docs/data-sources/`, +and `docs/resources/` subdirectories will be converted to `tfplugindocs` templates. + +The `website/docs/guides/` and `docs/guides/` subdirectories will be copied as-is to the `--templates-dir` folder. + +All other files in the conventional paths will be ignored. + ### Templates The templates are implemented with Go [`text/template`](https://golang.org/pkg/text/template/) @@ -179,7 +240,7 @@ using the following data fields and functions: | `tffile` | A special case of the `codefile` function, designed for Terraform files (i.e. `.tf`). | | `trimspace` | Equivalent to [`strings.TrimSpace`](https://pkg.go.dev/strings#TrimSpace). | | `upper` | Equivalent to [`strings.ToUpper`](https://pkg.go.dev/strings#ToUpper). | - + ## Disclaimer This is still under development: while it's being used for production-ready providers, you might still find bugs diff --git a/cmd/tfplugindocs/main_test.go b/cmd/tfplugindocs/main_test.go index b8799966..703dd4cb 100644 --- a/cmd/tfplugindocs/main_test.go +++ b/cmd/tfplugindocs/main_test.go @@ -46,3 +46,11 @@ func Test_SchemaJson_GenerateAcceptanceTests(t *testing.T) { Dir: "testdata/scripts/schema-json/generate", }) } + +func Test_SchemaJson_MigrateAcceptanceTests(t *testing.T) { + t.Parallel() + + testscript.Run(t, testscript.Params{ + Dir: "testdata/scripts/schema-json/migrate", + }) +} diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/migrate/time_provider_success_docs_website.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/migrate/time_provider_success_docs_website.txtar new file mode 100644 index 00000000..15b8c754 --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/migrate/time_provider_success_docs_website.txtar @@ -0,0 +1,892 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs -migrate on the Time provider using the docs website layout +[!unix] skip + +# Run migrate command +exec tfplugindocs migrate +cmpenv stdout expected-output.txt + +# Check template files +cmpenv templates/index.md.tmpl exp-templates/index.md.tmpl +cmpenv templates/resources/offset.md.tmpl exp-templates/resources/offset.md.tmpl +cmpenv templates/resources/rotating.md.tmpl exp-templates/resources/rotating.md.tmpl +cmpenv templates/resources/sleep.md.tmpl exp-templates/resources/sleep.md.tmpl +cmpenv templates/resources/static.md.tmpl exp-templates/resources/static.md.tmpl + +# Check generated example files +cmpenv examples/example_1.tf examples/example_1.tf + +cmpenv examples/resources/offset/example_1.tf exp-examples/resources/offset/example_1.tf +cmpenv examples/resources/offset/example_2.tf exp-examples/resources/offset/example_2.tf +cmpenv examples/resources/offset/import_1.sh exp-examples/resources/offset/import_1.sh + +cmpenv examples/resources/rotating/example_1.tf exp-examples/resources/rotating/example_1.tf +cmpenv examples/resources/rotating/import_1.sh exp-examples/resources/rotating/import_1.sh +cmpenv examples/resources/rotating/import_2.sh exp-examples/resources/rotating/import_2.sh + +cmpenv examples/resources/sleep/example_1.tf exp-examples/resources/sleep/example_1.tf +cmpenv examples/resources/sleep/example_2.tf exp-examples/resources/sleep/example_2.tf +cmpenv examples/resources/sleep/example_3.tf exp-examples/resources/sleep/example_3.tf +cmpenv examples/resources/sleep/import_1.sh exp-examples/resources/sleep/import_1.sh +cmpenv examples/resources/sleep/import_2.sh exp-examples/resources/sleep/import_2.sh + +cmpenv examples/resources/static/example_1.tf examples/resources/static/example_1.tf +cmpenv examples/resources/static/example_2.tf examples/resources/static/example_2.tf +cmpenv examples/resources/static/import_1.sh examples/resources/static/import_1.sh + +-- expected-output.txt -- +migrating website from "$WORK/docs" to "$WORK/templates" +migrating provider index: index.html.markdown +migrating file "index.html.markdown" +extracting YAML frontmatter to "$WORK/templates/index.md.tmpl" +extracting code examples from "index.html.markdown" +creating example file "$WORK/examples/example_1.tf" +finished creating template "$WORK/templates/index.md.tmpl" +migrating resources directory: resources +migrating file "offset.html.markdown" +extracting YAML frontmatter to "$WORK/templates/resources/offset.md.tmpl" +extracting code examples from "offset.html.markdown" +creating example file "$WORK/examples/resources/offset/example_1.tf" +creating example file "$WORK/examples/resources/offset/example_2.tf" +creating import file "$WORK/examples/resources/offset/import_1.sh" +finished creating template "$WORK/templates/resources/offset.md.tmpl" +migrating file "rotating.html.markdown" +extracting YAML frontmatter to "$WORK/templates/resources/rotating.md.tmpl" +extracting code examples from "rotating.html.markdown" +creating example file "$WORK/examples/resources/rotating/example_1.tf" +creating import file "$WORK/examples/resources/rotating/import_1.sh" +creating import file "$WORK/examples/resources/rotating/import_2.sh" +finished creating template "$WORK/templates/resources/rotating.md.tmpl" +migrating file "sleep.html.markdown" +extracting YAML frontmatter to "$WORK/templates/resources/sleep.md.tmpl" +extracting code examples from "sleep.html.markdown" +creating example file "$WORK/examples/resources/sleep/example_1.tf" +creating example file "$WORK/examples/resources/sleep/example_2.tf" +creating example file "$WORK/examples/resources/sleep/example_3.tf" +creating import file "$WORK/examples/resources/sleep/import_1.sh" +creating import file "$WORK/examples/resources/sleep/import_2.sh" +finished creating template "$WORK/templates/resources/sleep.md.tmpl" +migrating file "static.html.markdown" +extracting YAML frontmatter to "$WORK/templates/resources/static.md.tmpl" +extracting code examples from "static.html.markdown" +creating example file "$WORK/examples/resources/static/example_1.tf" +creating example file "$WORK/examples/resources/static/example_2.tf" +creating import file "$WORK/examples/resources/static/import_1.sh" +finished creating template "$WORK/templates/resources/static.md.tmpl" +-- docs/index.html.markdown -- +--- +layout: "time" +page_title: "Provider: Time" +description: |- + The time provider is used to interact with time-based resources. +--- + +# Time Provider + +The time provider is used to interact with time-based resources. The provider itself has no configuration options. + +Use the navigation to the left to read about the available resources. + +## Resource "Triggers" + +Certain time resources, only perform actions during specific lifecycle actions: + +- `time_offset`: Saves base timestamp into Terraform state only when created. +- `time_sleep`: Sleeps when created and/or destroyed. +- `time_static`: Saves base timestamp into Terraform state only when created. + +These resources provide an optional map argument called `triggers` that can be populated with arbitrary key/value pairs. When the keys or values of this argument are updated, Terraform will re-perform the desired action, such as updating the base timestamp or sleeping again. + +For example: + +```hcl +resource "time_static" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_static resource to ensure that + # both will change together. + ami = time_static.ami_update.triggers.ami_id + + tags = { + AmiUpdateTime = time_static.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +``` + +`triggers` are *not* treated as sensitive attributes; a value used for `triggers` will be displayed in Terraform UI output as plaintext. + +To force a these actions to reoccur without updating `triggers`, the [`terraform taint` command](https://www.terraform.io/docs/commands/taint.html) can be used to produce the action on the next run. +-- docs/resources/offset.html.markdown -- +--- +layout: "time" +page_title: "Time: time_offset" +description: |- + Manages a offset time resource. +--- + +# Resource: time_offset + +Manages an offset time resource, which keeps an UTC timestamp stored in the Terraform state that is offset from a locally sourced base timestamp. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html). + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +### Basic Usage + +```hcl +resource "time_offset" "example" { + offset_days = 7 +} + +output "one_week_from_now" { + value = time_offset.example.rfc3339 +} +``` + +### Triggers Usage + +```hcl +resource "time_offset" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } + + offset_days = 7 +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_offset resource to ensure that + # both will change together. + ami = time_offset.ami_update.triggers.ami_id + + tags = { + ExpirationTime = time_offset.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +``` + +## Argument Reference + +~> **NOTE:** At least one of the `offset_` arguments must be configured. + +The following arguments are optional: + +* `base_rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. See [the main provider documentation](../index.html) for more information. +* `offset_days` - (Optional) Number of days to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_hours` - (Optional) Number of hours to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_minutes` - (Optional) Number of minutes to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_months` - (Optional) Number of months to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_seconds` - (Optional) Number of seconds to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_years` - (Optional) Number of years to offset the base timestamp. Conflicts with other `offset_` arguments. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of offset timestamp. +* `hour` - Number hour of offset timestamp. +* `id` - UTC RFC3339 format of the base timestamp, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of offset timestamp. +* `month` - Number month of offset timestamp. +* `rfc3339` - UTC RFC3339 format of the offset timestamp, e.g. `2020-02-12T06:36:13Z`. +* `second` - Number second of offset timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of offset timestamp. + +## Import + +This resource can be imported using the base UTC RFC3339 timestamp and offset years, months, days, hours, minutes, and seconds, separated by commas (`,`), e.g. + +```console +$ terraform import time_offset.example 2020-02-12T06:36:13Z,0,0,7,0,0,0 +``` + +The `triggers` argument cannot be imported. +-- docs/resources/rotating.html.markdown -- +--- +layout: "time" +page_title: "Time: time_rotating" +description: |- + Manages a rotating time resource. +--- + +# Resource: time_rotating + +Manages a rotating time resource, which keeps a rotating UTC timestamp stored in the Terraform state and proposes resource recreation when the locally sourced current time is beyond the rotation time. This rotation only occurs when Terraform is executed, meaning there will be drift between the rotation timestamp and actual rotation. The new rotation timestamp offset includes this drift. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html) by only forcing a new value on the set cadence. + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +This example configuration will rotate (destroy/create) the resource every 30 days. + +```hcl +resource "time_rotating" "example" { + rotation_days = 30 +} +``` + +## Argument Reference + +~> **NOTE:** At least one of the `rotation_` arguments must be configured. + +The following arguments are optional: + +* `rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. +* `rotation_days` - (Optional) Number of days to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_hours` - (Optional) Number of hours to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_minutes` - (Optional) Number of minutes to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_months` - (Optional) Number of months to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_rfc3339` - (Optional) Configure the rotation timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_years` - (Optional) Number of years to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. These conditions recreate the resource in addition to other rotation arguments. See [the main provider documentation](../index.html) for more information. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of timestamp. +* `hour` - Number hour of timestamp. +* `id` - UTC RFC3339 format of the base timestamp, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of timestamp. +* `month` - Number month of timestamp. +* `second` - Number second of timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of timestamp. + +## Import + +This resource can be imported using the base UTC RFC3339 value and rotation years, months, days, hours, and minutes, separated by commas (`,`), e.g. for 30 days + +```console +$ terraform import time_rotation.example 2020-02-12T06:36:13Z,0,0,30,0,0 +``` + +Otherwise, to import with the rotation RFC3339 value, the base UTC RFC3339 value and rotation UTC RFC3339 value, separated by commas (`,`), e.g. + +```console +$ terraform import time_rotation.example 2020-02-12T06:36:13Z,2020-02-13T06:36:13Z +``` + +The `triggers` argument cannot be imported. +-- docs/resources/sleep.html.markdown -- +--- +layout: "time" +page_title: "Time: time_sleep" +description: |- + Manages a static time resource. +--- + +# Resource: time_sleep + +Manages a resource that delays creation and/or destruction, typically for further resources. This prevents cross-platform compatibility and destroy-time issues with using the [`local-exec` provisioner](https://www.terraform.io/docs/provisioners/local-exec.html). + +-> In many cases, this resource should be considered a workaround for issues that should be reported and handled in downstream Terraform Provider logic. Downstream resources can usually introduce or adjust retries in their code to handle time delay issues for all Terraform configurations or upstream resources can be improved to better wait for a resource to be fully ready and available. + +## Example Usage + +### Delay Create Usage + +```hcl +# This resource will destroy (potentially immediately) after null_resource.next +resource "null_resource" "previous" {} + +resource "time_sleep" "wait_30_seconds" { + depends_on = [null_resource.previous] + + create_duration = "30s" +} + +# This resource will create (at least) 30 seconds after null_resource.previous +resource "null_resource" "next" { + depends_on = [time_sleep.wait_30_seconds] +} +``` + +### Delay Destroy Usage + +```hcl +# This resource will destroy (at least) 30 seconds after null_resource.next +resource "null_resource" "previous" {} + +resource "time_sleep" "wait_30_seconds" { + depends_on = [null_resource.previous] + + destroy_duration = "30s" +} + +# This resource will create (potentially immediately) after null_resource.previous +resource "null_resource" "next" { + depends_on = [time_sleep.wait_30_seconds] +} +``` + +### Triggers Usage + +```hcl +resource "aws_ram_resource_association" "example" { + resource_arn = aws_subnet.example.arn + resource_share_arn = aws_ram_resource_share.example.arn +} + +# AWS resources shared via Resource Access Manager can take a few seconds to +# propagate across AWS accounts after RAM returns a successful association. +resource "time_sleep" "ram_resource_propagation" { + create_duration = "60s" + + triggers = { + # This sets up a proper dependency on the RAM association + subnet_arn = aws_ram_resource_association.example.resource_arn + subnet_id = aws_subnet.example.id + } +} + +resource "aws_db_subnet_group" "example" { + name = "example" + + # Read the Subnet identifier "through" the time_sleep resource to ensure a + # proper dependency and that both will change together. + subnet_ids = [time_sleep.ram_resource_propagation.triggers["subnet_id"]] +} +``` + +## Argument Reference + +The following arguments are optional: + +* `create_duration` - (Optional) [Time duration][1] to delay resource creation. For example, `30s` for 30 seconds or `5m` for 5 minutes. Updating this value by itself will not trigger a delay. +* `destroy_duration` - (Optional) [Time duration][1] to delay resource destroy. For example, `30s` for 30 seconds or `5m` for 5 minutes. Updating this value by itself will not trigger a delay. This value or any updates to it must be successfully applied into the Terraform state before destroying this resource to take effect. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will run any creation or destroy delays again. See [the main provider documentation](../index.html) for more information. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - UTC RFC3339 timestamp of the creation or import, e.g. `2020-02-12T06:36:13Z`. + +## Import + +This resource can be imported with the `create_duration` and `destroy_duration`, separated by a comma (`,`). + +e.g. For 30 seconds create duration with no destroy duration: + +```console +$ terraform import time_sleep.example 30s, +``` + +e.g. For 30 seconds destroy duration with no create duration: + +```console +$ terraform import time_sleep.example ,30s +``` + +The `triggers` argument cannot be imported. + +[1]: https://golang.org/pkg/time/#ParseDuration +-- docs/resources/static.html.markdown -- +--- +layout: "time" +page_title: "Time: time_static" +description: |- + Manages a static time resource. +--- + +# Resource: time_static + +Manages a static time resource, which keeps a locally sourced UTC timestamp stored in the Terraform state. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html). + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +### Basic Usage + +```hcl +resource "time_static" "example" {} + +output "current_time" { + value = time_static.example.rfc3339 +} +``` + +### Triggers Usage + +```hcl +resource "time_static" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_static resource to ensure that + # both will change together. + ami = time_static.ami_update.triggers.ami_id + + tags = { + AmiUpdateTime = time_static.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +``` + +## Argument Reference + +The following arguments are optional: + +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. See [the main provider documentation](../index.html) for more information. +* `rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of timestamp. +* `hour` - Number hour of timestamp. +* `id` - UTC RFC3339 timestamp format, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of timestamp. +* `month` - Number month of timestamp. +* `rfc3339` - UTC RFC3339 format of timestamp, e.g. `2020-02-12T06:36:13Z`. +* `second` - Number second of timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of timestamp. + +## Import + +This resource can be imported using the UTC RFC3339 value, e.g. + +```console +$ terraform import time_static.example 2020-02-12T06:36:13Z +``` + +The `triggers` argument cannot be imported. +-- exp-examples/example_1.tf -- +resource "time_static" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_static resource to ensure that + # both will change together. + ami = time_static.ami_update.triggers.ami_id + + tags = { + AmiUpdateTime = time_static.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +-- exp-examples/resources/offset/example_1.tf -- +resource "time_offset" "example" { + offset_days = 7 +} + +output "one_week_from_now" { + value = time_offset.example.rfc3339 +} +-- exp-examples/resources/offset/example_2.tf -- +resource "time_offset" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } + + offset_days = 7 +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_offset resource to ensure that + # both will change together. + ami = time_offset.ami_update.triggers.ami_id + + tags = { + ExpirationTime = time_offset.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +-- exp-examples/resources/offset/import_1.sh -- +$ terraform import time_offset.example 2020-02-12T06:36:13Z,0,0,7,0,0,0 +-- exp-examples/resources/rotating/example_1.tf -- +resource "time_rotating" "example" { + rotation_days = 30 +} +-- exp-examples/resources/rotating/import_1.sh -- +$ terraform import time_rotation.example 2020-02-12T06:36:13Z,0,0,30,0,0 +-- exp-examples/resources/rotating/import_2.sh -- +$ terraform import time_rotation.example 2020-02-12T06:36:13Z,2020-02-13T06:36:13Z +-- exp-examples/resources/sleep/example_1.tf -- +# This resource will destroy (potentially immediately) after null_resource.next +resource "null_resource" "previous" {} + +resource "time_sleep" "wait_30_seconds" { + depends_on = [null_resource.previous] + + create_duration = "30s" +} + +# This resource will create (at least) 30 seconds after null_resource.previous +resource "null_resource" "next" { + depends_on = [time_sleep.wait_30_seconds] +} +-- exp-examples/resources/sleep/example_2.tf -- +# This resource will destroy (at least) 30 seconds after null_resource.next +resource "null_resource" "previous" {} + +resource "time_sleep" "wait_30_seconds" { + depends_on = [null_resource.previous] + + destroy_duration = "30s" +} + +# This resource will create (potentially immediately) after null_resource.previous +resource "null_resource" "next" { + depends_on = [time_sleep.wait_30_seconds] +} +-- exp-examples/resources/sleep/example_3.tf -- +resource "aws_ram_resource_association" "example" { + resource_arn = aws_subnet.example.arn + resource_share_arn = aws_ram_resource_share.example.arn +} + +# AWS resources shared via Resource Access Manager can take a few seconds to +# propagate across AWS accounts after RAM returns a successful association. +resource "time_sleep" "ram_resource_propagation" { + create_duration = "60s" + + triggers = { + # This sets up a proper dependency on the RAM association + subnet_arn = aws_ram_resource_association.example.resource_arn + subnet_id = aws_subnet.example.id + } +} + +resource "aws_db_subnet_group" "example" { + name = "example" + + # Read the Subnet identifier "through" the time_sleep resource to ensure a + # proper dependency and that both will change together. + subnet_ids = [time_sleep.ram_resource_propagation.triggers["subnet_id"]] +} +-- exp-examples/resources/sleep/import_1.sh -- +$ terraform import time_sleep.example 30s, +-- exp-examples/resources/sleep/import_2.sh -- +$ terraform import time_sleep.example ,30s +-- exp-examples/resources/static/example_1.tf -- +resource "time_static" "example" {} + +output "current_time" { + value = time_static.example.rfc3339 +} +-- exp-examples/resources/static/example_2.tf -- +resource "time_static" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_static resource to ensure that + # both will change together. + ami = time_static.ami_update.triggers.ami_id + + tags = { + AmiUpdateTime = time_static.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +-- exp-examples/resources/static/import_1.sh -- +$ terraform import time_static.example 2020-02-12T06:36:13Z +-- exp-templates/index.md.tmpl -- +--- +page_title: "Provider: Time" +description: |- + The time provider is used to interact with time-based resources. +--- + +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider source code. */ -}} + +# Time Provider + +The time provider is used to interact with time-based resources. The provider itself has no configuration options. + +Use the navigation to the left to read about the available resources. + +## Resource "Triggers" + +Certain time resources, only perform actions during specific lifecycle actions: + +- `time_offset`: Saves base timestamp into Terraform state only when created. +- `time_sleep`: Sleeps when created and/or destroyed. +- `time_static`: Saves base timestamp into Terraform state only when created. + +These resources provide an optional map argument called `triggers` that can be populated with arbitrary key/value pairs. When the keys or values of this argument are updated, Terraform will re-perform the desired action, such as updating the base timestamp or sleeping again. + +For example: + +{{tffile "$WORK/examples/example_1.tf"}} + +`triggers` are *not* treated as sensitive attributes; a value used for `triggers` will be displayed in Terraform UI output as plaintext. + +To force a these actions to reoccur without updating `triggers`, the [`terraform taint` command](https://www.terraform.io/docs/commands/taint.html) can be used to produce the action on the next run. +-- exp-templates/resources/offset.md.tmpl -- +--- +page_title: "Time: time_offset" +description: |- + Manages a offset time resource. +--- + +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider source code. */ -}} + +# Resource: time_offset + +Manages an offset time resource, which keeps an UTC timestamp stored in the Terraform state that is offset from a locally sourced base timestamp. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html). + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +### Basic Usage + +{{tffile "$WORK/examples/resources/offset/example_1.tf"}} + +### Triggers Usage + +{{tffile "$WORK/examples/resources/offset/example_2.tf"}} + +## Argument Reference + +~> **NOTE:** At least one of the `offset_` arguments must be configured. + +The following arguments are optional: + +* `base_rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. See [the main provider documentation](../index.html) for more information. +* `offset_days` - (Optional) Number of days to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_hours` - (Optional) Number of hours to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_minutes` - (Optional) Number of minutes to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_months` - (Optional) Number of months to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_seconds` - (Optional) Number of seconds to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_years` - (Optional) Number of years to offset the base timestamp. Conflicts with other `offset_` arguments. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of offset timestamp. +* `hour` - Number hour of offset timestamp. +* `id` - UTC RFC3339 format of the base timestamp, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of offset timestamp. +* `month` - Number month of offset timestamp. +* `rfc3339` - UTC RFC3339 format of the offset timestamp, e.g. `2020-02-12T06:36:13Z`. +* `second` - Number second of offset timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of offset timestamp. + +## Import + +This resource can be imported using the base UTC RFC3339 timestamp and offset years, months, days, hours, minutes, and seconds, separated by commas (`,`), e.g. + +{{codefile "shell" "$WORK/examples/resources/offset/import_1.sh"}} + +The `triggers` argument cannot be imported. +-- exp-templates/resources/rotating.md.tmpl -- +--- +page_title: "Time: time_rotating" +description: |- + Manages a rotating time resource. +--- + +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider source code. */ -}} + +# Resource: time_rotating + +Manages a rotating time resource, which keeps a rotating UTC timestamp stored in the Terraform state and proposes resource recreation when the locally sourced current time is beyond the rotation time. This rotation only occurs when Terraform is executed, meaning there will be drift between the rotation timestamp and actual rotation. The new rotation timestamp offset includes this drift. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html) by only forcing a new value on the set cadence. + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +This example configuration will rotate (destroy/create) the resource every 30 days. + +{{tffile "$WORK/examples/resources/rotating/example_1.tf"}} + +## Argument Reference + +~> **NOTE:** At least one of the `rotation_` arguments must be configured. + +The following arguments are optional: + +* `rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. +* `rotation_days` - (Optional) Number of days to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_hours` - (Optional) Number of hours to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_minutes` - (Optional) Number of minutes to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_months` - (Optional) Number of months to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_rfc3339` - (Optional) Configure the rotation timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_years` - (Optional) Number of years to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. These conditions recreate the resource in addition to other rotation arguments. See [the main provider documentation](../index.html) for more information. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of timestamp. +* `hour` - Number hour of timestamp. +* `id` - UTC RFC3339 format of the base timestamp, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of timestamp. +* `month` - Number month of timestamp. +* `second` - Number second of timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of timestamp. + +## Import + +This resource can be imported using the base UTC RFC3339 value and rotation years, months, days, hours, and minutes, separated by commas (`,`), e.g. for 30 days + +{{codefile "shell" "$WORK/examples/resources/rotating/import_1.sh"}} + +Otherwise, to import with the rotation RFC3339 value, the base UTC RFC3339 value and rotation UTC RFC3339 value, separated by commas (`,`), e.g. + +{{codefile "shell" "$WORK/examples/resources/rotating/import_2.sh"}} + +The `triggers` argument cannot be imported. +-- exp-templates/resources/sleep.md.tmpl -- +--- +page_title: "Time: time_sleep" +description: |- + Manages a static time resource. +--- + +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider source code. */ -}} + +# Resource: time_sleep + +Manages a resource that delays creation and/or destruction, typically for further resources. This prevents cross-platform compatibility and destroy-time issues with using the [`local-exec` provisioner](https://www.terraform.io/docs/provisioners/local-exec.html). + +-> In many cases, this resource should be considered a workaround for issues that should be reported and handled in downstream Terraform Provider logic. Downstream resources can usually introduce or adjust retries in their code to handle time delay issues for all Terraform configurations or upstream resources can be improved to better wait for a resource to be fully ready and available. + +## Example Usage + +### Delay Create Usage + +{{tffile "$WORK/examples/resources/sleep/example_1.tf"}} + +### Delay Destroy Usage + +{{tffile "$WORK/examples/resources/sleep/example_2.tf"}} + +### Triggers Usage + +{{tffile "$WORK/examples/resources/sleep/example_3.tf"}} + +## Argument Reference + +The following arguments are optional: + +* `create_duration` - (Optional) [Time duration](https://golang.org/pkg/time/#ParseDuration) to delay resource creation. For example, `30s` for 30 seconds or `5m` for 5 minutes. Updating this value by itself will not trigger a delay. +* `destroy_duration` - (Optional) [Time duration](https://golang.org/pkg/time/#ParseDuration) to delay resource destroy. For example, `30s` for 30 seconds or `5m` for 5 minutes. Updating this value by itself will not trigger a delay. This value or any updates to it must be successfully applied into the Terraform state before destroying this resource to take effect. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will run any creation or destroy delays again. See [the main provider documentation](../index.html) for more information. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - UTC RFC3339 timestamp of the creation or import, e.g. `2020-02-12T06:36:13Z`. + +## Import + +This resource can be imported with the `create_duration` and `destroy_duration`, separated by a comma (`,`). + +e.g. For 30 seconds create duration with no destroy duration: + +{{codefile "shell" "$WORK/examples/resources/sleep/import_1.sh"}} + +e.g. For 30 seconds destroy duration with no create duration: + +{{codefile "shell" "$WORK/examples/resources/sleep/import_2.sh"}} + +The `triggers` argument cannot be imported. +-- exp-templates/resources/static.md.tmpl -- +--- +page_title: "Time: time_static" +description: |- + Manages a static time resource. +--- + +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider source code. */ -}} + +# Resource: time_static + +Manages a static time resource, which keeps a locally sourced UTC timestamp stored in the Terraform state. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html). + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +### Basic Usage + +{{tffile "$WORK/examples/resources/static/example_1.tf"}} + +### Triggers Usage + +{{tffile "$WORK/examples/resources/static/example_2.tf"}} + +## Argument Reference + +The following arguments are optional: + +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. See [the main provider documentation](../index.html) for more information. +* `rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of timestamp. +* `hour` - Number hour of timestamp. +* `id` - UTC RFC3339 timestamp format, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of timestamp. +* `month` - Number month of timestamp. +* `rfc3339` - UTC RFC3339 format of timestamp, e.g. `2020-02-12T06:36:13Z`. +* `second` - Number second of timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of timestamp. + +## Import + +This resource can be imported using the UTC RFC3339 value, e.g. + +{{codefile "shell" "$WORK/examples/resources/static/import_1.sh"}} + +The `triggers` argument cannot be imported. \ No newline at end of file diff --git a/cmd/tfplugindocs/testdata/scripts/schema-json/migrate/time_provider_success_legacy_website.txtar b/cmd/tfplugindocs/testdata/scripts/schema-json/migrate/time_provider_success_legacy_website.txtar new file mode 100644 index 00000000..ec1af20e --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/schema-json/migrate/time_provider_success_legacy_website.txtar @@ -0,0 +1,896 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs -migrate on the Time provider using the legacy website layout +[!unix] skip + +# Run migrate command +exec tfplugindocs migrate +cmpenv stdout expected-output.txt + +# Check template files +cmpenv templates/index.md.tmpl exp-templates/index.md.tmpl +cmpenv templates/resources/offset.md.tmpl exp-templates/resources/offset.md.tmpl +cmpenv templates/resources/rotating.md.tmpl exp-templates/resources/rotating.md.tmpl +cmpenv templates/resources/sleep.md.tmpl exp-templates/resources/sleep.md.tmpl +cmpenv templates/resources/static.md.tmpl exp-templates/resources/static.md.tmpl + +# Check generated example files +cmpenv examples/example_1.tf examples/example_1.tf + +cmpenv examples/resources/offset/example_1.tf exp-examples/resources/offset/example_1.tf +cmpenv examples/resources/offset/example_2.tf exp-examples/resources/offset/example_2.tf +cmpenv examples/resources/offset/import_1.sh exp-examples/resources/offset/import_1.sh + +cmpenv examples/resources/rotating/example_1.tf exp-examples/resources/rotating/example_1.tf +cmpenv examples/resources/rotating/import_1.sh exp-examples/resources/rotating/import_1.sh +cmpenv examples/resources/rotating/import_2.sh exp-examples/resources/rotating/import_2.sh + +cmpenv examples/resources/sleep/example_1.tf exp-examples/resources/sleep/example_1.tf +cmpenv examples/resources/sleep/example_2.tf exp-examples/resources/sleep/example_2.tf +cmpenv examples/resources/sleep/example_3.tf exp-examples/resources/sleep/example_3.tf +cmpenv examples/resources/sleep/import_1.sh exp-examples/resources/sleep/import_1.sh +cmpenv examples/resources/sleep/import_2.sh exp-examples/resources/sleep/import_2.sh + +cmpenv examples/resources/static/example_1.tf examples/resources/static/example_1.tf +cmpenv examples/resources/static/example_2.tf examples/resources/static/example_2.tf +cmpenv examples/resources/static/import_1.sh examples/resources/static/import_1.sh + +# Verify legacy website directory is removed +! exists website/ + +-- expected-output.txt -- +migrating website from "$WORK/website/docs" to "$WORK/templates" +migrating provider index: index.html.markdown +migrating file "index.html.markdown" +extracting YAML frontmatter to "$WORK/templates/index.md.tmpl" +extracting code examples from "index.html.markdown" +creating example file "$WORK/examples/example_1.tf" +finished creating template "$WORK/templates/index.md.tmpl" +migrating resources directory: r +migrating file "offset.html.markdown" +extracting YAML frontmatter to "$WORK/templates/resources/offset.md.tmpl" +extracting code examples from "offset.html.markdown" +creating example file "$WORK/examples/resources/offset/example_1.tf" +creating example file "$WORK/examples/resources/offset/example_2.tf" +creating import file "$WORK/examples/resources/offset/import_1.sh" +finished creating template "$WORK/templates/resources/offset.md.tmpl" +migrating file "rotating.html.markdown" +extracting YAML frontmatter to "$WORK/templates/resources/rotating.md.tmpl" +extracting code examples from "rotating.html.markdown" +creating example file "$WORK/examples/resources/rotating/example_1.tf" +creating import file "$WORK/examples/resources/rotating/import_1.sh" +creating import file "$WORK/examples/resources/rotating/import_2.sh" +finished creating template "$WORK/templates/resources/rotating.md.tmpl" +migrating file "sleep.html.markdown" +extracting YAML frontmatter to "$WORK/templates/resources/sleep.md.tmpl" +extracting code examples from "sleep.html.markdown" +creating example file "$WORK/examples/resources/sleep/example_1.tf" +creating example file "$WORK/examples/resources/sleep/example_2.tf" +creating example file "$WORK/examples/resources/sleep/example_3.tf" +creating import file "$WORK/examples/resources/sleep/import_1.sh" +creating import file "$WORK/examples/resources/sleep/import_2.sh" +finished creating template "$WORK/templates/resources/sleep.md.tmpl" +migrating file "static.html.markdown" +extracting YAML frontmatter to "$WORK/templates/resources/static.md.tmpl" +extracting code examples from "static.html.markdown" +creating example file "$WORK/examples/resources/static/example_1.tf" +creating example file "$WORK/examples/resources/static/example_2.tf" +creating import file "$WORK/examples/resources/static/import_1.sh" +finished creating template "$WORK/templates/resources/static.md.tmpl" +-- website/docs/index.html.markdown -- +--- +layout: "time" +page_title: "Provider: Time" +description: |- + The time provider is used to interact with time-based resources. +--- + +# Time Provider + +The time provider is used to interact with time-based resources. The provider itself has no configuration options. + +Use the navigation to the left to read about the available resources. + +## Resource "Triggers" + +Certain time resources, only perform actions during specific lifecycle actions: + +- `time_offset`: Saves base timestamp into Terraform state only when created. +- `time_sleep`: Sleeps when created and/or destroyed. +- `time_static`: Saves base timestamp into Terraform state only when created. + +These resources provide an optional map argument called `triggers` that can be populated with arbitrary key/value pairs. When the keys or values of this argument are updated, Terraform will re-perform the desired action, such as updating the base timestamp or sleeping again. + +For example: + +```hcl +resource "time_static" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_static resource to ensure that + # both will change together. + ami = time_static.ami_update.triggers.ami_id + + tags = { + AmiUpdateTime = time_static.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +``` + +`triggers` are *not* treated as sensitive attributes; a value used for `triggers` will be displayed in Terraform UI output as plaintext. + +To force a these actions to reoccur without updating `triggers`, the [`terraform taint` command](https://www.terraform.io/docs/commands/taint.html) can be used to produce the action on the next run. +-- website/docs/r/offset.html.markdown -- +--- +layout: "time" +page_title: "Time: time_offset" +description: |- + Manages a offset time resource. +--- + +# Resource: time_offset + +Manages an offset time resource, which keeps an UTC timestamp stored in the Terraform state that is offset from a locally sourced base timestamp. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html). + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +### Basic Usage + +```hcl +resource "time_offset" "example" { + offset_days = 7 +} + +output "one_week_from_now" { + value = time_offset.example.rfc3339 +} +``` + +### Triggers Usage + +```hcl +resource "time_offset" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } + + offset_days = 7 +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_offset resource to ensure that + # both will change together. + ami = time_offset.ami_update.triggers.ami_id + + tags = { + ExpirationTime = time_offset.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +``` + +## Argument Reference + +~> **NOTE:** At least one of the `offset_` arguments must be configured. + +The following arguments are optional: + +* `base_rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. See [the main provider documentation](../index.html) for more information. +* `offset_days` - (Optional) Number of days to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_hours` - (Optional) Number of hours to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_minutes` - (Optional) Number of minutes to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_months` - (Optional) Number of months to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_seconds` - (Optional) Number of seconds to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_years` - (Optional) Number of years to offset the base timestamp. Conflicts with other `offset_` arguments. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of offset timestamp. +* `hour` - Number hour of offset timestamp. +* `id` - UTC RFC3339 format of the base timestamp, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of offset timestamp. +* `month` - Number month of offset timestamp. +* `rfc3339` - UTC RFC3339 format of the offset timestamp, e.g. `2020-02-12T06:36:13Z`. +* `second` - Number second of offset timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of offset timestamp. + +## Import + +This resource can be imported using the base UTC RFC3339 timestamp and offset years, months, days, hours, minutes, and seconds, separated by commas (`,`), e.g. + +```console +$ terraform import time_offset.example 2020-02-12T06:36:13Z,0,0,7,0,0,0 +``` + +The `triggers` argument cannot be imported. +-- website/docs/r/rotating.html.markdown -- +--- +layout: "time" +page_title: "Time: time_rotating" +description: |- + Manages a rotating time resource. +--- + +# Resource: time_rotating + +Manages a rotating time resource, which keeps a rotating UTC timestamp stored in the Terraform state and proposes resource recreation when the locally sourced current time is beyond the rotation time. This rotation only occurs when Terraform is executed, meaning there will be drift between the rotation timestamp and actual rotation. The new rotation timestamp offset includes this drift. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html) by only forcing a new value on the set cadence. + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +This example configuration will rotate (destroy/create) the resource every 30 days. + +```hcl +resource "time_rotating" "example" { + rotation_days = 30 +} +``` + +## Argument Reference + +~> **NOTE:** At least one of the `rotation_` arguments must be configured. + +The following arguments are optional: + +* `rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. +* `rotation_days` - (Optional) Number of days to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_hours` - (Optional) Number of hours to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_minutes` - (Optional) Number of minutes to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_months` - (Optional) Number of months to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_rfc3339` - (Optional) Configure the rotation timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_years` - (Optional) Number of years to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. These conditions recreate the resource in addition to other rotation arguments. See [the main provider documentation](../index.html) for more information. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of timestamp. +* `hour` - Number hour of timestamp. +* `id` - UTC RFC3339 format of the base timestamp, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of timestamp. +* `month` - Number month of timestamp. +* `second` - Number second of timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of timestamp. + +## Import + +This resource can be imported using the base UTC RFC3339 value and rotation years, months, days, hours, and minutes, separated by commas (`,`), e.g. for 30 days + +```console +$ terraform import time_rotation.example 2020-02-12T06:36:13Z,0,0,30,0,0 +``` + +Otherwise, to import with the rotation RFC3339 value, the base UTC RFC3339 value and rotation UTC RFC3339 value, separated by commas (`,`), e.g. + +```console +$ terraform import time_rotation.example 2020-02-12T06:36:13Z,2020-02-13T06:36:13Z +``` + +The `triggers` argument cannot be imported. +-- website/docs/r/sleep.html.markdown -- +--- +layout: "time" +page_title: "Time: time_sleep" +description: |- + Manages a static time resource. +--- + +# Resource: time_sleep + +Manages a resource that delays creation and/or destruction, typically for further resources. This prevents cross-platform compatibility and destroy-time issues with using the [`local-exec` provisioner](https://www.terraform.io/docs/provisioners/local-exec.html). + +-> In many cases, this resource should be considered a workaround for issues that should be reported and handled in downstream Terraform Provider logic. Downstream resources can usually introduce or adjust retries in their code to handle time delay issues for all Terraform configurations or upstream resources can be improved to better wait for a resource to be fully ready and available. + +## Example Usage + +### Delay Create Usage + +```hcl +# This resource will destroy (potentially immediately) after null_resource.next +resource "null_resource" "previous" {} + +resource "time_sleep" "wait_30_seconds" { + depends_on = [null_resource.previous] + + create_duration = "30s" +} + +# This resource will create (at least) 30 seconds after null_resource.previous +resource "null_resource" "next" { + depends_on = [time_sleep.wait_30_seconds] +} +``` + +### Delay Destroy Usage + +```hcl +# This resource will destroy (at least) 30 seconds after null_resource.next +resource "null_resource" "previous" {} + +resource "time_sleep" "wait_30_seconds" { + depends_on = [null_resource.previous] + + destroy_duration = "30s" +} + +# This resource will create (potentially immediately) after null_resource.previous +resource "null_resource" "next" { + depends_on = [time_sleep.wait_30_seconds] +} +``` + +### Triggers Usage + +```hcl +resource "aws_ram_resource_association" "example" { + resource_arn = aws_subnet.example.arn + resource_share_arn = aws_ram_resource_share.example.arn +} + +# AWS resources shared via Resource Access Manager can take a few seconds to +# propagate across AWS accounts after RAM returns a successful association. +resource "time_sleep" "ram_resource_propagation" { + create_duration = "60s" + + triggers = { + # This sets up a proper dependency on the RAM association + subnet_arn = aws_ram_resource_association.example.resource_arn + subnet_id = aws_subnet.example.id + } +} + +resource "aws_db_subnet_group" "example" { + name = "example" + + # Read the Subnet identifier "through" the time_sleep resource to ensure a + # proper dependency and that both will change together. + subnet_ids = [time_sleep.ram_resource_propagation.triggers["subnet_id"]] +} +``` + +## Argument Reference + +The following arguments are optional: + +* `create_duration` - (Optional) [Time duration][1] to delay resource creation. For example, `30s` for 30 seconds or `5m` for 5 minutes. Updating this value by itself will not trigger a delay. +* `destroy_duration` - (Optional) [Time duration][1] to delay resource destroy. For example, `30s` for 30 seconds or `5m` for 5 minutes. Updating this value by itself will not trigger a delay. This value or any updates to it must be successfully applied into the Terraform state before destroying this resource to take effect. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will run any creation or destroy delays again. See [the main provider documentation](../index.html) for more information. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - UTC RFC3339 timestamp of the creation or import, e.g. `2020-02-12T06:36:13Z`. + +## Import + +This resource can be imported with the `create_duration` and `destroy_duration`, separated by a comma (`,`). + +e.g. For 30 seconds create duration with no destroy duration: + +```console +$ terraform import time_sleep.example 30s, +``` + +e.g. For 30 seconds destroy duration with no create duration: + +```console +$ terraform import time_sleep.example ,30s +``` + +The `triggers` argument cannot be imported. + +[1]: https://golang.org/pkg/time/#ParseDuration +-- website/docs/r/static.html.markdown -- +--- +layout: "time" +page_title: "Time: time_static" +description: |- + Manages a static time resource. +--- + +# Resource: time_static + +Manages a static time resource, which keeps a locally sourced UTC timestamp stored in the Terraform state. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html). + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +### Basic Usage + +```hcl +resource "time_static" "example" {} + +output "current_time" { + value = time_static.example.rfc3339 +} +``` + +### Triggers Usage + +```hcl +resource "time_static" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_static resource to ensure that + # both will change together. + ami = time_static.ami_update.triggers.ami_id + + tags = { + AmiUpdateTime = time_static.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +``` + +## Argument Reference + +The following arguments are optional: + +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. See [the main provider documentation](../index.html) for more information. +* `rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of timestamp. +* `hour` - Number hour of timestamp. +* `id` - UTC RFC3339 timestamp format, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of timestamp. +* `month` - Number month of timestamp. +* `rfc3339` - UTC RFC3339 format of timestamp, e.g. `2020-02-12T06:36:13Z`. +* `second` - Number second of timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of timestamp. + +## Import + +This resource can be imported using the UTC RFC3339 value, e.g. + +```console +$ terraform import time_static.example 2020-02-12T06:36:13Z +``` + +The `triggers` argument cannot be imported. + +-- exp-examples/example_1.tf -- +resource "time_static" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_static resource to ensure that + # both will change together. + ami = time_static.ami_update.triggers.ami_id + + tags = { + AmiUpdateTime = time_static.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +-- exp-examples/resources/offset/example_1.tf -- +resource "time_offset" "example" { + offset_days = 7 +} + +output "one_week_from_now" { + value = time_offset.example.rfc3339 +} +-- exp-examples/resources/offset/example_2.tf -- +resource "time_offset" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } + + offset_days = 7 +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_offset resource to ensure that + # both will change together. + ami = time_offset.ami_update.triggers.ami_id + + tags = { + ExpirationTime = time_offset.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +-- exp-examples/resources/offset/import_1.sh -- +$ terraform import time_offset.example 2020-02-12T06:36:13Z,0,0,7,0,0,0 +-- exp-examples/resources/rotating/example_1.tf -- +resource "time_rotating" "example" { + rotation_days = 30 +} +-- exp-examples/resources/rotating/import_1.sh -- +$ terraform import time_rotation.example 2020-02-12T06:36:13Z,0,0,30,0,0 +-- exp-examples/resources/rotating/import_2.sh -- +$ terraform import time_rotation.example 2020-02-12T06:36:13Z,2020-02-13T06:36:13Z +-- exp-examples/resources/sleep/example_1.tf -- +# This resource will destroy (potentially immediately) after null_resource.next +resource "null_resource" "previous" {} + +resource "time_sleep" "wait_30_seconds" { + depends_on = [null_resource.previous] + + create_duration = "30s" +} + +# This resource will create (at least) 30 seconds after null_resource.previous +resource "null_resource" "next" { + depends_on = [time_sleep.wait_30_seconds] +} +-- exp-examples/resources/sleep/example_2.tf -- +# This resource will destroy (at least) 30 seconds after null_resource.next +resource "null_resource" "previous" {} + +resource "time_sleep" "wait_30_seconds" { + depends_on = [null_resource.previous] + + destroy_duration = "30s" +} + +# This resource will create (potentially immediately) after null_resource.previous +resource "null_resource" "next" { + depends_on = [time_sleep.wait_30_seconds] +} +-- exp-examples/resources/sleep/example_3.tf -- +resource "aws_ram_resource_association" "example" { + resource_arn = aws_subnet.example.arn + resource_share_arn = aws_ram_resource_share.example.arn +} + +# AWS resources shared via Resource Access Manager can take a few seconds to +# propagate across AWS accounts after RAM returns a successful association. +resource "time_sleep" "ram_resource_propagation" { + create_duration = "60s" + + triggers = { + # This sets up a proper dependency on the RAM association + subnet_arn = aws_ram_resource_association.example.resource_arn + subnet_id = aws_subnet.example.id + } +} + +resource "aws_db_subnet_group" "example" { + name = "example" + + # Read the Subnet identifier "through" the time_sleep resource to ensure a + # proper dependency and that both will change together. + subnet_ids = [time_sleep.ram_resource_propagation.triggers["subnet_id"]] +} +-- exp-examples/resources/sleep/import_1.sh -- +$ terraform import time_sleep.example 30s, +-- exp-examples/resources/sleep/import_2.sh -- +$ terraform import time_sleep.example ,30s +-- exp-examples/resources/static/example_1.tf -- +resource "time_static" "example" {} + +output "current_time" { + value = time_static.example.rfc3339 +} +-- exp-examples/resources/static/example_2.tf -- +resource "time_static" "ami_update" { + triggers = { + # Save the time each switch of an AMI id + ami_id = data.aws_ami.example.id + } +} + +resource "aws_instance" "server" { + # Read the AMI id "through" the time_static resource to ensure that + # both will change together. + ami = time_static.ami_update.triggers.ami_id + + tags = { + AmiUpdateTime = time_static.ami_update.rfc3339 + } + + # ... (other aws_instance arguments) ... +} +-- exp-examples/resources/static/import_1.sh -- +$ terraform import time_static.example 2020-02-12T06:36:13Z +-- exp-templates/index.md.tmpl -- +--- +page_title: "Provider: Time" +description: |- + The time provider is used to interact with time-based resources. +--- + +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider source code. */ -}} + +# Time Provider + +The time provider is used to interact with time-based resources. The provider itself has no configuration options. + +Use the navigation to the left to read about the available resources. + +## Resource "Triggers" + +Certain time resources, only perform actions during specific lifecycle actions: + +- `time_offset`: Saves base timestamp into Terraform state only when created. +- `time_sleep`: Sleeps when created and/or destroyed. +- `time_static`: Saves base timestamp into Terraform state only when created. + +These resources provide an optional map argument called `triggers` that can be populated with arbitrary key/value pairs. When the keys or values of this argument are updated, Terraform will re-perform the desired action, such as updating the base timestamp or sleeping again. + +For example: + +{{tffile "$WORK/examples/example_1.tf"}} + +`triggers` are *not* treated as sensitive attributes; a value used for `triggers` will be displayed in Terraform UI output as plaintext. + +To force a these actions to reoccur without updating `triggers`, the [`terraform taint` command](https://www.terraform.io/docs/commands/taint.html) can be used to produce the action on the next run. +-- exp-templates/resources/offset.md.tmpl -- +--- +page_title: "Time: time_offset" +description: |- + Manages a offset time resource. +--- + +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider source code. */ -}} + +# Resource: time_offset + +Manages an offset time resource, which keeps an UTC timestamp stored in the Terraform state that is offset from a locally sourced base timestamp. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html). + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +### Basic Usage + +{{tffile "$WORK/examples/resources/offset/example_1.tf"}} + +### Triggers Usage + +{{tffile "$WORK/examples/resources/offset/example_2.tf"}} + +## Argument Reference + +~> **NOTE:** At least one of the `offset_` arguments must be configured. + +The following arguments are optional: + +* `base_rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. See [the main provider documentation](../index.html) for more information. +* `offset_days` - (Optional) Number of days to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_hours` - (Optional) Number of hours to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_minutes` - (Optional) Number of minutes to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_months` - (Optional) Number of months to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_seconds` - (Optional) Number of seconds to offset the base timestamp. Conflicts with other `offset_` arguments. +* `offset_years` - (Optional) Number of years to offset the base timestamp. Conflicts with other `offset_` arguments. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of offset timestamp. +* `hour` - Number hour of offset timestamp. +* `id` - UTC RFC3339 format of the base timestamp, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of offset timestamp. +* `month` - Number month of offset timestamp. +* `rfc3339` - UTC RFC3339 format of the offset timestamp, e.g. `2020-02-12T06:36:13Z`. +* `second` - Number second of offset timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of offset timestamp. + +## Import + +This resource can be imported using the base UTC RFC3339 timestamp and offset years, months, days, hours, minutes, and seconds, separated by commas (`,`), e.g. + +{{codefile "shell" "$WORK/examples/resources/offset/import_1.sh"}} + +The `triggers` argument cannot be imported. +-- exp-templates/resources/rotating.md.tmpl -- +--- +page_title: "Time: time_rotating" +description: |- + Manages a rotating time resource. +--- + +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider source code. */ -}} + +# Resource: time_rotating + +Manages a rotating time resource, which keeps a rotating UTC timestamp stored in the Terraform state and proposes resource recreation when the locally sourced current time is beyond the rotation time. This rotation only occurs when Terraform is executed, meaning there will be drift between the rotation timestamp and actual rotation. The new rotation timestamp offset includes this drift. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html) by only forcing a new value on the set cadence. + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +This example configuration will rotate (destroy/create) the resource every 30 days. + +{{tffile "$WORK/examples/resources/rotating/example_1.tf"}} + +## Argument Reference + +~> **NOTE:** At least one of the `rotation_` arguments must be configured. + +The following arguments are optional: + +* `rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. +* `rotation_days` - (Optional) Number of days to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_hours` - (Optional) Number of hours to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_minutes` - (Optional) Number of minutes to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_months` - (Optional) Number of months to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_rfc3339` - (Optional) Configure the rotation timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `rotation_years` - (Optional) Number of years to add to the base timestamp to configure the rotation timestamp. When the current time has passed the rotation timestamp, the resource will trigger recreation. Conflicts with other `rotation_` arguments. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. These conditions recreate the resource in addition to other rotation arguments. See [the main provider documentation](../index.html) for more information. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of timestamp. +* `hour` - Number hour of timestamp. +* `id` - UTC RFC3339 format of the base timestamp, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of timestamp. +* `month` - Number month of timestamp. +* `second` - Number second of timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of timestamp. + +## Import + +This resource can be imported using the base UTC RFC3339 value and rotation years, months, days, hours, and minutes, separated by commas (`,`), e.g. for 30 days + +{{codefile "shell" "$WORK/examples/resources/rotating/import_1.sh"}} + +Otherwise, to import with the rotation RFC3339 value, the base UTC RFC3339 value and rotation UTC RFC3339 value, separated by commas (`,`), e.g. + +{{codefile "shell" "$WORK/examples/resources/rotating/import_2.sh"}} + +The `triggers` argument cannot be imported. +-- exp-templates/resources/sleep.md.tmpl -- +--- +page_title: "Time: time_sleep" +description: |- + Manages a static time resource. +--- + +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider source code. */ -}} + +# Resource: time_sleep + +Manages a resource that delays creation and/or destruction, typically for further resources. This prevents cross-platform compatibility and destroy-time issues with using the [`local-exec` provisioner](https://www.terraform.io/docs/provisioners/local-exec.html). + +-> In many cases, this resource should be considered a workaround for issues that should be reported and handled in downstream Terraform Provider logic. Downstream resources can usually introduce or adjust retries in their code to handle time delay issues for all Terraform configurations or upstream resources can be improved to better wait for a resource to be fully ready and available. + +## Example Usage + +### Delay Create Usage + +{{tffile "$WORK/examples/resources/sleep/example_1.tf"}} + +### Delay Destroy Usage + +{{tffile "$WORK/examples/resources/sleep/example_2.tf"}} + +### Triggers Usage + +{{tffile "$WORK/examples/resources/sleep/example_3.tf"}} + +## Argument Reference + +The following arguments are optional: + +* `create_duration` - (Optional) [Time duration](https://golang.org/pkg/time/#ParseDuration) to delay resource creation. For example, `30s` for 30 seconds or `5m` for 5 minutes. Updating this value by itself will not trigger a delay. +* `destroy_duration` - (Optional) [Time duration](https://golang.org/pkg/time/#ParseDuration) to delay resource destroy. For example, `30s` for 30 seconds or `5m` for 5 minutes. Updating this value by itself will not trigger a delay. This value or any updates to it must be successfully applied into the Terraform state before destroying this resource to take effect. +* `triggers` - (Optional) Arbitrary map of values that, when changed, will run any creation or destroy delays again. See [the main provider documentation](../index.html) for more information. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - UTC RFC3339 timestamp of the creation or import, e.g. `2020-02-12T06:36:13Z`. + +## Import + +This resource can be imported with the `create_duration` and `destroy_duration`, separated by a comma (`,`). + +e.g. For 30 seconds create duration with no destroy duration: + +{{codefile "shell" "$WORK/examples/resources/sleep/import_1.sh"}} + +e.g. For 30 seconds destroy duration with no create duration: + +{{codefile "shell" "$WORK/examples/resources/sleep/import_2.sh"}} + +The `triggers` argument cannot be imported. +-- exp-templates/resources/static.md.tmpl -- +--- +page_title: "Time: time_static" +description: |- + Manages a static time resource. +--- + +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider source code. */ -}} + +# Resource: time_static + +Manages a static time resource, which keeps a locally sourced UTC timestamp stored in the Terraform state. This prevents perpetual differences caused by using the [`timestamp()` function](https://www.terraform.io/docs/configuration/functions/timestamp.html). + +-> Further manipulation of incoming or outgoing values can be accomplished with the [`formatdate()` function](https://www.terraform.io/docs/configuration/functions/formatdate.html) and the [`timeadd()` function](https://www.terraform.io/docs/configuration/functions/timeadd.html). + +## Example Usage + +### Basic Usage + +{{tffile "$WORK/examples/resources/static/example_1.tf"}} + +### Triggers Usage + +{{tffile "$WORK/examples/resources/static/example_2.tf"}} + +## Argument Reference + +The following arguments are optional: + +* `triggers` - (Optional) Arbitrary map of values that, when changed, will trigger a new base timestamp value to be saved. See [the main provider documentation](../index.html) for more information. +* `rfc3339` - (Optional) Configure the base timestamp with an UTC [RFC3339 time string](https://tools.ietf.org/html/rfc3339#section-5.8) (`YYYY-MM-DDTHH:MM:SSZ`). Defaults to the current time. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `day` - Number day of timestamp. +* `hour` - Number hour of timestamp. +* `id` - UTC RFC3339 timestamp format, e.g. `2020-02-12T06:36:13Z`. +* `minute` - Number minute of timestamp. +* `month` - Number month of timestamp. +* `rfc3339` - UTC RFC3339 format of timestamp, e.g. `2020-02-12T06:36:13Z`. +* `second` - Number second of timestamp. +* `unix` - Number of seconds since epoch time, e.g. `1581489373`. +* `year` - Number year of timestamp. + +## Import + +This resource can be imported using the UTC RFC3339 value, e.g. + +{{codefile "shell" "$WORK/examples/resources/static/import_1.sh"}} + +The `triggers` argument cannot be imported. \ No newline at end of file diff --git a/go.mod b/go.mod index 0ac6e670..da29dcd1 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/hashicorp/terraform-plugin-docs go 1.19 require ( + github.com/Kunde21/markdownfmt/v3 v3.1.0 github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hc-install v0.6.2 @@ -12,6 +13,8 @@ require ( github.com/mitchellh/cli v1.1.5 github.com/rogpeppe/go-internal v1.11.0 github.com/russross/blackfriday v1.6.0 + github.com/yuin/goldmark v1.6.0 + github.com/yuin/goldmark-meta v1.1.0 github.com/zclconf/go-cty v1.14.1 golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df golang.org/x/text v0.14.0 @@ -36,6 +39,7 @@ require ( github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/posener/complete v1.2.3 // indirect @@ -45,4 +49,5 @@ require ( golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/tools v0.13.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/go.sum b/go.sum index f2081990..90fdf3b7 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +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.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= @@ -80,6 +82,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -110,10 +114,14 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ 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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= +github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +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.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA= github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -177,9 +185,11 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/cmd/migrate.go b/internal/cmd/migrate.go new file mode 100644 index 00000000..8248b9ec --- /dev/null +++ b/internal/cmd/migrate.go @@ -0,0 +1,96 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cmd + +import ( + "flag" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-docs/internal/provider" +) + +type migrateCmd struct { + commonCmd + + flagProviderDir string + flagTemplatesDir string + flagExamplesDir string +} + +func (cmd *migrateCmd) Synopsis() string { + return "migrates website files from either the legacy rendered website directory (`website/docs/r`) or the docs rendered website directory (`docs/resources`) to the tfplugindocs supported structure (`templates/`)." +} + +func (cmd *migrateCmd) Help() string { + strBuilder := &strings.Builder{} + + longestName := 0 + longestUsage := 0 + cmd.Flags().VisitAll(func(f *flag.Flag) { + if len(f.Name) > longestName { + longestName = len(f.Name) + } + if len(f.Usage) > longestUsage { + longestUsage = len(f.Usage) + } + }) + + strBuilder.WriteString("\nUsage: tfplugindocs migrate []\n\n") + cmd.Flags().VisitAll(func(f *flag.Flag) { + if f.DefValue != "" { + strBuilder.WriteString(fmt.Sprintf(" --%s %s%s%s (default: %q)\n", + f.Name, + strings.Repeat(" ", longestName-len(f.Name)+2), + f.Usage, + strings.Repeat(" ", longestUsage-len(f.Usage)+2), + f.DefValue, + )) + } else { + strBuilder.WriteString(fmt.Sprintf(" --%s %s%s%s\n", + f.Name, + strings.Repeat(" ", longestName-len(f.Name)+2), + f.Usage, + strings.Repeat(" ", longestUsage-len(f.Usage)+2), + )) + } + }) + strBuilder.WriteString("\n") + + return strBuilder.String() +} + +func (cmd *migrateCmd) Flags() *flag.FlagSet { + fs := flag.NewFlagSet("migrate", flag.ExitOnError) + + fs.StringVar(&cmd.flagProviderDir, "provider-dir", "", "relative or absolute path to the root provider code directory; this will default to the current working directory if not set") + fs.StringVar(&cmd.flagTemplatesDir, "templates-dir", "templates", "new website templates directory based on provider-dir; files will be migrated to this directory") + fs.StringVar(&cmd.flagExamplesDir, "examples-dir", "examples", "examples directory based on provider-dir; extracted code examples will be migrated to this directory") + return fs +} + +func (cmd *migrateCmd) Run(args []string) int { + fs := cmd.Flags() + err := fs.Parse(args) + if err != nil { + cmd.ui.Error(fmt.Sprintf("unable to parse flags: %s", err)) + return 1 + } + + return cmd.run(cmd.runInternal) +} + +func (cmd *migrateCmd) runInternal() error { + err := provider.Migrate( + cmd.ui, + cmd.flagProviderDir, + cmd.flagTemplatesDir, + cmd.flagExamplesDir, + ) + if err != nil { + return fmt.Errorf("unable to migrate website: %w", err) + } + + return nil +} diff --git a/internal/cmd/run.go b/internal/cmd/run.go index bf0266b4..a1f5391b 100644 --- a/internal/cmd/run.go +++ b/internal/cmd/run.go @@ -57,10 +57,19 @@ func initCommands(ui cli.Ui) map[string]cli.CommandFactory { }, nil } + migrateFactory := func() (cli.Command, error) { + return &migrateCmd{ + commonCmd: commonCmd{ + ui: ui, + }, + }, nil + } + return map[string]cli.CommandFactory{ "": defaultFactory, "generate": generateFactory, "validate": validateFactory, + "migrate": migrateFactory, //"serve": serveFactory, } } diff --git a/internal/provider/migrate.go b/internal/provider/migrate.go new file mode 100644 index 00000000..4e9159d7 --- /dev/null +++ b/internal/provider/migrate.go @@ -0,0 +1,397 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "bufio" + "bytes" + "fmt" + "io/fs" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + + "github.com/mitchellh/cli" + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/text" +) + +type migrator struct { + // providerDir is the absolute path to the root provider directory + providerDir string + + websiteDir string + templatesDir string + examplesDir string + + ui cli.Ui +} + +func (m *migrator) infof(format string, a ...interface{}) { + m.ui.Info(fmt.Sprintf(format, a...)) +} + +func (m *migrator) warnf(format string, a ...interface{}) { + m.ui.Warn(fmt.Sprintf(format, a...)) +} + +func Migrate(ui cli.Ui, providerDir string, templatesDir string, examplesDir string) error { + // Ensure provider directory is resolved absolute path + if providerDir == "" { + wd, err := os.Getwd() + + if err != nil { + return fmt.Errorf("error getting working directory: %w", err) + } + + providerDir = wd + } else { + absProviderDir, err := filepath.Abs(providerDir) + + if err != nil { + return fmt.Errorf("error getting absolute path with provider directory %q: %w", providerDir, err) + } + + providerDir = absProviderDir + } + + // Verify provider directory + providerDirFileInfo, err := os.Stat(providerDir) + + if err != nil { + return fmt.Errorf("error getting information for provider directory %q: %w", providerDir, err) + } + + if !providerDirFileInfo.IsDir() { + return fmt.Errorf("expected %q to be a directory", providerDir) + } + + // Determine website directory + websiteDir, err := determineWebsiteDir(providerDir) + if err != nil { + return err + } + + m := &migrator{ + providerDir: providerDir, + templatesDir: templatesDir, + examplesDir: examplesDir, + websiteDir: websiteDir, + ui: ui, + } + + return m.Migrate() +} + +func (m *migrator) Migrate() error { + m.infof("migrating website from %q to %q", m.ProviderWebsiteDir(), m.ProviderTemplatesDir()) + + err := filepath.WalkDir(m.ProviderWebsiteDir(), func(path string, d os.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("unable to walk path %q: %w", path, err) + } + + if d.IsDir() { + switch d.Name() { + case "d", "data-sources": //data-sources + m.infof("migrating data-sources directory: %s", d.Name()) + err := filepath.WalkDir(path, m.MigrateTemplate("data-sources")) + if err != nil { + return err + } + return filepath.SkipDir + case "r", "resources": //resources + m.infof("migrating resources directory: %s", d.Name()) + err := filepath.WalkDir(path, m.MigrateTemplate("resources")) + if err != nil { + return err + } + return filepath.SkipDir + case "guides": + m.infof("copying guides directory: %s", d.Name()) + err := cp(path, filepath.Join(m.ProviderTemplatesDir(), "guides")) + if err != nil { + return fmt.Errorf("unable to copy guides directory %q: %w", path, err) + } + return filepath.SkipDir + } + } else { + switch { + case regexp.MustCompile(`index.*`).MatchString(d.Name()): //index file + m.infof("migrating provider index: %s", d.Name()) + err := filepath.WalkDir(path, m.MigrateTemplate("")) + if err != nil { + return err + } + return nil + default: + //skip non-index files + return nil + } + } + + return nil + }) + if err != nil { + return fmt.Errorf("unable to migrate website: %w", err) + } + + //remove legacy website directory + err = os.RemoveAll(filepath.Join(m.providerDir, "website")) + if err != nil { + return fmt.Errorf("unable to remove legacy website directory: %w", err) + } + + return nil +} + +func (m *migrator) MigrateTemplate(relDir string) fs.WalkDirFunc { + return func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + //skip processing directories + return nil + } + + m.infof("migrating file %q", d.Name()) + data, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("unable to read file %q: %w", d.Name(), err) + } + + baseName, _, _ := strings.Cut(d.Name(), ".") + + var exampleRelDir string + if baseName == "index" { + exampleRelDir = relDir + } else { + exampleRelDir = filepath.Join(relDir, baseName) + } + templateFilePath := filepath.Join(m.ProviderTemplatesDir(), relDir, baseName+".md.tmpl") + + err = os.MkdirAll(filepath.Dir(templateFilePath), 0755) + if err != nil { + return fmt.Errorf("unable to create directory %q: %w", templateFilePath, err) + } + + m.infof("extracting YAML frontmatter to %q", templateFilePath) + err = m.ExtractFrontMatter(data, templateFilePath) + if err != nil { + return fmt.Errorf("unable to extract front matter to %q: %w", templateFilePath, err) + } + + m.infof("extracting code examples from %q", d.Name()) + err = m.ExtractCodeExamples(data, exampleRelDir, templateFilePath) + if err != nil { + return fmt.Errorf("unable to extract code examples from %q: %w", templateFilePath, err) + } + + return nil + } + +} + +func (m *migrator) ExtractFrontMatter(content []byte, templateFilePath string) error { + templateFile, err := os.OpenFile(templateFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return fmt.Errorf("unable to open file %q: %w", templateFilePath, err) + } + defer func(f *os.File) { + err := f.Close() + if err != nil { + m.warnf("unable to close file %q: %q", templateFilePath, err) + } + }(templateFile) + + fileScanner := bufio.NewScanner(bytes.NewReader(content)) + fileScanner.Split(bufio.ScanLines) + + hasFirstLine := fileScanner.Scan() + if !hasFirstLine || fileScanner.Text() != "---" { + m.warnf("no frontmatter found in %q", templateFilePath) + return nil + } + _, err = templateFile.WriteString(fileScanner.Text() + "\n") + if err != nil { + return fmt.Errorf("unable to append frontmatter to %q: %w", templateFilePath, err) + } + exited := false + for fileScanner.Scan() { + if strings.Contains(fileScanner.Text(), "layout:") { + // skip layout front matter + continue + } + _, err = templateFile.WriteString(fileScanner.Text() + "\n") + if err != nil { + return fmt.Errorf("unable to append frontmatter to %q: %w", templateFilePath, err) + } + if fileScanner.Text() == "---" { + exited = true + break + } + } + + if !exited { + return fmt.Errorf("cannot find ending of frontmatter block in %q", templateFilePath) + } + + // add comment to end of front matter briefly explaining template functionality + _, err = templateFile.WriteString(migrateProviderTemplateComment + "\n") + if err != nil { + return fmt.Errorf("unable to append template comment to %q: %w", templateFilePath, err) + } + + return nil +} + +func (m *migrator) ExtractCodeExamples(content []byte, newRelDir string, templateFilePath string) error { + templateFile, err := os.OpenFile(templateFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return fmt.Errorf("unable to open file %q: %w", templateFilePath, err) + } + defer func(f *os.File) { + err := f.Close() + if err != nil { + m.warnf("unable to close file %q: %q", templateFilePath, err) + } + }(templateFile) + + md := newMarkdownRenderer() + p := md.Parser() + root := p.Parse(text.NewReader(content)) + + exampleCount := 0 + importCount := 0 + + err = ast.Walk(root, func(node ast.Node, enter bool) (ast.WalkStatus, error) { + // skip the root node + if !enter || node.Type() == ast.TypeDocument { + return ast.WalkContinue, nil + } + + if fencedNode, isFenced := node.(*ast.FencedCodeBlock); isFenced && fencedNode.Info != nil { + var ext, exampleName, examplePath, template string + + lang := string(fencedNode.Info.Text(content)[:]) + switch lang { + case "hcl", "terraform": + exampleCount++ + ext = ".tf" + exampleName = "example_" + strconv.Itoa(exampleCount) + ext + examplePath = filepath.Join(m.ProviderExamplesDir(), newRelDir, exampleName) + template = fmt.Sprintf("{{tffile \"%s\"}}", examplePath) + m.infof("creating example file %q", examplePath) + case "console": + importCount++ + ext = ".sh" + exampleName = "import_" + strconv.Itoa(importCount) + ext + examplePath = filepath.Join(m.ProviderExamplesDir(), newRelDir, exampleName) + template = fmt.Sprintf("{{codefile \"shell\" \"%s\"}}", examplePath) + m.infof("creating import file %q", examplePath) + default: + // Render node as is + m.infof("skipping code block with unknown language %q", lang) + err = md.Renderer().Render(templateFile, content, node) + if err != nil { + return ast.WalkStop, fmt.Errorf("unable to render node: %w", err) + } + return ast.WalkSkipChildren, nil + } + + // add code block text to buffer + codeBuf := bytes.Buffer{} + for i := 0; i < node.Lines().Len(); i++ { + line := node.Lines().At(i) + _, _ = codeBuf.Write(line.Value(content)) + } + + // create example file from code block + err = writeFile(examplePath, codeBuf.String()) + if err != nil { + return ast.WalkStop, fmt.Errorf("unable to write file %q: %w", examplePath, err) + } + + // replace original code block with tfplugindocs template + _, err = templateFile.WriteString("\n\n" + template) + if err != nil { + return ast.WalkStop, fmt.Errorf("unable to write to template %q: %w", template, err) + } + + return ast.WalkSkipChildren, nil + } + + // Render non-code nodes as is + err = md.Renderer().Render(templateFile, content, node) + if err != nil { + return ast.WalkStop, fmt.Errorf("unable to render node: %w", err) + } + if node.HasChildren() { + return ast.WalkSkipChildren, nil + } + + return ast.WalkContinue, nil + }) + if err != nil { + return fmt.Errorf("unable to walk AST: %w", err) + } + + _, err = templateFile.WriteString("\n") + if err != nil { + return fmt.Errorf("unable to write to template %q: %w", templateFilePath, err) + } + m.infof("finished creating template %q", templateFilePath) + + return nil +} + +// ProviderWebsiteDir returns the absolute path to the joined provider and +// the website directory that templates will be migrated from, which defaults to either "website/docs/" or "docs". +func (m *migrator) ProviderWebsiteDir() string { + return filepath.Join(m.providerDir, m.websiteDir) +} + +// ProviderTemplatesDir returns the absolute path to the joined provider and +// given new templates directory, which defaults to "templates". +func (m *migrator) ProviderTemplatesDir() string { + return filepath.Join(m.providerDir, m.templatesDir) +} + +// ProviderExamplesDir returns the absolute path to the joined provider and +// given examples directory, which defaults to "examples". +func (m *migrator) ProviderExamplesDir() string { + return filepath.Join(m.providerDir, m.examplesDir) +} + +func determineWebsiteDir(providerDir string) (string, error) { + // Check for legacy website directory + providerWebsiteDirFileInfo, err := os.Stat(filepath.Join(providerDir, "website/docs")) + + if err != nil { + if os.IsNotExist(err) { + // Legacy website directory does not exist, check for docs directory + } else { + return "", fmt.Errorf("error getting information for provider website directory %q: %w", providerDir, err) + } + } else if providerWebsiteDirFileInfo.IsDir() { + return "website/docs", nil + } + + // Check for docs directory + providerDocsDirFileInfo, err := os.Stat(filepath.Join(providerDir, "docs")) + + if err != nil { + return "", fmt.Errorf("error getting information for provider docs directory %q: %w", providerDir, err) + } + + if providerDocsDirFileInfo.IsDir() { + return "docs", nil + } + + return "", fmt.Errorf("unable to determine website directory for provider %q", providerDir) + +} diff --git a/internal/provider/template.go b/internal/provider/template.go index f2a24cbc..809d7411 100644 --- a/internal/provider/template.go +++ b/internal/provider/template.go @@ -249,3 +249,9 @@ description: |- {{ .SchemaMarkdown | trimspace }} ` + +const migrateProviderTemplateComment string = ` +{{/* This template serves as a starting point for documentation generation, and can be customized with hardcoded values and/or doc gen templates. + +For example, the {{ SchemaMarkdown }} template can be used to replace manual schema documentation if descriptions of schema attributes are added in the provider source code. */ -}} +` diff --git a/internal/provider/util.go b/internal/provider/util.go index bb4f50e4..21063dc8 100644 --- a/internal/provider/util.go +++ b/internal/provider/util.go @@ -12,7 +12,12 @@ import ( "path/filepath" "strings" + "github.com/Kunde21/markdownfmt/v3/markdown" tfjson "github.com/hashicorp/terraform-json" + "github.com/yuin/goldmark" + meta "github.com/yuin/goldmark-meta" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" ) func providerShortName(n string) string { @@ -160,3 +165,21 @@ func extractSchemaFromFile(path string) (*tfjson.ProviderSchemas, error) { return schemas, nil } + +func newMarkdownRenderer() goldmark.Markdown { + mr := markdown.NewRenderer() + extensions := []goldmark.Extender{ + extension.GFM, + meta.Meta, // We need this to skip YAML frontmatter when parsing. + } + parserOptions := []parser.Option{ + parser.WithAttribute(), // We need this to enable # headers {#custom-ids}. + } + + gm := goldmark.New( + goldmark.WithExtensions(extensions...), + goldmark.WithParserOptions(parserOptions...), + goldmark.WithRenderer(mr), + ) + return gm +}