├── .changes ├── 0.10.0.md ├── 0.11.0.md ├── 0.11.1.md ├── 0.11.2.md ├── 0.12.0.md ├── 0.13.0.md ├── 0.14.0.md ├── 0.15.0.md ├── 0.16.0.md ├── 0.17.0.md ├── 0.18.0.md ├── 0.19.0-alpha.1.md ├── 0.19.0.md ├── 0.20.0.md ├── 0.8.0.md ├── 0.9.0.md └── unreleased │ └── .gitkeep ├── .changie.yaml ├── .copywrite.hcl ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── documentation_suggestion.md │ └── feature_request.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── ci-changie.yml │ ├── ci-github-actions.yml │ ├── ci-go.yml │ ├── ci-goreleaser.yml │ ├── compliance.yml │ ├── issue-comment-triage.yml │ ├── lock.yml │ └── release.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── CHANGELOG.md ├── LICENSE ├── META.d └── _summary.yaml ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── internal ├── logging │ ├── context.go │ ├── doc.go │ ├── environment_variables.go │ ├── keys.go │ └── mux.go ├── tf5dynamicvalue │ ├── doc.go │ └── must.go ├── tf5testserver │ └── tf5testserver.go ├── tf6dynamicvalue │ ├── doc.go │ └── must.go ├── tf6testserver │ └── tf6testserver.go ├── tfprotov5tov6 │ ├── tfprotov5tov6.go │ └── tfprotov5tov6_test.go └── tfprotov6tov5 │ ├── tfprotov6tov5.go │ └── tfprotov6tov5_test.go ├── tf5muxserver ├── diagnostics.go ├── doc.go ├── mux_server.go ├── mux_server_ApplyResourceChange.go ├── mux_server_ApplyResourceChange_test.go ├── mux_server_CallFunction.go ├── mux_server_CallFunction_test.go ├── mux_server_CloseEphemeralResource.go ├── mux_server_CloseEphemeralResource_test.go ├── mux_server_ConfigureProvider.go ├── mux_server_ConfigureProvider_test.go ├── mux_server_GetFunctions.go ├── mux_server_GetFunctions_test.go ├── mux_server_GetMetadata.go ├── mux_server_GetMetadata_test.go ├── mux_server_GetProviderSchema.go ├── mux_server_GetProviderSchema_test.go ├── mux_server_GetResourceIdentitySchemas.go ├── mux_server_GetResourceIdentitySchemas_test.go ├── mux_server_ImportResourceState.go ├── mux_server_ImportResourceState_test.go ├── mux_server_MoveResourceState.go ├── mux_server_MoveResourceState_test.go ├── mux_server_OpenEphemeralResource.go ├── mux_server_OpenEphemeralResource_test.go ├── mux_server_PlanResourceChange.go ├── mux_server_PlanResourceChange_test.go ├── mux_server_PrepareProviderConfig.go ├── mux_server_PrepareProviderConfig_test.go ├── mux_server_ReadDataSource.go ├── mux_server_ReadDataSource_test.go ├── mux_server_ReadResource.go ├── mux_server_ReadResource_test.go ├── mux_server_RenewEphemeralResource.go ├── mux_server_RenewEphemeralResource_test.go ├── mux_server_StopProvider.go ├── mux_server_StopProvider_test.go ├── mux_server_UpgradeResourceIdentity.go ├── mux_server_UpgradeResourceIdentity_test.go ├── mux_server_UpgradeResourceState.go ├── mux_server_UpgradeResourceState_test.go ├── mux_server_ValidateDataSourceConfig.go ├── mux_server_ValidateDataSourceConfig_test.go ├── mux_server_ValidateEphemeralResourceConfig.go ├── mux_server_ValidateEphemeralResourceConfig_test.go ├── mux_server_ValidateResourceTypeConfig.go ├── mux_server_ValidateResourceTypeConfig_test.go ├── mux_server_example_test.go ├── mux_server_test.go ├── schema_equality.go └── server_capabilities.go ├── tf5to6server ├── doc.go ├── tf5to6server.go └── tf5to6server_test.go ├── tf6muxserver ├── diagnostics.go ├── doc.go ├── mux_server.go ├── mux_server_ApplyResourceChange.go ├── mux_server_ApplyResourceChange_test.go ├── mux_server_CallFunction.go ├── mux_server_CallFunction_test.go ├── mux_server_CloseEphemeralResource.go ├── mux_server_CloseEphemeralResource_test.go ├── mux_server_ConfigureProvider.go ├── mux_server_ConfigureProvider_test.go ├── mux_server_GetFunctions.go ├── mux_server_GetFunctions_test.go ├── mux_server_GetMetadata.go ├── mux_server_GetMetadata_test.go ├── mux_server_GetProviderSchema.go ├── mux_server_GetProviderSchema_test.go ├── mux_server_GetResourceIdentitySchemas.go ├── mux_server_GetResourceIdentitySchemas_test.go ├── mux_server_ImportResourceState.go ├── mux_server_ImportResourceState_test.go ├── mux_server_MoveResourceState.go ├── mux_server_MoveResourceState_test.go ├── mux_server_OpenEphemeralResource.go ├── mux_server_OpenEphemeralResource_test.go ├── mux_server_PlanResourceChange.go ├── mux_server_PlanResourceChange_test.go ├── mux_server_ReadDataSource.go ├── mux_server_ReadDataSource_test.go ├── mux_server_ReadResource.go ├── mux_server_ReadResource_test.go ├── mux_server_RenewEphemeralResource.go ├── mux_server_RenewEphemeralResource_test.go ├── mux_server_StopProvider.go ├── mux_server_StopProvider_test.go ├── mux_server_UpgradeResourceIdentity.go ├── mux_server_UpgradeResourceIdentity_test.go ├── mux_server_UpgradeResourceState.go ├── mux_server_UpgradeResourceState_test.go ├── mux_server_ValidateDataResourceConfig.go ├── mux_server_ValidateDataResourceConfig_test.go ├── mux_server_ValidateEphemeralResourceConfig.go ├── mux_server_ValidateEphemeralResourceConfig_test.go ├── mux_server_ValidateProviderConfig.go ├── mux_server_ValidateProviderConfig_test.go ├── mux_server_ValidateResourceConfig.go ├── mux_server_ValidateResourceConfig_test.go ├── mux_server_example_test.go ├── mux_server_test.go ├── schema_equality.go └── server_capabilities.go ├── tf6to5server ├── doc.go ├── tf6to5server.go └── tf6to5server_test.go ├── tools ├── copywrite.go ├── go.mod └── go.sum └── website ├── Makefile ├── README.md ├── data └── plugin-mux-nav-data.json ├── docs └── plugin │ └── mux │ ├── combining-protocol-version-5-providers.mdx │ ├── combining-protocol-version-6-providers.mdx │ ├── index.mdx │ ├── translating-protocol-version-5-to-6.mdx │ └── translating-protocol-version-6-to-5.mdx ├── img └── .gitkeep ├── package-lock.json ├── package.json └── scripts ├── should-build.sh ├── website-build.sh └── website-start.sh /.changes/0.10.0.md: -------------------------------------------------------------------------------- 1 | ## 0.10.0 (April 24, 2023) 2 | 3 | NOTES: 4 | 5 | * This Go module has been updated to Go 1.19 per the [Go support policy](https://golang.org/doc/devel/release.html#policy). Any consumers building on earlier Go versions may experience errors. ([#143](https://github.com/hashicorp/terraform-plugin-mux/issues/143)) 6 | 7 | BUG FIXES: 8 | 9 | * tf5muxserver+tf6muxserver: Ensure provider acceptance testing can properly detect mux server errors in `GetProviderSchema` response ([#152](https://github.com/hashicorp/terraform-plugin-mux/issues/152)) 10 | 11 | -------------------------------------------------------------------------------- /.changes/0.11.0.md: -------------------------------------------------------------------------------- 1 | ## 0.11.0 (June 28, 2023) 2 | 3 | BUG FIXES: 4 | 5 | * tf5muxserver: Removed unnecessary resource schema caching, which reduces resident memory utilization ([#168](https://github.com/hashicorp/terraform-plugin-mux/issues/168)) 6 | * tf6muxserver: Removed unnecessary resource schema caching, which reduces resident memory utilization ([#168](https://github.com/hashicorp/terraform-plugin-mux/issues/168)) 7 | 8 | -------------------------------------------------------------------------------- /.changes/0.11.1.md: -------------------------------------------------------------------------------- 1 | ## 0.11.1 (June 29, 2023) 2 | 3 | BUG FIXES: 4 | 5 | * tf5muxserver: Adjust function signature of `NewMuxServer()` to return `*muxServer`, which is required to satisfy the `tfprotov5.ProviderServer` interface ([#172](https://github.com/hashicorp/terraform-plugin-mux/issues/172)) 6 | * tf6muxserver: Adjust function signature of `NewMuxServer()` to return `*muxServer`, which is required to satisfy the `tfprotov6.ProviderServer` interface ([#172](https://github.com/hashicorp/terraform-plugin-mux/issues/172)) 7 | 8 | -------------------------------------------------------------------------------- /.changes/0.11.2.md: -------------------------------------------------------------------------------- 1 | ## 0.11.2 (July 14, 2023) 2 | 3 | BUG FIXES: 4 | 5 | * tf5muxserver: Ensure `GetProviderSchema` RPC diagnostics are properly returned to Terraform ([#176](https://github.com/hashicorp/terraform-plugin-mux/issues/176)) 6 | * tf6muxserver: Ensure `GetProviderSchema` RPC diagnostics are properly returned to Terraform ([#176](https://github.com/hashicorp/terraform-plugin-mux/issues/176)) 7 | 8 | -------------------------------------------------------------------------------- /.changes/0.12.0.md: -------------------------------------------------------------------------------- 1 | ## 0.12.0 (September 06, 2023) 2 | 3 | NOTES: 4 | 5 | * all: This Go module has been updated to Go 1.20 per the [Go support policy](https://go.dev/doc/devel/release#policy). It is recommended to review the [Go 1.20 release notes](https://go.dev/doc/go1.20) before upgrading. Any consumers building on earlier Go versions may experience errors. ([#188](https://github.com/hashicorp/terraform-plugin-mux/issues/188)) 6 | 7 | FEATURES: 8 | 9 | * all: Upgrade to protocol versions 5.4 and 6.4, which can significantly reduce memory usage with Terraform 1.6 and later when a configuration includes multiple instances of the same provider ([#185](https://github.com/hashicorp/terraform-plugin-mux/issues/185)) 10 | 11 | -------------------------------------------------------------------------------- /.changes/0.13.0.md: -------------------------------------------------------------------------------- 1 | ## 0.13.0 (December 14, 2023) 2 | 3 | NOTES: 4 | 5 | * all: Update `google.golang.org/grpc` dependency to address CVE-2023-44487 ([#203](https://github.com/hashicorp/terraform-plugin-mux/issues/203)) 6 | 7 | FEATURES: 8 | 9 | * all: Upgrade protocol versions to support provider-defined functions ([#209](https://github.com/hashicorp/terraform-plugin-mux/issues/209)) 10 | 11 | -------------------------------------------------------------------------------- /.changes/0.14.0.md: -------------------------------------------------------------------------------- 1 | ## 0.14.0 (January 29, 2024) 2 | 3 | FEATURES: 4 | 5 | * all: Upgrade protocol versions to support the `MoveResourceState` RPC ([#220](https://github.com/hashicorp/terraform-plugin-mux/issues/220)) 6 | 7 | -------------------------------------------------------------------------------- /.changes/0.15.0.md: -------------------------------------------------------------------------------- 1 | ## 0.15.0 (February 23, 2024) 2 | 3 | ENHANCEMENTS: 4 | 5 | * all: Upgrade protocol versions to support modified `CallFunction` RPC which returns a FunctionError rather than Diagnostics ([#226](https://github.com/hashicorp/terraform-plugin-mux/issues/226)) 6 | 7 | -------------------------------------------------------------------------------- /.changes/0.16.0.md: -------------------------------------------------------------------------------- 1 | ## 0.16.0 (May 08, 2024) 2 | 3 | NOTES: 4 | 5 | * all: The `v0.15.0` release updated this Go module to Go 1.21 per the [Go support policy](https://go.dev/doc/devel/release#policy). It is recommended to review the [Go 1.21 release notes](https://go.dev/doc/go1.21) before upgrading. Any consumers building on earlier Go versions may experience errors ([#227](https://github.com/hashicorp/terraform-plugin-mux/issues/227)) 6 | 7 | ENHANCEMENTS: 8 | 9 | * tf5to6server: Add deferred action request and response fields to RPC translations ([#237](https://github.com/hashicorp/terraform-plugin-mux/issues/237)) 10 | * tf6to5server: Add deferred action request and response fields to RPC translations ([#237](https://github.com/hashicorp/terraform-plugin-mux/issues/237)) 11 | 12 | -------------------------------------------------------------------------------- /.changes/0.17.0.md: -------------------------------------------------------------------------------- 1 | ## 0.17.0 (October 30, 2024) 2 | 3 | NOTES: 4 | 5 | * all: This Go module has been updated to Go 1.22 per the [Go support policy](https://go.dev/doc/devel/release#policy). It is recommended to review the [Go 1.22 release notes](https://go.dev/doc/go1.22) before upgrading. Any consumers building on earlier Go versions may experience errors. ([#250](https://github.com/hashicorp/terraform-plugin-mux/issues/250)) 6 | 7 | FEATURES: 8 | 9 | * all: Upgrade protocol versions to support ephemeral resource types ([#257](https://github.com/hashicorp/terraform-plugin-mux/issues/257)) 10 | 11 | -------------------------------------------------------------------------------- /.changes/0.18.0.md: -------------------------------------------------------------------------------- 1 | ## 0.18.0 (January 23, 2025) 2 | 3 | FEATURES: 4 | 5 | * all: Upgrade protocol versions to support write-only attributes ([#272](https://github.com/hashicorp/terraform-plugin-mux/issues/272)) 6 | 7 | -------------------------------------------------------------------------------- /.changes/0.19.0-alpha.1.md: -------------------------------------------------------------------------------- 1 | ## 0.19.0-alpha.1 (March 18, 2025) 2 | 3 | NOTES: 4 | 5 | * all: This Go module has been updated to Go 1.23 per the [Go support policy](https://go.dev/doc/devel/release#policy). It is recommended to review the [Go 1.23 release notes](https://go.dev/doc/go1.23) before upgrading. Any consumers building on earlier Go versions may experience errors. ([#291](https://github.com/hashicorp/terraform-plugin-mux/issues/291)) 6 | * This alpha pre-release contains the muxing logic for managed resource identity, which can used with Terraform v1.12.0-alpha20250312, to store and read identity data during plan and apply workflows. ([#278](https://github.com/hashicorp/terraform-plugin-mux/issues/278)) 7 | 8 | -------------------------------------------------------------------------------- /.changes/0.19.0.md: -------------------------------------------------------------------------------- 1 | ## 0.19.0 (May 16, 2025) 2 | 3 | NOTES: 4 | 5 | * all: This Go module has been updated to Go 1.23 per the [Go support policy](https://go.dev/doc/devel/release#policy). It is recommended to review the [Go 1.23 release notes](https://go.dev/doc/go1.23) before upgrading. Any consumers building on earlier Go versions may experience errors. ([#291](https://github.com/hashicorp/terraform-plugin-mux/issues/291)) 6 | 7 | FEATURES: 8 | 9 | * tf5muxserver+tf6muxserver+tf6to5server+tf5to6server: Upgraded protocols and added types to support the new resource identity feature ([#278](https://github.com/hashicorp/terraform-plugin-mux/issues/278)) 10 | 11 | -------------------------------------------------------------------------------- /.changes/0.20.0.md: -------------------------------------------------------------------------------- 1 | ## 0.20.0 (May 21, 2025) 2 | 3 | BUG FIXES: 4 | 5 | * all: Fixed a bug where muxed provider servers were not enforced to implement `GetResourceIdentitySchemas`, which is required by Terraform v1.12.1 in the scenario where at least one of the muxed provider servers supports identity. Before upgrading this dependency the Go modules that support identity should also be upgraded to prevent confusing errors, which are: terraform-plugin-go@v0.28.0, terraform-plugin-framework@v1.15.0, terraform-plugin-sdk/v2@v2.37.0, and terraform-plugin-testing@v1.13.0. ([#307](https://github.com/hashicorp/terraform-plugin-mux/issues/307)) 6 | 7 | -------------------------------------------------------------------------------- /.changes/0.9.0.md: -------------------------------------------------------------------------------- 1 | ## 0.9.0 (February 08, 2023) 2 | 3 | ENHANCEMENTS: 4 | 5 | * tf5muxserver+tf6muxserver: Support Terraform 1.3+ PlanResourceChange on destroy for underlying servers which enable the capability, such as terraform-plugin-framework ([#133](https://github.com/hashicorp/terraform-plugin-mux/issues/133)) 6 | 7 | -------------------------------------------------------------------------------- /.changes/unreleased/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp/terraform-plugin-mux/101e9dfd0787e782fc4d0869931716180da5df09/.changes/unreleased/.gitkeep -------------------------------------------------------------------------------- /.changie.yaml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT - This GitHub Workflow is managed by automation 2 | # https://github.com/hashicorp/terraform-devex-repos 3 | changesDir: .changes 4 | unreleasedDir: unreleased 5 | changelogPath: CHANGELOG.md 6 | versionExt: md 7 | versionFormat: '## {{.Version}} ({{.Time.Format "January 02, 2006"}})' 8 | kindFormat: '{{.Kind}}:' 9 | changeFormat: '* {{.Body}} ([#{{.Custom.Issue}}](https://github.com/hashicorp/terraform-plugin-mux/issues/{{.Custom.Issue}}))' 10 | custom: 11 | - key: Issue 12 | label: Issue/PR Number 13 | type: int 14 | minInt: 1 15 | kinds: 16 | - label: BREAKING CHANGES 17 | - label: NOTES 18 | - label: FEATURES 19 | - label: ENHANCEMENTS 20 | - label: BUG FIXES 21 | newlines: 22 | afterKind: 1 23 | beforeKind: 1 24 | endOfVersion: 2 25 | -------------------------------------------------------------------------------- /.copywrite.hcl: -------------------------------------------------------------------------------- 1 | schema_version = 1 2 | 3 | project { 4 | license = "MPL-2.0" 5 | copyright_year = 2020 6 | 7 | header_ignore = [ 8 | # internal catalog metadata (prose) 9 | "META.d/**/*.yaml", 10 | 11 | # changie tooling configuration and CHANGELOG entries (prose) 12 | ".changes/unreleased/**", 13 | ".changie.yaml", 14 | 15 | # GitHub issue template configuration 16 | ".github/ISSUE_TEMPLATE/*.yml", 17 | 18 | # GitHub Actions workflow-specific configurations 19 | ".github/labeler-*.yml", 20 | 21 | # golangci-lint tooling configuration 22 | ".golangci.yml", 23 | 24 | # GoReleaser tooling configuration 25 | ".goreleaser.yml", 26 | 27 | # Release Engineering tooling configuration 28 | ".release/*.hcl", 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hashicorp/terraform-devex 2 | 3 | # engineering and web presence get notified of, and can approve changes to web tooling, but not content. 4 | 5 | /website/ @hashicorp/web-presence @hashicorp/terraform-devex 6 | /website/data/ 7 | /website/public/ 8 | /website/content/ 9 | 10 | # education and engineering get notified of, and can approve changes to web content. 11 | 12 | /website/data/ @hashicorp/team-docs-packer-and-terraform @hashicorp/terraform-devex 13 | /website/public/ @hashicorp/team-docs-packer-and-terraform @hashicorp/terraform-devex 14 | /website/content/ @hashicorp/team-docs-packer-and-terraform @hashicorp/terraform-devex 15 | /website/docs/ @hashicorp/team-docs-packer-and-terraform @hashicorp/terraform-devex 16 | /website/img/ @hashicorp/team-docs-packer-and-terraform @hashicorp/terraform-devex 17 | /website/README.md @hashicorp/team-docs-packer-and-terraform @hashicorp/terraform-devex 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug report 3 | about: Let us know about an unexpected error, a crash, or an incorrect behavior. 4 | labels: bug 5 | --- 6 | 7 | ### terraform-plugin-mux version 8 | 17 | 18 | ``` 19 | insert version here 20 | ``` 21 | 22 | ### Relevant provider source code 23 | 24 | 28 | ```go 29 | 30 | // insert code here 31 | 32 | ``` 33 | 34 | ### Terraform Configuration Files 35 | 38 | 39 | ```tf 40 | # insert config here 41 | ``` 42 | 43 | 44 | ### Expected Behavior 45 | 48 | 49 | ### Actual Behavior 50 | 53 | 54 | ### Steps to Reproduce 55 | 60 | 61 | ### References 62 | 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Terraform Language or Workflow Feedback and Questions 4 | url: https://github.com/hashicorp/terraform/issues/new/choose 5 | about: Terraform Core has its own repository, any language (HCL) or workflow related issues or questions should be directed there. 6 | - name: Terraform Plugin SDK Feedback 7 | url: https://github.com/hashicorp/terraform-plugin-sdk/issues/new/choose 8 | about: The Terraform Plugin SDK has its own repository, any feedback related to the SDK should be directed there. 9 | - name: terraform-plugin-go Feedback 10 | url: https://github.com/hashicorp/terraform-plugin-go/issues/new/choose 11 | about: The terraform-plugin-go library has its own repository, any feedback related to it shouldbe directed there. 12 | - name: ❓ Provider Development Questions 13 | url: https://discuss.hashicorp.com/c/terraform-providers/tf-plugin-sdk 14 | about: Please ask and answer questions about provider development through the Plugin SDK Community Forum. 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_suggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation suggestion 3 | about: Suggest new documentation or an enhancement to existing documentation. 4 | labels: documentation 5 | --- 6 | 7 | ### Does this documenation exist? 8 | 9 | * [ ] This is new documentation 10 | * [ ] This is an enhancement to existing documentation 11 | 12 | ### Where would you expect to find this documentation? 13 | 14 | * [ ] On terraform.io 15 | * [ ] In the GoDoc for this module 16 | * [ ] In this repo as a markdown file 17 | * [ ] Somewhere else 18 | 19 | #### Details 20 | 21 | 26 | 27 | ### Description 28 | 35 | 36 | ### References 37 | 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature or other enhancement. 4 | labels: enhancement 5 | --- 6 | 7 | ### terraform-plugin-mux version 8 | 16 | ``` 17 | insert version here 18 | ``` 19 | 20 | ### Use cases 21 | 28 | 29 | ### Attempted solutions 30 | 42 | 43 | ### Proposal 44 | 57 | 58 | ### References 59 | 66 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | ignore: 6 | # grpc should only be updated via terraform-plugin-go 7 | - dependency-name: "google.golang.org/grpc" 8 | schedule: 9 | interval: "daily" 10 | - package-ecosystem: "gomod" 11 | directory: "/tools" 12 | schedule: 13 | interval: "daily" 14 | - package-ecosystem: "github-actions" 15 | directory: "/" 16 | schedule: 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Related Issue 2 | 3 | Fixes # 4 | 5 | ## Description 6 | 7 | In plain English, describe your approach to addressing the issue linked above. For example, if you made a particular design decision, let us know why you chose this path instead of another solution. 8 | 9 | 10 | ## Rollback Plan 11 | 12 | - [ ] If a change needs to be reverted, we will roll out an update to the code within 7 days. 13 | 14 | ## Changes to Security Controls 15 | 16 | Are there any changes to security controls (access controls, encryption, logging) in this pull request? If so, explain. 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-changie.yml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT - This GitHub Workflow is managed by automation 2 | # https://github.com/hashicorp/terraform-devex-repos 3 | 4 | # Continuous integration handling for changie 5 | name: ci-changie 6 | 7 | on: 8 | pull_request: 9 | paths: 10 | - .changes/unreleased/*.yaml 11 | - .changie.yaml 12 | - .github/workflows/ci-changie.yml 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | check: 19 | runs-on: ubuntu-latest 20 | steps: 21 | # Ensure terraform-devex-repos is updated on version changes. 22 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 23 | # Ensure terraform-devex-repos is updated on version changes. 24 | - uses: miniscruff/changie-action@6dcc2533cac0495148ed4046c438487e4dceaa23 # v2.0.0 25 | with: 26 | version: latest 27 | args: batch patch --dry-run 28 | -------------------------------------------------------------------------------- /.github/workflows/ci-github-actions.yml: -------------------------------------------------------------------------------- 1 | # Continuous integration handling for GitHub Actions workflows 2 | name: ci-github-actions 3 | 4 | on: 5 | pull_request: 6 | paths: 7 | - .github/workflows/*.yml 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | actionlint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 17 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 18 | with: 19 | go-version-file: 'go.mod' 20 | - run: go install github.com/rhysd/actionlint/cmd/actionlint@latest 21 | - run: actionlint 22 | -------------------------------------------------------------------------------- /.github/workflows/ci-go.yml: -------------------------------------------------------------------------------- 1 | # Continuous integration handling for Go 2 | name: ci-go 3 | 4 | on: 5 | pull_request: 6 | paths: 7 | - .github/workflows/ci-go.yml 8 | - .golangci.yml 9 | - .go-version 10 | - go.mod 11 | - '**.go' 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | golangci-lint: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 21 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 22 | with: 23 | go-version-file: 'go.mod' 24 | - run: go mod download 25 | - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 26 | terraform-provider-corner-tfprotov5: 27 | defaults: 28 | run: 29 | working-directory: terraform-provider-corner 30 | name: terraform-provider-corner (tfprotov5 /Terraform ${{ matrix.terraform}}) 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 34 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 35 | with: 36 | path: terraform-provider-corner 37 | repository: hashicorp/terraform-provider-corner 38 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 39 | with: 40 | go-version-file: 'go.mod' 41 | - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 42 | with: 43 | terraform_version: ${{ matrix.terraform }} 44 | terraform_wrapper: false 45 | - run: go mod edit -replace=github.com/hashicorp/terraform-plugin-mux=../ 46 | - run: go mod tidy 47 | - run: go test -v ./internal/tf5muxprovider 48 | env: 49 | TF_ACC: '1' 50 | - run: go test -v ./internal/tf6to5provider 51 | env: 52 | TF_ACC: '1' 53 | strategy: 54 | fail-fast: false 55 | matrix: 56 | terraform: ${{ fromJSON(vars.TF_VERSIONS_PROTOCOL_V5) }} 57 | terraform-provider-corner-tfprotov6: 58 | defaults: 59 | run: 60 | working-directory: terraform-provider-corner 61 | name: terraform-provider-corner (tfprotov6 /Terraform ${{ matrix.terraform}}) 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 65 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 66 | with: 67 | path: terraform-provider-corner 68 | repository: hashicorp/terraform-provider-corner 69 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 70 | with: 71 | go-version-file: 'go.mod' 72 | - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 73 | with: 74 | terraform_version: ${{ matrix.terraform }} 75 | terraform_wrapper: false 76 | - run: go mod edit -replace=github.com/hashicorp/terraform-plugin-mux=../ 77 | - run: go mod tidy 78 | - run: go test -v ./internal/tf6muxprovider 79 | env: 80 | TF_ACC: '1' 81 | - run: go test -v ./internal/tf5to6provider 82 | env: 83 | TF_ACC: '1' 84 | strategy: 85 | fail-fast: false 86 | matrix: 87 | terraform: ${{ fromJSON(vars.TF_VERSIONS_PROTOCOL_V6) }} 88 | test: 89 | name: test (Go v${{ matrix.go-version }}) 90 | runs-on: ubuntu-latest 91 | strategy: 92 | matrix: 93 | go-version: [ '1.24', '1.23' ] 94 | steps: 95 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 96 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 97 | with: 98 | go-version: ${{ matrix.go-version }} 99 | - run: go mod download 100 | - run: go test -coverprofile=coverage.out ./... 101 | - run: go tool cover -html=coverage.out -o coverage.html 102 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 103 | with: 104 | name: go-${{ matrix.go-version }}-coverage 105 | path: coverage.html 106 | -------------------------------------------------------------------------------- /.github/workflows/ci-goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Continuous integration handling for GoReleaser 2 | name: ci-goreleaser 3 | 4 | on: 5 | pull_request: 6 | paths: 7 | - .github/workflows/ci-goreleaser.yml 8 | - .goreleaser.yml 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | check: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 18 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 19 | with: 20 | go-version-file: 'go.mod' 21 | - uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 22 | with: 23 | args: check 24 | -------------------------------------------------------------------------------- /.github/workflows/compliance.yml: -------------------------------------------------------------------------------- 1 | name: compliance 2 | 3 | on: 4 | pull_request: 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | # Reference: ENGSRV-059 11 | copywrite: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 15 | - uses: hashicorp/setup-copywrite@32638da2d4e81d56a0764aa1547882fc4d209636 # v1.1.3 16 | - run: copywrite headers --plan 17 | - run: copywrite license --plan -------------------------------------------------------------------------------- /.github/workflows/issue-comment-triage.yml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT - This GitHub Workflow is managed by automation 2 | # https://github.com/hashicorp/terraform-devex-repos 3 | name: Issue Comment Triage 4 | 5 | on: 6 | issue_comment: 7 | types: [created] 8 | 9 | jobs: 10 | issue_comment_triage: 11 | runs-on: ubuntu-latest 12 | env: 13 | # issue_comment events are triggered by comments on issues and pull requests. Checking the 14 | # value of github.event.issue.pull_request tells us whether the issue is an issue or is 15 | # actually a pull request, allowing us to dynamically set the gh subcommand: 16 | # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment-on-issues-only-or-pull-requests-only 17 | COMMAND: ${{ github.event.issue.pull_request && 'pr' || 'issue' }} 18 | GH_TOKEN: ${{ github.token }} 19 | steps: 20 | - name: 'Remove waiting-response on comment' 21 | run: gh ${{ env.COMMAND }} edit ${{ github.event.issue.html_url }} --remove-label waiting-response 22 | -------------------------------------------------------------------------------- /.github/workflows/lock.yml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT - This GitHub Workflow is managed by automation 2 | # https://github.com/hashicorp/terraform-devex-repos 3 | name: 'Lock Threads' 4 | 5 | on: 6 | schedule: 7 | - cron: '58 11 * * *' 8 | 9 | jobs: 10 | lock: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # NOTE: When TSCCR updates the GitHub action version, update the template workflow file to avoid drift: 14 | # https://github.com/hashicorp/terraform-devex-repos/blob/main/modules/repo/workflows/lock.tftpl 15 | - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 16 | with: 17 | github-token: ${{ github.token }} 18 | issue-inactive-days: '30' 19 | issue-lock-reason: resolved 20 | pr-inactive-days: '30' 21 | pr-lock-reason: resolved 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | versionNumber: 7 | description: 'Release version number (v#.#.#)' 8 | type: string 9 | required: true 10 | 11 | permissions: 12 | contents: read # Changelog commit operations use service account PAT 13 | 14 | jobs: 15 | changelog-version: 16 | runs-on: ubuntu-latest 17 | outputs: 18 | version: ${{ steps.changelog-version.outputs.version }} 19 | steps: 20 | - id: changelog-version 21 | run: echo "version=$(echo "${{ inputs.versionNumber }}" | cut -c 2-)" >> "$GITHUB_OUTPUT" 22 | 23 | changelog: 24 | needs: changelog-version 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 29 | with: 30 | fetch-depth: 0 31 | # Avoid persisting GITHUB_TOKEN credentials as they take priority over our service account PAT for `git push` operations 32 | # More details: https://github.com/actions/checkout/blob/b4626ce19ce1106186ddf9bb20e706842f11a7c3/adrs/0153-checkout-v2.md#persist-credentials 33 | persist-credentials: false 34 | - name: Batch changes 35 | uses: miniscruff/changie-action@6dcc2533cac0495148ed4046c438487e4dceaa23 # v2.0.0 36 | with: 37 | version: latest 38 | args: batch ${{ needs.changelog-version.outputs.version }} 39 | - name: Merge changes 40 | uses: miniscruff/changie-action@6dcc2533cac0495148ed4046c438487e4dceaa23 # v2.0.0 41 | with: 42 | version: latest 43 | args: merge 44 | - name: Git push changelog 45 | run: | 46 | git config --global user.name "${{ vars.TF_DEVEX_CI_COMMIT_AUTHOR }}" 47 | git config --global user.email "${{ vars.TF_DEVEX_CI_COMMIT_EMAIL }}" 48 | git add . 49 | git commit -a -m "Update changelog" 50 | git push "https://${{ vars.TF_DEVEX_CI_COMMIT_AUTHOR }}:${{ secrets.TF_DEVEX_COMMIT_GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" 51 | 52 | release-tag: 53 | needs: changelog 54 | runs-on: ubuntu-latest 55 | steps: 56 | - name: Checkout 57 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 58 | with: 59 | fetch-depth: 0 60 | # Default input is the SHA that initially triggered the workflow. As we created a new commit in the previous job, 61 | # to ensure we get the latest commit we use the ref for checkout: 'refs/heads/' 62 | ref: ${{ github.ref }} 63 | # Avoid persisting GITHUB_TOKEN credentials as they take priority over our service account PAT for `git push` operations 64 | # More details: https://github.com/actions/checkout/blob/b4626ce19ce1106186ddf9bb20e706842f11a7c3/adrs/0153-checkout-v2.md#persist-credentials 65 | persist-credentials: false 66 | 67 | - name: Git push release tag 68 | run: | 69 | git config --global user.name "${{ vars.TF_DEVEX_CI_COMMIT_AUTHOR }}" 70 | git config --global user.email "${{ vars.TF_DEVEX_CI_COMMIT_EMAIL }}" 71 | 72 | git tag "${{ inputs.versionNumber }}" 73 | git push "https://${{ vars.TF_DEVEX_CI_COMMIT_AUTHOR }}:${{ secrets.TF_DEVEX_COMMIT_GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" "${{ inputs.versionNumber }}" 74 | 75 | goreleaser: 76 | needs: [ changelog-version, changelog, release-tag ] 77 | runs-on: ubuntu-latest 78 | permissions: 79 | contents: write # Needed for goreleaser to create GitHub release 80 | issues: write # Needed for goreleaser to close associated milestone 81 | steps: 82 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 83 | with: 84 | ref: ${{ inputs.versionNumber }} 85 | fetch-depth: 0 86 | 87 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 88 | with: 89 | go-version-file: 'go.mod' 90 | 91 | - name: Generate Release Notes 92 | run: | 93 | cd .changes 94 | sed -e "1{/# /d;}" -e "2{/^$/d;}" ${{ needs.changelog-version.outputs.version }}.md > /tmp/release-notes.txt 95 | 96 | - uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 97 | env: 98 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 99 | with: 100 | args: release --release-notes /tmp/release-notes.txt --clean 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | website-preview -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - copyloopvar 6 | - durationcheck 7 | - errcheck 8 | - forcetypeassert 9 | - govet 10 | - ineffassign 11 | - makezero 12 | - misspell 13 | - nilerr 14 | - paralleltest 15 | - predeclared 16 | - staticcheck 17 | - unconvert 18 | - unparam 19 | - unused 20 | - usetesting 21 | exclusions: 22 | generated: lax 23 | presets: 24 | - comments 25 | - common-false-positives 26 | - legacy 27 | - std-error-handling 28 | paths: 29 | - third_party$ 30 | - builtin$ 31 | - examples$ 32 | issues: 33 | max-issues-per-linter: 0 34 | max-same-issues: 0 35 | formatters: 36 | enable: 37 | - gofmt 38 | exclusions: 39 | generated: lax 40 | paths: 41 | - third_party$ 42 | - builtin$ 43 | - examples$ 44 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | project_name: terraform-plugin-mux 3 | builds: 4 | - skip: true 5 | milestones: 6 | - close: true 7 | release: 8 | prerelease: auto 9 | ids: 10 | - 'none' 11 | -------------------------------------------------------------------------------- /META.d/_summary.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | schema: 1.1 3 | 4 | partition: tf-ecosystem 5 | 6 | summary: 7 | owner: team-tf-core-plugins 8 | description: | 9 | A router for Terraform's RPC protocol. 10 | visibility: public 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := website 2 | 3 | # Generate copywrite headers 4 | generate: 5 | cd tools; go generate ./... 6 | 7 | # Default: run this if working on the website locally to run in watch mode. 8 | .PHONY: website 9 | website: 10 | $(MAKE) -C website website 11 | 12 | # Use this if you have run `website/build-local` to use the locally built image. 13 | .PHONY: website/local 14 | website/local: 15 | $(MAKE) -C website website/local 16 | 17 | # Run this to generate a new local Docker image. 18 | .PHONY: website/build-local 19 | website/build-local: 20 | $(MAKE) -C website website/build-local -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/hashicorp/terraform-plugin-mux)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux) 2 | 3 | # terraform-plugin-mux 4 | 5 | terraform-plugin-mux provides a method for combining Terraform providers built 6 | in multiple different SDKs and frameworks to be combined into a single logical 7 | provider for Terraform to work with. It is designed to allow provider 8 | developers to implement resources and data sources at the level of abstraction 9 | that is most suitable for that specific resource or data source, and to allow 10 | provider developers to upgrade between SDKs or frameworks on a 11 | resource-by-resource basis instead of all at once. 12 | 13 | ## Status 14 | 15 | terraform-plugin-mux is a [Go 16 | module](https://github.com/golang/go/wiki/Modules) versioned using [semantic 17 | versioning](https://semver.org/). 18 | 19 | The module is currently on a v0 major version, indicating our lack of 20 | confidence in the stability of its exported API. Developers depending on it 21 | should do so with an explicit understanding that the API may change and shift 22 | until we hit v1.0.0, as we learn more about the needs and expectations of 23 | developers working with the module. 24 | 25 | We are confident in the correctness of the code and it is safe to build on so 26 | long as the developer understands that the API may change in backwards 27 | incompatible ways and they are expected to be tracking these changes. 28 | 29 | ## Compatibility 30 | 31 | Providers built on terraform-plugin-mux will only be usable with Terraform 32 | v0.12.0 and later. Developing providers for versions of Terraform below 0.12.0 33 | is unsupported by the Terraform Plugin SDK team. 34 | 35 | Providers built on the Terraform Plugin SDK must be using version 2.2.0 of the 36 | Plugin SDK or higher to be able to be used with terraform-plugin-mux. 37 | 38 | ## Go Compatibility 39 | 40 | This project follows the [support policy](https://golang.org/doc/devel/release.html#policy) of Go as its support policy. The two latest major releases of Go are supported by the project. 41 | 42 | Currently, that means Go **1.23** or later must be used when including this project as a dependency. 43 | 44 | ## Documentation 45 | 46 | - **Website Documentation**: Getting started, usage, and testing information: [terraform.io](https://terraform.io/plugin/mux). 47 | - **Go Documentation**: Go language types, functions, and method implementation information: [pkg.go.dev](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux). 48 | - **Documentation Requests**: Open a [GitHub issue](https://github.com/hashicorp/terraform-plugin-mux/issues/new/choose). 49 | 50 | ## Contributing 51 | 52 | Refer to [`.github/CONTRIBUTING.md`](https://github.com/hashicorp/terraform-plugin-mux/blob/master/.github/CONTRIBUTING.md). The [website directory README](/website/README.md) contains details about how to contribute to the documentation on terraform.io. 53 | 54 | ## License 55 | 56 | This module is licensed under the [Mozilla Public License v2.0](https://github.com/hashicorp/terraform-plugin-mux/blob/master/LICENSE). 57 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hashicorp/terraform-plugin-mux 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.7 6 | 7 | require ( 8 | github.com/google/go-cmp v0.7.0 9 | github.com/hashicorp/terraform-plugin-go v0.28.0 10 | github.com/hashicorp/terraform-plugin-log v0.9.0 11 | google.golang.org/grpc v1.72.1 12 | ) 13 | 14 | require ( 15 | github.com/fatih/color v1.13.0 // indirect 16 | github.com/golang/protobuf v1.5.4 // indirect 17 | github.com/hashicorp/go-hclog v1.5.0 // indirect 18 | github.com/hashicorp/go-plugin v1.6.3 // indirect 19 | github.com/hashicorp/go-uuid v1.0.3 // indirect 20 | github.com/hashicorp/terraform-registry-address v0.2.5 // indirect 21 | github.com/hashicorp/terraform-svchost v0.1.1 // indirect 22 | github.com/hashicorp/yamux v0.1.1 // indirect 23 | github.com/mattn/go-colorable v0.1.12 // indirect 24 | github.com/mattn/go-isatty v0.0.17 // indirect 25 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 26 | github.com/oklog/run v1.0.0 // indirect 27 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 28 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 29 | golang.org/x/net v0.39.0 // indirect 30 | golang.org/x/sys v0.32.0 // indirect 31 | golang.org/x/text v0.24.0 // indirect 32 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect 33 | google.golang.org/protobuf v1.36.6 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /internal/logging/context.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package logging 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 12 | "github.com/hashicorp/terraform-plugin-log/tflog" 13 | "github.com/hashicorp/terraform-plugin-log/tfsdklog" 14 | ) 15 | 16 | // InitContext creates SDK logger contexts. 17 | func InitContext(ctx context.Context) context.Context { 18 | ctx = tfsdklog.NewSubsystem(ctx, SubsystemMux, tfsdklog.WithLevelFromEnv(EnvTfLogSdkMux)) 19 | 20 | return ctx 21 | } 22 | 23 | // RpcContext injects the RPC name into logger contexts. 24 | func RpcContext(ctx context.Context, rpc string) context.Context { 25 | ctx = tflog.SetField(ctx, KeyTfRpc, rpc) 26 | ctx = tfsdklog.SetField(ctx, KeyTfRpc, rpc) 27 | ctx = tfsdklog.SubsystemSetField(ctx, SubsystemMux, KeyTfRpc, rpc) 28 | 29 | return ctx 30 | } 31 | 32 | // Tfprotov5ProviderServerContext injects the chosen provider Go type 33 | func Tfprotov5ProviderServerContext(ctx context.Context, p tfprotov5.ProviderServer) context.Context { 34 | providerType := fmt.Sprintf("%T", p) 35 | ctx = tflog.SetField(ctx, KeyTfMuxProvider, providerType) 36 | ctx = tfsdklog.SetField(ctx, KeyTfMuxProvider, providerType) 37 | ctx = tfsdklog.SubsystemSetField(ctx, SubsystemMux, KeyTfMuxProvider, providerType) 38 | 39 | return ctx 40 | } 41 | 42 | // Tfprotov6ProviderServerContext injects the chosen provider Go type 43 | func Tfprotov6ProviderServerContext(ctx context.Context, p tfprotov6.ProviderServer) context.Context { 44 | providerType := fmt.Sprintf("%T", p) 45 | ctx = tflog.SetField(ctx, KeyTfMuxProvider, providerType) 46 | ctx = tfsdklog.SetField(ctx, KeyTfMuxProvider, providerType) 47 | ctx = tfsdklog.SubsystemSetField(ctx, SubsystemMux, KeyTfMuxProvider, providerType) 48 | 49 | return ctx 50 | } 51 | -------------------------------------------------------------------------------- /internal/logging/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // Package logging contains shared environment variable and log functionality. 5 | package logging 6 | -------------------------------------------------------------------------------- /internal/logging/environment_variables.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package logging 5 | 6 | // Environment variables. 7 | const ( 8 | // EnvTfLogSdkMux is an environment variable that sets the logging level 9 | // of the mux logger. Infers root SDK logging level, if unset. 10 | EnvTfLogSdkMux = "TF_LOG_SDK_MUX" 11 | ) 12 | -------------------------------------------------------------------------------- /internal/logging/keys.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package logging 5 | 6 | // Global logging keys attached to all requests. 7 | // 8 | // Practitioners or tooling reading logs may be depending on these keys, so be 9 | // conscious of that when changing them. 10 | const ( 11 | // Go type of the provider selected by mux. 12 | KeyTfMuxProvider = "tf_mux_provider" 13 | 14 | // The RPC being run, such as "ApplyResourceChange" 15 | KeyTfRpc = "tf_rpc" 16 | ) 17 | -------------------------------------------------------------------------------- /internal/logging/mux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package logging 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-log/tfsdklog" 10 | ) 11 | 12 | const ( 13 | // SubsystemMux is the tfsdklog subsystem name for mux logging. 14 | SubsystemMux = "mux" 15 | ) 16 | 17 | // MuxTrace emits a mux subsystem log at TRACE level. 18 | func MuxTrace(ctx context.Context, msg string, additionalFields ...map[string]interface{}) { 19 | tfsdklog.SubsystemTrace(ctx, SubsystemMux, msg, additionalFields...) 20 | } 21 | -------------------------------------------------------------------------------- /internal/tf5dynamicvalue/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // Package tf5dynamicvalue contains shared *tfprotov5.DynamicValue functions. 5 | package tf5dynamicvalue 6 | -------------------------------------------------------------------------------- /internal/tf5dynamicvalue/must.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5dynamicvalue 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | "github.com/hashicorp/terraform-plugin-go/tftypes" 11 | ) 12 | 13 | // Must creates a *tfprotov5.DynamicValue or panics. This is intended only for 14 | // simplifying testing code. 15 | // 16 | // The tftypes.Type parameter is separate to enable DynamicPsuedoType testing. 17 | func Must(typ tftypes.Type, value tftypes.Value) *tfprotov5.DynamicValue { 18 | dynamicValue, err := tfprotov5.NewDynamicValue(typ, value) 19 | 20 | if err != nil { 21 | panic(fmt.Sprintf("unable to create DynamicValue: %s", err.Error())) 22 | } 23 | 24 | return &dynamicValue 25 | } 26 | -------------------------------------------------------------------------------- /internal/tf6dynamicvalue/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // Package tf6dynamicvalue contains shared *tfprotov6.DynamicValue functions. 5 | package tf6dynamicvalue 6 | -------------------------------------------------------------------------------- /internal/tf6dynamicvalue/must.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6dynamicvalue 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-go/tftypes" 11 | ) 12 | 13 | // Must creates a *tfprotov6.DynamicValue or panics. This is intended only for 14 | // simplifying testing code. 15 | // 16 | // The tftypes.Type parameter is separate to enable DynamicPsuedoType testing. 17 | func Must(typ tftypes.Type, value tftypes.Value) *tfprotov6.DynamicValue { 18 | dynamicValue, err := tfprotov6.NewDynamicValue(typ, value) 19 | 20 | if err != nil { 21 | panic(fmt.Sprintf("unable to create DynamicValue: %s", err.Error())) 22 | } 23 | 24 | return &dynamicValue 25 | } 26 | -------------------------------------------------------------------------------- /tf5muxserver/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // Package tf5muxserver combines multiple provider servers that implement protocol version 5, into a single server. 5 | // 6 | // Supported protocol version 5 provider servers include any which implement 7 | // the tfprotov5.ProviderServer (https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov5#ProviderServer) 8 | // interface, such as: 9 | // 10 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server 11 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf6to5server 12 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema 13 | // 14 | // Refer to the NewMuxServer() function for creating a combined server. 15 | package tf5muxserver 16 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ApplyResourceChange.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ApplyResourceChange calls the ApplyResourceChange method, passing `req`, on 14 | // the provider that returned the resource specified by req.TypeName in its 15 | // schema. 16 | func (s *muxServer) ApplyResourceChange(ctx context.Context, req *tfprotov5.ApplyResourceChangeRequest) (*tfprotov5.ApplyResourceChangeResponse, error) { 17 | rpc := "ApplyResourceChange" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov5.ApplyResourceChangeResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.ApplyResourceChange(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ApplyResourceChange_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerApplyResourceChange(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().ApplyResourceChange(ctx, &tfprotov5.ApplyResourceChangeRequest{ 43 | TypeName: "test_resource_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.ApplyResourceChangeCalled["test_resource_server1"] { 51 | t.Errorf("expected test_resource_server1 ApplyResourceChange to be called on server1") 52 | } 53 | 54 | if testServer2.ApplyResourceChangeCalled["test_resource_server1"] { 55 | t.Errorf("unexpected test_resource_server1 ApplyResourceChange called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().ApplyResourceChange(ctx, &tfprotov5.ApplyResourceChangeRequest{ 59 | TypeName: "test_resource_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.ApplyResourceChangeCalled["test_resource_server2"] { 67 | t.Errorf("unexpected test_resource_server2 ApplyResourceChange called on server1") 68 | } 69 | 70 | if !testServer2.ApplyResourceChangeCalled["test_resource_server2"] { 71 | t.Errorf("expected test_resource_server2 ApplyResourceChange to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_CallFunction.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 13 | ) 14 | 15 | // CallFunction calls the CallFunction method of the underlying provider 16 | // serving the function. 17 | func (s *muxServer) CallFunction(ctx context.Context, req *tfprotov5.CallFunctionRequest) (*tfprotov5.CallFunctionResponse, error) { 18 | rpc := "CallFunction" 19 | ctx = logging.InitContext(ctx) 20 | ctx = logging.RpcContext(ctx, rpc) 21 | 22 | server, diags, err := s.getFunctionServer(ctx, req.Name) 23 | 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | if diagnosticsHasError(diags) { 29 | var text string 30 | 31 | for _, d := range diags { 32 | if d.Severity == tfprotov5.DiagnosticSeverityError { 33 | if text != "" { 34 | text += "\n" 35 | } 36 | 37 | text += fmt.Sprintf("%s: %s", d.Summary, d.Detail) 38 | } 39 | } 40 | 41 | return &tfprotov5.CallFunctionResponse{ 42 | Error: &tfprotov5.FunctionError{ 43 | Text: text, 44 | }, 45 | }, nil 46 | } 47 | 48 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 49 | logging.MuxTrace(ctx, "calling downstream server") 50 | 51 | return server.CallFunction(ctx, req) 52 | } 53 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_CallFunction_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerCallFunction(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | Functions: map[string]*tfprotov5.Function{ 23 | "test_function1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | Functions: map[string]*tfprotov5.Function{ 30 | "test_function2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().CallFunction(ctx, &tfprotov5.CallFunctionRequest{ 43 | Name: "test_function1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.CallFunctionCalled["test_function1"] { 51 | t.Errorf("expected test_function1 CallFunction to be called on server1") 52 | } 53 | 54 | if testServer2.CallFunctionCalled["test_function1"] { 55 | t.Errorf("unexpected test_function1 CallFunction called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().CallFunction(ctx, &tfprotov5.CallFunctionRequest{ 59 | Name: "test_function2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.CallFunctionCalled["test_function2"] { 67 | t.Errorf("unexpected test_function2 CallFunction called on server1") 68 | } 69 | 70 | if !testServer2.CallFunctionCalled["test_function2"] { 71 | t.Errorf("expected test_function2 CallFunction to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_CloseEphemeralResource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | func (s *muxServer) CloseEphemeralResource(ctx context.Context, req *tfprotov5.CloseEphemeralResourceRequest) (*tfprotov5.CloseEphemeralResourceResponse, error) { 15 | rpc := "CloseEphemeralResource" 16 | ctx = logging.InitContext(ctx) 17 | ctx = logging.RpcContext(ctx, rpc) 18 | 19 | server, diags, err := s.getEphemeralResourceServer(ctx, req.TypeName) 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | if diagnosticsHasError(diags) { 26 | return &tfprotov5.CloseEphemeralResourceResponse{ 27 | Diagnostics: diags, 28 | }, nil 29 | } 30 | 31 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 32 | logging.MuxTrace(ctx, "calling downstream server") 33 | 34 | return server.CloseEphemeralResource(ctx, req) 35 | } 36 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_CloseEphemeralResource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerCloseEphemeralResource(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_ephemeral_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_ephemeral_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 35 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 36 | 37 | if err != nil { 38 | t.Fatalf("unexpected error setting up factory: %s", err) 39 | } 40 | 41 | _, err = muxServer.ProviderServer().CloseEphemeralResource(ctx, &tfprotov5.CloseEphemeralResourceRequest{ 42 | TypeName: "test_ephemeral_resource_server1", 43 | }) 44 | 45 | if err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | 49 | if !testServer1.CloseEphemeralResourceCalled["test_ephemeral_resource_server1"] { 50 | t.Errorf("expected test_ephemeral_resource_server1 CloseEphemeralResource to be called on server1") 51 | } 52 | 53 | if testServer2.CloseEphemeralResourceCalled["test_ephemeral_resource_server1"] { 54 | t.Errorf("unexpected test_ephemeral_resource_server1 CloseEphemeralResource called on server2") 55 | } 56 | 57 | _, err = muxServer.ProviderServer().CloseEphemeralResource(ctx, &tfprotov5.CloseEphemeralResourceRequest{ 58 | TypeName: "test_ephemeral_resource_server2", 59 | }) 60 | 61 | if err != nil { 62 | t.Fatalf("unexpected error: %s", err) 63 | } 64 | 65 | if testServer1.CloseEphemeralResourceCalled["test_ephemeral_resource_server2"] { 66 | t.Errorf("unexpected test_ephemeral_resource_server2 CloseEphemeralResource called on server1") 67 | } 68 | 69 | if !testServer2.CloseEphemeralResourceCalled["test_ephemeral_resource_server2"] { 70 | t.Errorf("expected test_ephemeral_resource_server2 CloseEphemeralResource to be called on server2") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ConfigureProvider.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | // ConfigureProvider calls each provider's ConfigureProvider method, one at a 15 | // time, passing `req`. Any Diagnostic with severity error will abort the 16 | // process and return immediately; non-Error severity Diagnostics will be 17 | // combined and returned. 18 | func (s *muxServer) ConfigureProvider(ctx context.Context, req *tfprotov5.ConfigureProviderRequest) (*tfprotov5.ConfigureProviderResponse, error) { 19 | rpc := "ConfigureProvider" 20 | ctx = logging.InitContext(ctx) 21 | ctx = logging.RpcContext(ctx, rpc) 22 | var diags []*tfprotov5.Diagnostic 23 | 24 | for _, server := range s.servers { 25 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 26 | logging.MuxTrace(ctx, "calling downstream server") 27 | 28 | resp, err := server.ConfigureProvider(ctx, req) 29 | 30 | if err != nil { 31 | return resp, fmt.Errorf("error configuring %T: %w", server, err) 32 | } 33 | 34 | for _, diag := range resp.Diagnostics { 35 | if diag == nil { 36 | continue 37 | } 38 | 39 | diags = append(diags, diag) 40 | 41 | if diag.Severity != tfprotov5.DiagnosticSeverityError { 42 | continue 43 | } 44 | 45 | resp.Diagnostics = diags 46 | 47 | return resp, err 48 | } 49 | } 50 | 51 | return &tfprotov5.ConfigureProviderResponse{Diagnostics: diags}, nil 52 | } 53 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ConfigureProvider_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/google/go-cmp/cmp" 11 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 12 | 13 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 14 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 15 | ) 16 | 17 | func TestMuxServerConfigureProvider(t *testing.T) { 18 | t.Parallel() 19 | 20 | ctx := context.Background() 21 | testServers := [5]*tf5testserver.TestServer{ 22 | {}, 23 | { 24 | ConfigureProviderResponse: &tfprotov5.ConfigureProviderResponse{ 25 | Diagnostics: []*tfprotov5.Diagnostic{ 26 | { 27 | Severity: tfprotov5.DiagnosticSeverityWarning, 28 | Summary: "warning summary", 29 | Detail: "warning detail", 30 | }, 31 | }, 32 | }, 33 | }, 34 | {}, 35 | { 36 | ConfigureProviderResponse: &tfprotov5.ConfigureProviderResponse{ 37 | Diagnostics: []*tfprotov5.Diagnostic{ 38 | { 39 | Severity: tfprotov5.DiagnosticSeverityError, 40 | Summary: "error summary", 41 | Detail: "error detail", 42 | }, 43 | }, 44 | }, 45 | }, 46 | { 47 | ConfigureProviderResponse: &tfprotov5.ConfigureProviderResponse{ 48 | Diagnostics: []*tfprotov5.Diagnostic{ 49 | { 50 | Severity: tfprotov5.DiagnosticSeverityError, 51 | Summary: "unexpected error summary", 52 | Detail: "unexpected error detail", 53 | }, 54 | }, 55 | }, 56 | }, 57 | } 58 | 59 | servers := []func() tfprotov5.ProviderServer{ 60 | testServers[0].ProviderServer, 61 | testServers[1].ProviderServer, 62 | testServers[2].ProviderServer, 63 | testServers[3].ProviderServer, 64 | testServers[4].ProviderServer, 65 | } 66 | 67 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 68 | 69 | if err != nil { 70 | t.Fatalf("error setting up muxer: %s", err) 71 | } 72 | 73 | resp, err := muxServer.ProviderServer().ConfigureProvider(ctx, &tfprotov5.ConfigureProviderRequest{}) 74 | 75 | if err != nil { 76 | t.Fatalf("error calling ConfigureProvider: %s", err) 77 | } 78 | 79 | expectedResp := &tfprotov5.ConfigureProviderResponse{ 80 | Diagnostics: []*tfprotov5.Diagnostic{ 81 | { 82 | Severity: tfprotov5.DiagnosticSeverityWarning, 83 | Summary: "warning summary", 84 | Detail: "warning detail", 85 | }, 86 | { 87 | Severity: tfprotov5.DiagnosticSeverityError, 88 | Summary: "error summary", 89 | Detail: "error detail", 90 | }, 91 | }, 92 | } 93 | 94 | if diff := cmp.Diff(resp, expectedResp); diff != "" { 95 | t.Errorf("unexpected difference: %s", diff) 96 | } 97 | 98 | for num, testServer := range testServers { 99 | if num < 4 && !testServer.ConfigureProviderCalled { 100 | t.Errorf("configure not called on server%d", num+1) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_GetFunctions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 13 | ) 14 | 15 | // GetFunctions merges the functions returned by the tfprotov5.ProviderServers 16 | // associated with muxServer into a single response. Functions must be returned 17 | // from only one server or an error diagnostic is returned. 18 | func (s *muxServer) GetFunctions(ctx context.Context, req *tfprotov5.GetFunctionsRequest) (*tfprotov5.GetFunctionsResponse, error) { 19 | rpc := "GetFunctions" 20 | ctx = logging.InitContext(ctx) 21 | ctx = logging.RpcContext(ctx, rpc) 22 | 23 | s.serverDiscoveryMutex.Lock() 24 | defer s.serverDiscoveryMutex.Unlock() 25 | 26 | resp := &tfprotov5.GetFunctionsResponse{ 27 | Functions: make(map[string]*tfprotov5.Function), 28 | } 29 | 30 | for _, server := range s.servers { 31 | ctx := logging.Tfprotov5ProviderServerContext(ctx, server) 32 | 33 | logging.MuxTrace(ctx, "calling downstream server") 34 | 35 | serverResp, err := server.GetFunctions(ctx, &tfprotov5.GetFunctionsRequest{}) 36 | 37 | if err != nil { 38 | return resp, fmt.Errorf("error calling GetFunctions for %T: %w", server, err) 39 | } 40 | 41 | resp.Diagnostics = append(resp.Diagnostics, serverResp.Diagnostics...) 42 | 43 | for name, definition := range serverResp.Functions { 44 | if _, ok := resp.Functions[name]; ok { 45 | resp.Diagnostics = append(resp.Diagnostics, functionDuplicateError(name)) 46 | 47 | continue 48 | } 49 | 50 | s.functions[name] = server 51 | resp.Functions[name] = definition 52 | } 53 | } 54 | 55 | // Intentionally not setting overall server discovery as complete, as data 56 | // sources and resources are not discovered via this RPC. 57 | 58 | return resp, nil 59 | } 60 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_GetResourceIdentitySchemas.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 13 | ) 14 | 15 | // GetResourceIdentitySchemas merges the schemas returned by the 16 | // tfprotov5.ResourceIdentitySchema associated with muxServer into a single schema. 17 | // Everything must be returned from only one server. 18 | // Schemas must be identical between all servers. 19 | func (s *muxServer) GetResourceIdentitySchemas(ctx context.Context, req *tfprotov5.GetResourceIdentitySchemasRequest) (*tfprotov5.GetResourceIdentitySchemasResponse, error) { 20 | rpc := "GetResourceIdentitySchemas" 21 | ctx = logging.InitContext(ctx) 22 | ctx = logging.RpcContext(ctx, rpc) 23 | 24 | resp := &tfprotov5.GetResourceIdentitySchemasResponse{ 25 | IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{}, 26 | Diagnostics: []*tfprotov5.Diagnostic{}, 27 | } 28 | 29 | for _, server := range s.servers { 30 | ctx := logging.Tfprotov5ProviderServerContext(ctx, server) 31 | logging.MuxTrace(ctx, "calling downstream server") 32 | 33 | resourceIdentitySchemas, err := server.GetResourceIdentitySchemas(ctx, req) 34 | 35 | if err != nil { 36 | return resp, fmt.Errorf("error calling GetResourceIdentitySchemas for %T: %w", server, err) 37 | } 38 | 39 | resp.Diagnostics = append(resp.Diagnostics, resourceIdentitySchemas.Diagnostics...) 40 | 41 | for resourceIdentityType, schema := range resourceIdentitySchemas.IdentitySchemas { 42 | if _, ok := resp.IdentitySchemas[resourceIdentityType]; ok { 43 | resp.Diagnostics = append(resp.Diagnostics, resourceIdentityDuplicateError(resourceIdentityType)) 44 | 45 | continue 46 | } 47 | 48 | resp.IdentitySchemas[resourceIdentityType] = schema 49 | } 50 | } 51 | 52 | return resp, nil 53 | } 54 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ImportResourceState.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ImportResourceState calls the ImportResourceState method, passing `req`, on 14 | // the provider that returned the resource specified by req.TypeName in its 15 | // schema. 16 | func (s *muxServer) ImportResourceState(ctx context.Context, req *tfprotov5.ImportResourceStateRequest) (*tfprotov5.ImportResourceStateResponse, error) { 17 | rpc := "ImportResourceState" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov5.ImportResourceStateResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.ImportResourceState(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ImportResourceState_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerImportResourceState(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().ImportResourceState(ctx, &tfprotov5.ImportResourceStateRequest{ 43 | TypeName: "test_resource_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.ImportResourceStateCalled["test_resource_server1"] { 51 | t.Errorf("expected test_resource_server1 ImportResourceState to be called on server1") 52 | } 53 | 54 | if testServer2.ImportResourceStateCalled["test_resource_server1"] { 55 | t.Errorf("unexpected test_resource_server1 ImportResourceState called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().ImportResourceState(ctx, &tfprotov5.ImportResourceStateRequest{ 59 | TypeName: "test_resource_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.ImportResourceStateCalled["test_resource_server2"] { 67 | t.Errorf("unexpected test_resource_server2 ImportResourceState called on server1") 68 | } 69 | 70 | if !testServer2.ImportResourceStateCalled["test_resource_server2"] { 71 | t.Errorf("expected test_resource_server2 ImportResourceState to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_MoveResourceState.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // MoveResourceState calls the MoveResourceState method of the underlying 14 | // provider serving the resource. 15 | func (s *muxServer) MoveResourceState(ctx context.Context, req *tfprotov5.MoveResourceStateRequest) (*tfprotov5.MoveResourceStateResponse, error) { 16 | rpc := "MoveResourceState" 17 | ctx = logging.InitContext(ctx) 18 | ctx = logging.RpcContext(ctx, rpc) 19 | 20 | server, diags, err := s.getResourceServer(ctx, req.TargetTypeName) 21 | 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | if diagnosticsHasError(diags) { 27 | return &tfprotov5.MoveResourceStateResponse{ 28 | Diagnostics: diags, 29 | }, nil 30 | } 31 | 32 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 33 | logging.MuxTrace(ctx, "calling downstream server") 34 | 35 | return server.MoveResourceState(ctx, req) 36 | } 37 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_MoveResourceState_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerMoveResourceState(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_resource1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_resource2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().MoveResourceState(ctx, &tfprotov5.MoveResourceStateRequest{ 43 | TargetTypeName: "test_resource1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.MoveResourceStateCalled["test_resource1"] { 51 | t.Errorf("expected test_resource1 MoveResourceState to be called on server1") 52 | } 53 | 54 | if testServer2.MoveResourceStateCalled["test_resource1"] { 55 | t.Errorf("unexpected test_resource1 MoveResourceState called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().MoveResourceState(ctx, &tfprotov5.MoveResourceStateRequest{ 59 | TargetTypeName: "test_resource2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.MoveResourceStateCalled["test_resource2"] { 67 | t.Errorf("unexpected test_resource2 MoveResourceState called on server1") 68 | } 69 | 70 | if !testServer2.MoveResourceStateCalled["test_resource2"] { 71 | t.Errorf("expected test_resource2 MoveResourceState to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_OpenEphemeralResource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | func (s *muxServer) OpenEphemeralResource(ctx context.Context, req *tfprotov5.OpenEphemeralResourceRequest) (*tfprotov5.OpenEphemeralResourceResponse, error) { 15 | rpc := "OpenEphemeralResource" 16 | ctx = logging.InitContext(ctx) 17 | ctx = logging.RpcContext(ctx, rpc) 18 | 19 | server, diags, err := s.getEphemeralResourceServer(ctx, req.TypeName) 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | if diagnosticsHasError(diags) { 26 | return &tfprotov5.OpenEphemeralResourceResponse{ 27 | Diagnostics: diags, 28 | }, nil 29 | } 30 | 31 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 32 | logging.MuxTrace(ctx, "calling downstream server") 33 | 34 | return server.OpenEphemeralResource(ctx, req) 35 | } 36 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_OpenEphemeralResource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerOpenEphemeralResource(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_ephemeral_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_ephemeral_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 35 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 36 | 37 | if err != nil { 38 | t.Fatalf("unexpected error setting up factory: %s", err) 39 | } 40 | 41 | _, err = muxServer.ProviderServer().OpenEphemeralResource(ctx, &tfprotov5.OpenEphemeralResourceRequest{ 42 | TypeName: "test_ephemeral_resource_server1", 43 | }) 44 | 45 | if err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | 49 | if !testServer1.OpenEphemeralResourceCalled["test_ephemeral_resource_server1"] { 50 | t.Errorf("expected test_ephemeral_resource_server1 OpenEphemeralResource to be called on server1") 51 | } 52 | 53 | if testServer2.OpenEphemeralResourceCalled["test_ephemeral_resource_server1"] { 54 | t.Errorf("unexpected test_ephemeral_resource_server1 OpenEphemeralResource called on server2") 55 | } 56 | 57 | _, err = muxServer.ProviderServer().OpenEphemeralResource(ctx, &tfprotov5.OpenEphemeralResourceRequest{ 58 | TypeName: "test_ephemeral_resource_server2", 59 | }) 60 | 61 | if err != nil { 62 | t.Fatalf("unexpected error: %s", err) 63 | } 64 | 65 | if testServer1.OpenEphemeralResourceCalled["test_ephemeral_resource_server2"] { 66 | t.Errorf("unexpected test_ephemeral_resource_server2 OpenEphemeralResource called on server1") 67 | } 68 | 69 | if !testServer2.OpenEphemeralResourceCalled["test_ephemeral_resource_server2"] { 70 | t.Errorf("expected test_ephemeral_resource_server2 OpenEphemeralResource to be called on server2") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_PlanResourceChange.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | // PlanResourceChange calls the PlanResourceChange method, passing `req`, on 15 | // the provider that returned the resource specified by req.TypeName in its 16 | // schema. 17 | func (s *muxServer) PlanResourceChange(ctx context.Context, req *tfprotov5.PlanResourceChangeRequest) (*tfprotov5.PlanResourceChangeResponse, error) { 18 | rpc := "PlanResourceChange" 19 | ctx = logging.InitContext(ctx) 20 | ctx = logging.RpcContext(ctx, rpc) 21 | 22 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 23 | 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | if diagnosticsHasError(diags) { 29 | return &tfprotov5.PlanResourceChangeResponse{ 30 | Diagnostics: diags, 31 | }, nil 32 | } 33 | 34 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 35 | 36 | // Prevent ServerCapabilities.PlanDestroy from sending destroy plans to 37 | // servers which do not enable the capability. 38 | if !serverSupportsPlanDestroy(s.resourceCapabilities[req.TypeName]) { 39 | if req.ProposedNewState == nil { 40 | logging.MuxTrace(ctx, "server does not enable destroy plans, returning without calling downstream server") 41 | 42 | resp := &tfprotov5.PlanResourceChangeResponse{ 43 | // Presumably, we must preserve any prior private state so it 44 | // is still available during ApplyResourceChange. 45 | PlannedPrivate: req.PriorPrivate, 46 | } 47 | 48 | return resp, nil 49 | } 50 | 51 | isDestroyPlan, err := req.ProposedNewState.IsNull() 52 | 53 | if err != nil { 54 | return nil, fmt.Errorf("unable to determine if request is destroy plan: %w", err) 55 | } 56 | 57 | if isDestroyPlan { 58 | logging.MuxTrace(ctx, "server does not enable destroy plans, returning without calling downstream server") 59 | 60 | resp := &tfprotov5.PlanResourceChangeResponse{ 61 | // Presumably, we must preserve any prior private state so it 62 | // is still available during ApplyResourceChange. 63 | PlannedPrivate: req.PriorPrivate, 64 | PlannedState: req.ProposedNewState, 65 | } 66 | 67 | return resp, nil 68 | } 69 | } 70 | 71 | logging.MuxTrace(ctx, "calling downstream server") 72 | 73 | return server.PlanResourceChange(ctx, req) 74 | } 75 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_PrepareProviderConfig.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | // PrepareProviderConfig calls the PrepareProviderConfig method on each server 15 | // in order, passing `req`. Response diagnostics are appended from all servers. 16 | // Response PreparedConfig must be equal across all servers with nil values 17 | // skipped. 18 | func (s *muxServer) PrepareProviderConfig(ctx context.Context, req *tfprotov5.PrepareProviderConfigRequest) (*tfprotov5.PrepareProviderConfigResponse, error) { 19 | rpc := "PrepareProviderConfig" 20 | ctx = logging.InitContext(ctx) 21 | ctx = logging.RpcContext(ctx, rpc) 22 | 23 | resp := &tfprotov5.PrepareProviderConfigResponse{ 24 | PreparedConfig: req.Config, // ignored by Terraform anyways 25 | } 26 | 27 | for _, server := range s.servers { 28 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 29 | logging.MuxTrace(ctx, "calling downstream server") 30 | 31 | res, err := server.PrepareProviderConfig(ctx, req) 32 | 33 | if err != nil { 34 | return resp, fmt.Errorf("error from %T validating provider config: %w", server, err) 35 | } 36 | 37 | if res == nil { 38 | continue 39 | } 40 | 41 | resp.Diagnostics = append(resp.Diagnostics, res.Diagnostics...) 42 | } 43 | 44 | return resp, nil 45 | } 46 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ReadDataSource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ReadDataSource calls the ReadDataSource method, passing `req`, on the 14 | // provider that returned the data source specified by req.TypeName in its 15 | // schema. 16 | func (s *muxServer) ReadDataSource(ctx context.Context, req *tfprotov5.ReadDataSourceRequest) (*tfprotov5.ReadDataSourceResponse, error) { 17 | rpc := "ReadDataSource" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getDataSourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov5.ReadDataSourceResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.ReadDataSource(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ReadDataSource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerReadDataSource(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | DataSourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_data_source_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | DataSourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_data_source_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().ReadDataSource(ctx, &tfprotov5.ReadDataSourceRequest{ 43 | TypeName: "test_data_source_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.ReadDataSourceCalled["test_data_source_server1"] { 51 | t.Errorf("expected test_data_source_server1 ReadDataSource to be called on server1") 52 | } 53 | 54 | if testServer2.ReadDataSourceCalled["test_data_source_server1"] { 55 | t.Errorf("unexpected test_data_source_server1 ReadDataSource called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().ReadDataSource(ctx, &tfprotov5.ReadDataSourceRequest{ 59 | TypeName: "test_data_source_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.ReadDataSourceCalled["test_data_source_server2"] { 67 | t.Errorf("unexpected test_data_source_server2 ReadDataSource called on server1") 68 | } 69 | 70 | if !testServer2.ReadDataSourceCalled["test_data_source_server2"] { 71 | t.Errorf("expected test_data_source_server2 ReadDataSource to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ReadResource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ReadResource calls the ReadResource method, passing `req`, on the provider 14 | // that returned the resource specified by req.TypeName in its schema. 15 | func (s *muxServer) ReadResource(ctx context.Context, req *tfprotov5.ReadResourceRequest) (*tfprotov5.ReadResourceResponse, error) { 16 | rpc := "ReadResource" 17 | ctx = logging.InitContext(ctx) 18 | ctx = logging.RpcContext(ctx, rpc) 19 | 20 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 21 | 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | if diagnosticsHasError(diags) { 27 | return &tfprotov5.ReadResourceResponse{ 28 | Diagnostics: diags, 29 | }, nil 30 | } 31 | 32 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 33 | logging.MuxTrace(ctx, "calling downstream server") 34 | 35 | return server.ReadResource(ctx, req) 36 | } 37 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ReadResource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerReadResource(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().ReadResource(ctx, &tfprotov5.ReadResourceRequest{ 43 | TypeName: "test_resource_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.ReadResourceCalled["test_resource_server1"] { 51 | t.Errorf("expected test_resource_server1 ReadResource to be called on server1") 52 | } 53 | 54 | if testServer2.ReadResourceCalled["test_resource_server1"] { 55 | t.Errorf("unexpected test_resource_server1 ReadResource called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().ReadResource(ctx, &tfprotov5.ReadResourceRequest{ 59 | TypeName: "test_resource_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.ReadResourceCalled["test_resource_server2"] { 67 | t.Errorf("unexpected test_resource_server2 ReadResource called on server1") 68 | } 69 | 70 | if !testServer2.ReadResourceCalled["test_resource_server2"] { 71 | t.Errorf("expected test_resource_server2 ReadResource to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_RenewEphemeralResource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | func (s *muxServer) RenewEphemeralResource(ctx context.Context, req *tfprotov5.RenewEphemeralResourceRequest) (*tfprotov5.RenewEphemeralResourceResponse, error) { 15 | rpc := "RenewEphemeralResource" 16 | ctx = logging.InitContext(ctx) 17 | ctx = logging.RpcContext(ctx, rpc) 18 | 19 | server, diags, err := s.getEphemeralResourceServer(ctx, req.TypeName) 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | if diagnosticsHasError(diags) { 26 | return &tfprotov5.RenewEphemeralResourceResponse{ 27 | Diagnostics: diags, 28 | }, nil 29 | } 30 | 31 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 32 | logging.MuxTrace(ctx, "calling downstream server") 33 | 34 | return server.RenewEphemeralResource(ctx, req) 35 | } 36 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_RenewEphemeralResource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerRenewEphemeralResource(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_ephemeral_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_ephemeral_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 35 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 36 | 37 | if err != nil { 38 | t.Fatalf("unexpected error setting up factory: %s", err) 39 | } 40 | 41 | _, err = muxServer.ProviderServer().RenewEphemeralResource(ctx, &tfprotov5.RenewEphemeralResourceRequest{ 42 | TypeName: "test_ephemeral_resource_server1", 43 | }) 44 | 45 | if err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | 49 | if !testServer1.RenewEphemeralResourceCalled["test_ephemeral_resource_server1"] { 50 | t.Errorf("expected test_ephemeral_resource_server1 RenewEphemeralResource to be called on server1") 51 | } 52 | 53 | if testServer2.RenewEphemeralResourceCalled["test_ephemeral_resource_server1"] { 54 | t.Errorf("unexpected test_ephemeral_resource_server1 RenewEphemeralResource called on server2") 55 | } 56 | 57 | _, err = muxServer.ProviderServer().RenewEphemeralResource(ctx, &tfprotov5.RenewEphemeralResourceRequest{ 58 | TypeName: "test_ephemeral_resource_server2", 59 | }) 60 | 61 | if err != nil { 62 | t.Fatalf("unexpected error: %s", err) 63 | } 64 | 65 | if testServer1.RenewEphemeralResourceCalled["test_ephemeral_resource_server2"] { 66 | t.Errorf("unexpected test_ephemeral_resource_server2 RenewEphemeralResource called on server1") 67 | } 68 | 69 | if !testServer2.RenewEphemeralResourceCalled["test_ephemeral_resource_server2"] { 70 | t.Errorf("expected test_ephemeral_resource_server2 RenewEphemeralResource to be called on server2") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_StopProvider.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "strings" 10 | 11 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 12 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 13 | ) 14 | 15 | // StopProvider calls the StopProvider function for each provider associated 16 | // with the muxServer, one at a time. All Error fields will be joined 17 | // together and returned, but will not prevent the rest of the providers' 18 | // StopProvider methods from being called. 19 | func (s *muxServer) StopProvider(ctx context.Context, req *tfprotov5.StopProviderRequest) (*tfprotov5.StopProviderResponse, error) { 20 | rpc := "StopProvider" 21 | ctx = logging.InitContext(ctx) 22 | ctx = logging.RpcContext(ctx, rpc) 23 | var errs []string 24 | 25 | for _, server := range s.servers { 26 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 27 | logging.MuxTrace(ctx, "calling downstream server") 28 | 29 | resp, err := server.StopProvider(ctx, req) 30 | 31 | if err != nil { 32 | return resp, fmt.Errorf("error stopping %T: %w", server, err) 33 | } 34 | 35 | if resp.Error != "" { 36 | errs = append(errs, resp.Error) 37 | } 38 | } 39 | 40 | return &tfprotov5.StopProviderResponse{ 41 | Error: strings.Join(errs, "\n"), 42 | }, nil 43 | } 44 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_StopProvider_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/google/go-cmp/cmp" 11 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 12 | 13 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 14 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 15 | ) 16 | 17 | func TestMuxServerStopProvider(t *testing.T) { 18 | t.Parallel() 19 | 20 | ctx := context.Background() 21 | testServers := [5]*tf5testserver.TestServer{ 22 | {}, 23 | { 24 | StopProviderResponse: &tfprotov5.StopProviderResponse{ 25 | Error: "error in server2", 26 | }, 27 | }, 28 | {}, 29 | { 30 | StopProviderResponse: &tfprotov5.StopProviderResponse{ 31 | Error: "error in server4", 32 | }, 33 | }, 34 | {}, 35 | } 36 | 37 | servers := []func() tfprotov5.ProviderServer{ 38 | testServers[0].ProviderServer, 39 | testServers[1].ProviderServer, 40 | testServers[2].ProviderServer, 41 | testServers[3].ProviderServer, 42 | testServers[4].ProviderServer, 43 | } 44 | 45 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 46 | 47 | if err != nil { 48 | t.Fatalf("error setting up muxer: %s", err) 49 | } 50 | 51 | resp, err := muxServer.ProviderServer().StopProvider(ctx, &tfprotov5.StopProviderRequest{}) 52 | 53 | if err != nil { 54 | t.Fatalf("error calling StopProvider: %s", err) 55 | } 56 | 57 | expectedResp := &tfprotov5.StopProviderResponse{ 58 | Error: "error in server2\nerror in server4", 59 | } 60 | 61 | if diff := cmp.Diff(resp, expectedResp); diff != "" { 62 | t.Errorf("unexpected response Error difference: %s", diff) 63 | } 64 | 65 | for num, testServer := range testServers { 66 | if !testServer.StopProviderCalled { 67 | t.Errorf("StopProvider not called on server%d", num+1) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_UpgradeResourceIdentity.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // UpgradeResourceState calls the UpgradeResourceState method, passing `req`, 14 | // on the provider that returned the resource specified by req.TypeName in its 15 | // schema. 16 | func (s *muxServer) UpgradeResourceIdentity(ctx context.Context, req *tfprotov5.UpgradeResourceIdentityRequest) (*tfprotov5.UpgradeResourceIdentityResponse, error) { 17 | rpc := "UpgradeResourceIdentity" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov5.UpgradeResourceIdentityResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.UpgradeResourceIdentity(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_UpgradeResourceIdentity_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerUpgradeResourceIdentity(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ 22 | IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 27 | ResourceSchemas: map[string]*tfprotov5.Schema{ 28 | "test_resource_server1": {}, 29 | }, 30 | }, 31 | } 32 | testServer2 := &tf5testserver.TestServer{ 33 | GetResourceIdentitySchemasResponse: &tfprotov5.GetResourceIdentitySchemasResponse{ 34 | IdentitySchemas: map[string]*tfprotov5.ResourceIdentitySchema{ 35 | "test_resource_server2": {}, 36 | }, 37 | }, 38 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 39 | ResourceSchemas: map[string]*tfprotov5.Schema{ 40 | "test_resource_server2": {}, 41 | }, 42 | }, 43 | } 44 | 45 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 46 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 47 | 48 | if err != nil { 49 | t.Fatalf("unexpected error setting up factory: %s", err) 50 | } 51 | 52 | _, err = muxServer.ProviderServer().UpgradeResourceIdentity(ctx, &tfprotov5.UpgradeResourceIdentityRequest{ 53 | TypeName: "test_resource_server1", 54 | }) 55 | 56 | if err != nil { 57 | t.Fatalf("unexpected error: %s", err) 58 | } 59 | 60 | if !testServer1.UpgradeResourceIdentityCalled["test_resource_server1"] { 61 | t.Errorf("expected test_resource_server1 UpgradeResourceIdentity to be called on server1") 62 | } 63 | 64 | if testServer2.UpgradeResourceIdentityCalled["test_resource_server1"] { 65 | t.Errorf("unexpected test_resource_server1 UpgradeResourceIdentity called on server2") 66 | } 67 | 68 | _, err = muxServer.ProviderServer().UpgradeResourceIdentity(ctx, &tfprotov5.UpgradeResourceIdentityRequest{ 69 | TypeName: "test_resource_server2", 70 | }) 71 | 72 | if err != nil { 73 | t.Fatalf("unexpected error: %s", err) 74 | } 75 | 76 | if testServer1.UpgradeResourceIdentityCalled["test_resource_server2"] { 77 | t.Errorf("unexpected test_resource_server2 UpgradeResourceIdentity called on server1") 78 | } 79 | 80 | if !testServer2.UpgradeResourceIdentityCalled["test_resource_server2"] { 81 | t.Errorf("expected test_resource_server2 UpgradeResourceIdentity to be called on server2") 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_UpgradeResourceState.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // UpgradeResourceState calls the UpgradeResourceState method, passing `req`, 14 | // on the provider that returned the resource specified by req.TypeName in its 15 | // schema. 16 | func (s *muxServer) UpgradeResourceState(ctx context.Context, req *tfprotov5.UpgradeResourceStateRequest) (*tfprotov5.UpgradeResourceStateResponse, error) { 17 | rpc := "UpgradeResourceState" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov5.UpgradeResourceStateResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.UpgradeResourceState(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_UpgradeResourceState_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerUpgradeResourceState(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().UpgradeResourceState(ctx, &tfprotov5.UpgradeResourceStateRequest{ 43 | TypeName: "test_resource_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.UpgradeResourceStateCalled["test_resource_server1"] { 51 | t.Errorf("expected test_resource_server1 UpgradeResourceState to be called on server1") 52 | } 53 | 54 | if testServer2.UpgradeResourceStateCalled["test_resource_server1"] { 55 | t.Errorf("unexpected test_resource_server1 UpgradeResourceState called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().UpgradeResourceState(ctx, &tfprotov5.UpgradeResourceStateRequest{ 59 | TypeName: "test_resource_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.UpgradeResourceStateCalled["test_resource_server2"] { 67 | t.Errorf("unexpected test_resource_server2 UpgradeResourceState called on server1") 68 | } 69 | 70 | if !testServer2.UpgradeResourceStateCalled["test_resource_server2"] { 71 | t.Errorf("expected test_resource_server2 UpgradeResourceState to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ValidateDataSourceConfig.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ValidateDataSourceConfig calls the ValidateDataSourceConfig method, passing 14 | // `req`, on the provider that returned the data source specified by 15 | // req.TypeName in its schema. 16 | func (s *muxServer) ValidateDataSourceConfig(ctx context.Context, req *tfprotov5.ValidateDataSourceConfigRequest) (*tfprotov5.ValidateDataSourceConfigResponse, error) { 17 | rpc := "ValidateDataSourceConfig" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getDataSourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov5.ValidateDataSourceConfigResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.ValidateDataSourceConfig(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ValidateDataSourceConfig_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerValidateDataSourceConfig(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | DataSourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_data_source_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | DataSourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_data_source_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().ValidateDataSourceConfig(ctx, &tfprotov5.ValidateDataSourceConfigRequest{ 43 | TypeName: "test_data_source_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.ValidateDataSourceConfigCalled["test_data_source_server1"] { 51 | t.Errorf("expected test_data_source_server1 ValidateDataSourceConfig to be called on server1") 52 | } 53 | 54 | if testServer2.ValidateDataSourceConfigCalled["test_data_source_server1"] { 55 | t.Errorf("unexpected test_data_source_server1 ValidateDataSourceConfig called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().ValidateDataSourceConfig(ctx, &tfprotov5.ValidateDataSourceConfigRequest{ 59 | TypeName: "test_data_source_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.ValidateDataSourceConfigCalled["test_data_source_server2"] { 67 | t.Errorf("unexpected test_data_source_server2 ValidateDataSourceConfig called on server1") 68 | } 69 | 70 | if !testServer2.ValidateDataSourceConfigCalled["test_data_source_server2"] { 71 | t.Errorf("expected test_data_source_server2 ValidateDataSourceConfig to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ValidateEphemeralResourceConfig.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | func (s *muxServer) ValidateEphemeralResourceConfig(ctx context.Context, req *tfprotov5.ValidateEphemeralResourceConfigRequest) (*tfprotov5.ValidateEphemeralResourceConfigResponse, error) { 15 | rpc := "ValidateEphemeralResourceTypeConfig" 16 | ctx = logging.InitContext(ctx) 17 | ctx = logging.RpcContext(ctx, rpc) 18 | 19 | server, diags, err := s.getEphemeralResourceServer(ctx, req.TypeName) 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | if diagnosticsHasError(diags) { 26 | return &tfprotov5.ValidateEphemeralResourceConfigResponse{ 27 | Diagnostics: diags, 28 | }, nil 29 | } 30 | 31 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 32 | logging.MuxTrace(ctx, "calling downstream server") 33 | 34 | return server.ValidateEphemeralResourceConfig(ctx, req) 35 | } 36 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ValidateEphemeralResourceConfig_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerValidateEphemeralResourceConfig(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_ephemeral_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_ephemeral_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 35 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 36 | 37 | if err != nil { 38 | t.Fatalf("unexpected error setting up factory: %s", err) 39 | } 40 | 41 | _, err = muxServer.ProviderServer().ValidateEphemeralResourceConfig(ctx, &tfprotov5.ValidateEphemeralResourceConfigRequest{ 42 | TypeName: "test_ephemeral_resource_server1", 43 | }) 44 | 45 | if err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | 49 | if !testServer1.ValidateEphemeralResourceConfigCalled["test_ephemeral_resource_server1"] { 50 | t.Errorf("expected test_ephemeral_resource_server1 ValidateEphemeralResourceConfig to be called on server1") 51 | } 52 | 53 | if testServer2.ValidateEphemeralResourceConfigCalled["test_ephemeral_resource_server1"] { 54 | t.Errorf("unexpected test_ephemeral_resource_server1 ValidateEphemeralResourceConfig called on server2") 55 | } 56 | 57 | _, err = muxServer.ProviderServer().ValidateEphemeralResourceConfig(ctx, &tfprotov5.ValidateEphemeralResourceConfigRequest{ 58 | TypeName: "test_ephemeral_resource_server2", 59 | }) 60 | 61 | if err != nil { 62 | t.Fatalf("unexpected error: %s", err) 63 | } 64 | 65 | if testServer1.ValidateEphemeralResourceConfigCalled["test_ephemeral_resource_server2"] { 66 | t.Errorf("unexpected test_ephemeral_resource_server2 ValidateEphemeralResourceConfig called on server1") 67 | } 68 | 69 | if !testServer2.ValidateEphemeralResourceConfigCalled["test_ephemeral_resource_server2"] { 70 | t.Errorf("expected test_ephemeral_resource_server2 ValidateEphemeralResourceConfig to be called on server2") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ValidateResourceTypeConfig.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ValidateResourceTypeConfig calls the ValidateResourceTypeConfig method, 14 | // passing `req`, on the provider that returned the resource specified by 15 | // req.TypeName in its schema. 16 | func (s *muxServer) ValidateResourceTypeConfig(ctx context.Context, req *tfprotov5.ValidateResourceTypeConfigRequest) (*tfprotov5.ValidateResourceTypeConfigResponse, error) { 17 | rpc := "ValidateResourceTypeConfig" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov5.ValidateResourceTypeConfigResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov5ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.ValidateResourceTypeConfig(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_ValidateResourceTypeConfig_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 14 | ) 15 | 16 | func TestMuxServerValidateResourceTypeConfig(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf5testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov5.Schema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf5testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov5.Schema{ 30 | "test_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 35 | muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) 36 | 37 | if err != nil { 38 | t.Fatalf("unexpected error setting up factory: %s", err) 39 | } 40 | 41 | _, err = muxServer.ProviderServer().ValidateResourceTypeConfig(ctx, &tfprotov5.ValidateResourceTypeConfigRequest{ 42 | TypeName: "test_resource_server1", 43 | }) 44 | 45 | if err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | 49 | if !testServer1.ValidateResourceTypeConfigCalled["test_resource_server1"] { 50 | t.Errorf("expected test_resource_server1 ValidateResourceTypeConfig to be called on server1") 51 | } 52 | 53 | if testServer2.ValidateResourceTypeConfigCalled["test_resource_server1"] { 54 | t.Errorf("unexpected test_resource_server1 ValidateResourceTypeConfig called on server2") 55 | } 56 | 57 | _, err = muxServer.ProviderServer().ValidateResourceTypeConfig(ctx, &tfprotov5.ValidateResourceTypeConfigRequest{ 58 | TypeName: "test_resource_server2", 59 | }) 60 | 61 | if err != nil { 62 | t.Fatalf("unexpected error: %s", err) 63 | } 64 | 65 | if testServer1.ValidateResourceTypeConfigCalled["test_resource_server2"] { 66 | t.Errorf("unexpected test_resource_server2 ValidateResourceTypeConfig called on server1") 67 | } 68 | 69 | if !testServer2.ValidateResourceTypeConfigCalled["test_resource_server2"] { 70 | t.Errorf("expected test_resource_server2 ValidateResourceTypeConfig to be called on server2") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tf5muxserver/mux_server_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver_test 5 | 6 | import ( 7 | "context" 8 | "log" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 11 | "github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server" 12 | "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" 13 | ) 14 | 15 | func ExampleNewMuxServer() { 16 | ctx := context.Background() 17 | providers := []func() tfprotov5.ProviderServer{ 18 | // Example terraform-plugin-sdk ProviderServer function 19 | // sdkprovider.New("version")().GRPCProvider, 20 | // 21 | // Example terraform-plugin-go ProviderServer function 22 | // goprovider.Provider(), 23 | } 24 | 25 | // requests will be routed to whichever server advertises support for 26 | // them in the GetSchema response. Only one server may advertise 27 | // support for any given resource, data source, or the provider or 28 | // provider_meta schemas. An error will be returned if more than one 29 | // server claims support. 30 | muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) 31 | 32 | if err != nil { 33 | log.Fatalln(err.Error()) 34 | } 35 | 36 | // Use the result to start a muxed provider 37 | err = tf5server.Serve("registry.terraform.io/namespace/example", muxServer.ProviderServer) 38 | 39 | if err != nil { 40 | log.Fatalln(err.Error()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tf5muxserver/schema_equality.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import ( 7 | "github.com/google/go-cmp/cmp" 8 | "github.com/google/go-cmp/cmp/cmpopts" 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov5" 10 | ) 11 | 12 | // schemaCmpOptions ensures comparisons of SchemaAttribute and 13 | // SchemaNestedBlock slices are considered equal despite ordering differences. 14 | var schemaCmpOptions = []cmp.Option{ 15 | cmpopts.SortSlices(func(i, j *tfprotov5.SchemaAttribute) bool { 16 | return i.Name < j.Name 17 | }), 18 | cmpopts.SortSlices(func(i, j *tfprotov5.SchemaNestedBlock) bool { 19 | return i.TypeName < j.TypeName 20 | }), 21 | cmpopts.IgnoreFields(tfprotov5.SchemaNestedBlock{}, "MinItems", "MaxItems"), 22 | } 23 | 24 | // schemaDiff outputs the difference between schemas while accounting for 25 | // inconsequential ordering differences in SchemaAttribute and 26 | // SchemaNestedBlock slices. 27 | func schemaDiff(i, j *tfprotov5.Schema) string { 28 | return cmp.Diff(i, j, schemaCmpOptions...) 29 | } 30 | 31 | // schemaEquals asserts equality between schemas by normalizing inconsequential 32 | // ordering differences in SchemaAttribute and SchemaNestedBlock slices. 33 | func schemaEquals(i, j *tfprotov5.Schema) bool { 34 | return cmp.Equal(i, j, schemaCmpOptions...) 35 | } 36 | -------------------------------------------------------------------------------- /tf5muxserver/server_capabilities.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf5muxserver 5 | 6 | import "github.com/hashicorp/terraform-plugin-go/tfprotov5" 7 | 8 | // serverCapabilities always announces all ServerCapabilities. Individual 9 | // capabilities are handled in their respective RPCs to protect downstream 10 | // servers if they are not compatible with a capability. 11 | var serverCapabilities = &tfprotov5.ServerCapabilities{ 12 | GetProviderSchemaOptional: true, 13 | MoveResourceState: true, 14 | PlanDestroy: true, 15 | } 16 | 17 | // serverSupportsPlanDestroy returns true if the given ServerCapabilities is not 18 | // nil and enables the PlanDestroy capability. 19 | func serverSupportsPlanDestroy(capabilities *tfprotov5.ServerCapabilities) bool { 20 | if capabilities == nil { 21 | return false 22 | } 23 | 24 | return capabilities.PlanDestroy 25 | } 26 | -------------------------------------------------------------------------------- /tf5to6server/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // Package tf5to6server translates a provider that implements protocol version 5, into one that implements protocol version 6. 5 | // 6 | // Supported protocol version 5 provider servers include any which implement 7 | // the tfprotov5.ProviderServer (https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov5#ProviderServer) 8 | // interface, such as: 9 | // 10 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server 11 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf5muxserver 12 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema 13 | // 14 | // Refer to the UpgradeServer() function for wrapping a server. 15 | package tf5to6server 16 | -------------------------------------------------------------------------------- /tf6muxserver/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // Package tf6muxserver combines multiple provider servers that implement protocol version 6, into a single server. 5 | // 6 | // Supported protocol version 6 provider servers include any which implement 7 | // the tfprotov6.ProviderServer (https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov6#ProviderServer) 8 | // interface, such as: 9 | // 10 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework 11 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server 12 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf5to6server 13 | // 14 | // Refer to the NewMuxServer() function for creating a combined server. 15 | package tf6muxserver 16 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ApplyResourceChange.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ApplyResourceChange calls the ApplyResourceChange method, passing `req`, on 14 | // the provider that returned the resource specified by req.TypeName in its 15 | // schema. 16 | func (s *muxServer) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyResourceChangeRequest) (*tfprotov6.ApplyResourceChangeResponse, error) { 17 | rpc := "ApplyResourceChange" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov6.ApplyResourceChangeResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.ApplyResourceChange(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ApplyResourceChange_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerApplyResourceChange(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().ApplyResourceChange(ctx, &tfprotov6.ApplyResourceChangeRequest{ 43 | TypeName: "test_resource_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.ApplyResourceChangeCalled["test_resource_server1"] { 51 | t.Errorf("expected test_resource_server1 ApplyResourceChange to be called on server1") 52 | } 53 | 54 | if testServer2.ApplyResourceChangeCalled["test_resource_server1"] { 55 | t.Errorf("unexpected test_resource_server1 ApplyResourceChange called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().ApplyResourceChange(ctx, &tfprotov6.ApplyResourceChangeRequest{ 59 | TypeName: "test_resource_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.ApplyResourceChangeCalled["test_resource_server2"] { 67 | t.Errorf("unexpected test_resource_server2 ApplyResourceChange called on server1") 68 | } 69 | 70 | if !testServer2.ApplyResourceChangeCalled["test_resource_server2"] { 71 | t.Errorf("expected test_resource_server2 ApplyResourceChange to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_CallFunction.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 13 | ) 14 | 15 | // CallFunction calls the CallFunction method of the underlying provider 16 | // serving the function. 17 | func (s *muxServer) CallFunction(ctx context.Context, req *tfprotov6.CallFunctionRequest) (*tfprotov6.CallFunctionResponse, error) { 18 | rpc := "CallFunction" 19 | ctx = logging.InitContext(ctx) 20 | ctx = logging.RpcContext(ctx, rpc) 21 | 22 | server, diags, err := s.getFunctionServer(ctx, req.Name) 23 | 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | if diagnosticsHasError(diags) { 29 | var text string 30 | 31 | for _, d := range diags { 32 | if d.Severity == tfprotov6.DiagnosticSeverityError { 33 | if text != "" { 34 | text += "\n" 35 | } 36 | 37 | text += fmt.Sprintf("%s: %s", d.Summary, d.Detail) 38 | } 39 | } 40 | 41 | return &tfprotov6.CallFunctionResponse{ 42 | Error: &tfprotov6.FunctionError{ 43 | Text: text, 44 | }, 45 | }, nil 46 | } 47 | 48 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 49 | 50 | logging.MuxTrace(ctx, "calling downstream server") 51 | 52 | return server.CallFunction(ctx, req) 53 | } 54 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_CallFunction_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerCallFunction(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | Functions: map[string]*tfprotov6.Function{ 23 | "test_function1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | Functions: map[string]*tfprotov6.Function{ 30 | "test_function2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().CallFunction(ctx, &tfprotov6.CallFunctionRequest{ 43 | Name: "test_function1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.CallFunctionCalled["test_function1"] { 51 | t.Errorf("expected test_function1 CallFunction to be called on server1") 52 | } 53 | 54 | if testServer2.CallFunctionCalled["test_function1"] { 55 | t.Errorf("unexpected test_function1 CallFunction called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().CallFunction(ctx, &tfprotov6.CallFunctionRequest{ 59 | Name: "test_function2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.CallFunctionCalled["test_function2"] { 67 | t.Errorf("unexpected test_function2 CallFunction called on server1") 68 | } 69 | 70 | if !testServer2.CallFunctionCalled["test_function2"] { 71 | t.Errorf("expected test_function2 CallFunction to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_CloseEphemeralResource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | func (s *muxServer) CloseEphemeralResource(ctx context.Context, req *tfprotov6.CloseEphemeralResourceRequest) (*tfprotov6.CloseEphemeralResourceResponse, error) { 15 | rpc := "CloseEphemeralResource" 16 | ctx = logging.InitContext(ctx) 17 | ctx = logging.RpcContext(ctx, rpc) 18 | 19 | server, diags, err := s.getEphemeralResourceServer(ctx, req.TypeName) 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | if diagnosticsHasError(diags) { 26 | return &tfprotov6.CloseEphemeralResourceResponse{ 27 | Diagnostics: diags, 28 | }, nil 29 | } 30 | 31 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 32 | logging.MuxTrace(ctx, "calling downstream server") 33 | 34 | return server.CloseEphemeralResource(ctx, req) 35 | } 36 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_CloseEphemeralResource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerCloseEphemeralResource(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_ephemeral_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_ephemeral_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 35 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 36 | 37 | if err != nil { 38 | t.Fatalf("unexpected error setting up factory: %s", err) 39 | } 40 | 41 | _, err = muxServer.ProviderServer().CloseEphemeralResource(ctx, &tfprotov6.CloseEphemeralResourceRequest{ 42 | TypeName: "test_ephemeral_resource_server1", 43 | }) 44 | 45 | if err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | 49 | if !testServer1.CloseEphemeralResourceCalled["test_ephemeral_resource_server1"] { 50 | t.Errorf("expected test_ephemeral_resource_server1 CloseEphemeralResource to be called on server1") 51 | } 52 | 53 | if testServer2.CloseEphemeralResourceCalled["test_ephemeral_resource_server1"] { 54 | t.Errorf("unexpected test_ephemeral_resource_server1 CloseEphemeralResource called on server2") 55 | } 56 | 57 | _, err = muxServer.ProviderServer().CloseEphemeralResource(ctx, &tfprotov6.CloseEphemeralResourceRequest{ 58 | TypeName: "test_ephemeral_resource_server2", 59 | }) 60 | 61 | if err != nil { 62 | t.Fatalf("unexpected error: %s", err) 63 | } 64 | 65 | if testServer1.CloseEphemeralResourceCalled["test_ephemeral_resource_server2"] { 66 | t.Errorf("unexpected test_ephemeral_resource_server2 CloseEphemeralResource called on server1") 67 | } 68 | 69 | if !testServer2.CloseEphemeralResourceCalled["test_ephemeral_resource_server2"] { 70 | t.Errorf("expected test_ephemeral_resource_server2 CloseEphemeralResource to be called on server2") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ConfigureProvider.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | // ConfigureProvider calls each provider's ConfigureProvider method, one at a 15 | // time, passing `req`. Any Diagnostic with severity error will abort the 16 | // process and return immediately; non-Error severity Diagnostics will be 17 | // combined and returned. 18 | func (s *muxServer) ConfigureProvider(ctx context.Context, req *tfprotov6.ConfigureProviderRequest) (*tfprotov6.ConfigureProviderResponse, error) { 19 | rpc := "ConfigureProvider" 20 | ctx = logging.InitContext(ctx) 21 | ctx = logging.RpcContext(ctx, rpc) 22 | var diags []*tfprotov6.Diagnostic 23 | 24 | for _, server := range s.servers { 25 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 26 | logging.MuxTrace(ctx, "calling downstream server") 27 | 28 | resp, err := server.ConfigureProvider(ctx, req) 29 | 30 | if err != nil { 31 | return resp, fmt.Errorf("error configuring %T: %w", server, err) 32 | } 33 | 34 | for _, diag := range resp.Diagnostics { 35 | if diag == nil { 36 | continue 37 | } 38 | 39 | diags = append(diags, diag) 40 | 41 | if diag.Severity != tfprotov6.DiagnosticSeverityError { 42 | continue 43 | } 44 | 45 | resp.Diagnostics = diags 46 | 47 | return resp, err 48 | } 49 | } 50 | 51 | return &tfprotov6.ConfigureProviderResponse{Diagnostics: diags}, nil 52 | } 53 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ConfigureProvider_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/google/go-cmp/cmp" 11 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 12 | 13 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 14 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 15 | ) 16 | 17 | func TestMuxServerConfigureProvider(t *testing.T) { 18 | t.Parallel() 19 | 20 | ctx := context.Background() 21 | testServers := [5]*tf6testserver.TestServer{ 22 | {}, 23 | { 24 | ConfigureProviderResponse: &tfprotov6.ConfigureProviderResponse{ 25 | Diagnostics: []*tfprotov6.Diagnostic{ 26 | { 27 | Severity: tfprotov6.DiagnosticSeverityWarning, 28 | Summary: "warning summary", 29 | Detail: "warning detail", 30 | }, 31 | }, 32 | }, 33 | }, 34 | {}, 35 | { 36 | ConfigureProviderResponse: &tfprotov6.ConfigureProviderResponse{ 37 | Diagnostics: []*tfprotov6.Diagnostic{ 38 | { 39 | Severity: tfprotov6.DiagnosticSeverityError, 40 | Summary: "error summary", 41 | Detail: "error detail", 42 | }, 43 | }, 44 | }, 45 | }, 46 | { 47 | ConfigureProviderResponse: &tfprotov6.ConfigureProviderResponse{ 48 | Diagnostics: []*tfprotov6.Diagnostic{ 49 | { 50 | Severity: tfprotov6.DiagnosticSeverityError, 51 | Summary: "unexpected error summary", 52 | Detail: "unexpected error detail", 53 | }, 54 | }, 55 | }, 56 | }, 57 | } 58 | 59 | servers := []func() tfprotov6.ProviderServer{ 60 | testServers[0].ProviderServer, 61 | testServers[1].ProviderServer, 62 | testServers[2].ProviderServer, 63 | testServers[3].ProviderServer, 64 | testServers[4].ProviderServer, 65 | } 66 | 67 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 68 | 69 | if err != nil { 70 | t.Fatalf("error setting up muxer: %s", err) 71 | } 72 | 73 | resp, err := muxServer.ProviderServer().ConfigureProvider(ctx, &tfprotov6.ConfigureProviderRequest{}) 74 | 75 | if err != nil { 76 | t.Fatalf("error calling ConfigureProvider: %s", err) 77 | } 78 | 79 | expectedResp := &tfprotov6.ConfigureProviderResponse{ 80 | Diagnostics: []*tfprotov6.Diagnostic{ 81 | { 82 | Severity: tfprotov6.DiagnosticSeverityWarning, 83 | Summary: "warning summary", 84 | Detail: "warning detail", 85 | }, 86 | { 87 | Severity: tfprotov6.DiagnosticSeverityError, 88 | Summary: "error summary", 89 | Detail: "error detail", 90 | }, 91 | }, 92 | } 93 | 94 | if diff := cmp.Diff(resp, expectedResp); diff != "" { 95 | t.Errorf("unexpected difference: %s", diff) 96 | } 97 | 98 | for num, testServer := range testServers { 99 | if num < 4 && !testServer.ConfigureProviderCalled { 100 | t.Errorf("configure not called on server%d", num+1) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_GetFunctions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 13 | ) 14 | 15 | // GetFunctions merges the functions returned by the tfprotov6.ProviderServers 16 | // associated with muxServer into a single response. Functions must be returned 17 | // from only one server or an error diagnostic is returned. 18 | func (s *muxServer) GetFunctions(ctx context.Context, req *tfprotov6.GetFunctionsRequest) (*tfprotov6.GetFunctionsResponse, error) { 19 | rpc := "GetFunctions" 20 | ctx = logging.InitContext(ctx) 21 | ctx = logging.RpcContext(ctx, rpc) 22 | 23 | s.serverDiscoveryMutex.Lock() 24 | defer s.serverDiscoveryMutex.Unlock() 25 | 26 | resp := &tfprotov6.GetFunctionsResponse{ 27 | Functions: make(map[string]*tfprotov6.Function), 28 | } 29 | 30 | for _, server := range s.servers { 31 | ctx := logging.Tfprotov6ProviderServerContext(ctx, server) 32 | 33 | logging.MuxTrace(ctx, "calling downstream server") 34 | 35 | serverResp, err := server.GetFunctions(ctx, &tfprotov6.GetFunctionsRequest{}) 36 | if err != nil { 37 | return resp, fmt.Errorf("error calling GetFunctions for %T: %w", server, err) 38 | } 39 | 40 | resp.Diagnostics = append(resp.Diagnostics, serverResp.Diagnostics...) 41 | 42 | for name, definition := range serverResp.Functions { 43 | if _, ok := resp.Functions[name]; ok { 44 | resp.Diagnostics = append(resp.Diagnostics, functionDuplicateError(name)) 45 | 46 | continue 47 | } 48 | 49 | s.functions[name] = server 50 | resp.Functions[name] = definition 51 | } 52 | } 53 | 54 | // Intentionally not setting overall server discovery as complete, as data 55 | // sources and resources are not discovered via this RPC. 56 | 57 | return resp, nil 58 | } 59 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_GetMetadata.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 13 | ) 14 | 15 | // GetMetadata merges the metadata returned by the 16 | // tfprotov6.ProviderServers associated with muxServer into a single response. 17 | // Resources and data sources must be returned from only one server or an error 18 | // diagnostic is returned. 19 | func (s *muxServer) GetMetadata(ctx context.Context, req *tfprotov6.GetMetadataRequest) (*tfprotov6.GetMetadataResponse, error) { 20 | rpc := "GetMetadata" 21 | ctx = logging.InitContext(ctx) 22 | ctx = logging.RpcContext(ctx, rpc) 23 | 24 | s.serverDiscoveryMutex.Lock() 25 | defer s.serverDiscoveryMutex.Unlock() 26 | 27 | resp := &tfprotov6.GetMetadataResponse{ 28 | DataSources: make([]tfprotov6.DataSourceMetadata, 0), 29 | EphemeralResources: make([]tfprotov6.EphemeralResourceMetadata, 0), 30 | Functions: make([]tfprotov6.FunctionMetadata, 0), 31 | Resources: make([]tfprotov6.ResourceMetadata, 0), 32 | ServerCapabilities: serverCapabilities, 33 | } 34 | 35 | for _, server := range s.servers { 36 | ctx := logging.Tfprotov6ProviderServerContext(ctx, server) 37 | logging.MuxTrace(ctx, "calling downstream server") 38 | 39 | serverResp, err := server.GetMetadata(ctx, &tfprotov6.GetMetadataRequest{}) 40 | 41 | if err != nil { 42 | return resp, fmt.Errorf("error calling GetMetadata for %T: %w", server, err) 43 | } 44 | 45 | resp.Diagnostics = append(resp.Diagnostics, serverResp.Diagnostics...) 46 | 47 | for _, datasource := range serverResp.DataSources { 48 | if datasourceMetadataContainsTypeName(resp.DataSources, datasource.TypeName) { 49 | resp.Diagnostics = append(resp.Diagnostics, dataSourceDuplicateError(datasource.TypeName)) 50 | 51 | continue 52 | } 53 | 54 | s.dataSources[datasource.TypeName] = server 55 | resp.DataSources = append(resp.DataSources, datasource) 56 | } 57 | 58 | for _, ephemeralResource := range serverResp.EphemeralResources { 59 | if ephemeralResourceMetadataContainsTypeName(resp.EphemeralResources, ephemeralResource.TypeName) { 60 | resp.Diagnostics = append(resp.Diagnostics, ephemeralResourceDuplicateError(ephemeralResource.TypeName)) 61 | 62 | continue 63 | } 64 | 65 | s.ephemeralResources[ephemeralResource.TypeName] = server 66 | resp.EphemeralResources = append(resp.EphemeralResources, ephemeralResource) 67 | } 68 | 69 | for _, function := range serverResp.Functions { 70 | if functionMetadataContainsName(resp.Functions, function.Name) { 71 | resp.Diagnostics = append(resp.Diagnostics, functionDuplicateError(function.Name)) 72 | 73 | continue 74 | } 75 | 76 | s.functions[function.Name] = server 77 | resp.Functions = append(resp.Functions, function) 78 | } 79 | 80 | for _, resource := range serverResp.Resources { 81 | if resourceMetadataContainsTypeName(resp.Resources, resource.TypeName) { 82 | resp.Diagnostics = append(resp.Diagnostics, resourceDuplicateError(resource.TypeName)) 83 | 84 | continue 85 | } 86 | 87 | s.resources[resource.TypeName] = server 88 | s.resourceCapabilities[resource.TypeName] = serverResp.ServerCapabilities 89 | resp.Resources = append(resp.Resources, resource) 90 | } 91 | } 92 | 93 | return resp, nil 94 | } 95 | 96 | func datasourceMetadataContainsTypeName(metadatas []tfprotov6.DataSourceMetadata, typeName string) bool { 97 | for _, metadata := range metadatas { 98 | if typeName == metadata.TypeName { 99 | return true 100 | } 101 | } 102 | 103 | return false 104 | } 105 | 106 | func ephemeralResourceMetadataContainsTypeName(metadatas []tfprotov6.EphemeralResourceMetadata, typeName string) bool { 107 | for _, metadata := range metadatas { 108 | if typeName == metadata.TypeName { 109 | return true 110 | } 111 | } 112 | 113 | return false 114 | } 115 | 116 | func functionMetadataContainsName(metadatas []tfprotov6.FunctionMetadata, name string) bool { 117 | for _, metadata := range metadatas { 118 | if name == metadata.Name { 119 | return true 120 | } 121 | } 122 | 123 | return false 124 | } 125 | 126 | func resourceMetadataContainsTypeName(metadatas []tfprotov6.ResourceMetadata, typeName string) bool { 127 | for _, metadata := range metadatas { 128 | if typeName == metadata.TypeName { 129 | return true 130 | } 131 | } 132 | 133 | return false 134 | } 135 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_GetResourceIdentitySchemas.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 13 | ) 14 | 15 | // GetResourceIdentitySchemas merges the schemas returned by the 16 | // tfprotov6.ResourceIdentitySchema associated with muxServer into a single schema. 17 | // Everything must be returned from only one server. 18 | // Schemas must be identical between all servers. 19 | func (s *muxServer) GetResourceIdentitySchemas(ctx context.Context, req *tfprotov6.GetResourceIdentitySchemasRequest) (*tfprotov6.GetResourceIdentitySchemasResponse, error) { 20 | rpc := "GetResourceIdentitySchemas" 21 | ctx = logging.InitContext(ctx) 22 | ctx = logging.RpcContext(ctx, rpc) 23 | 24 | resp := &tfprotov6.GetResourceIdentitySchemasResponse{ 25 | IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{}, 26 | Diagnostics: []*tfprotov6.Diagnostic{}, 27 | } 28 | 29 | for _, server := range s.servers { 30 | ctx := logging.Tfprotov6ProviderServerContext(ctx, server) 31 | logging.MuxTrace(ctx, "calling downstream server") 32 | 33 | resourceIdentitySchemas, err := server.GetResourceIdentitySchemas(ctx, req) 34 | 35 | if err != nil { 36 | return resp, fmt.Errorf("error calling GetResourceIdentitySchemas for %T: %w", server, err) 37 | } 38 | 39 | resp.Diagnostics = append(resp.Diagnostics, resourceIdentitySchemas.Diagnostics...) 40 | 41 | for resourceIdentityType, schema := range resourceIdentitySchemas.IdentitySchemas { 42 | if _, ok := resp.IdentitySchemas[resourceIdentityType]; ok { 43 | resp.Diagnostics = append(resp.Diagnostics, resourceIdentityDuplicateError(resourceIdentityType)) 44 | 45 | continue 46 | } 47 | 48 | resp.IdentitySchemas[resourceIdentityType] = schema 49 | } 50 | } 51 | 52 | return resp, nil 53 | } 54 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ImportResourceState.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ImportResourceState calls the ImportResourceState method, passing `req`, on 14 | // the provider that returned the resource specified by req.TypeName in its 15 | // schema. 16 | func (s *muxServer) ImportResourceState(ctx context.Context, req *tfprotov6.ImportResourceStateRequest) (*tfprotov6.ImportResourceStateResponse, error) { 17 | rpc := "ImportResourceState" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov6.ImportResourceStateResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.ImportResourceState(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ImportResourceState_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerImportResourceState(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().ImportResourceState(ctx, &tfprotov6.ImportResourceStateRequest{ 43 | TypeName: "test_resource_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.ImportResourceStateCalled["test_resource_server1"] { 51 | t.Errorf("expected test_resource_server1 ImportResourceState to be called on server1") 52 | } 53 | 54 | if testServer2.ImportResourceStateCalled["test_resource_server1"] { 55 | t.Errorf("unexpected test_resource_server1 ImportResourceState called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().ImportResourceState(ctx, &tfprotov6.ImportResourceStateRequest{ 59 | TypeName: "test_resource_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.ImportResourceStateCalled["test_resource_server2"] { 67 | t.Errorf("unexpected test_resource_server2 ImportResourceState called on server1") 68 | } 69 | 70 | if !testServer2.ImportResourceStateCalled["test_resource_server2"] { 71 | t.Errorf("expected test_resource_server2 ImportResourceState to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_MoveResourceState.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // MoveResourceState calls the MoveResourceState method of the underlying 14 | // provider serving the resource. 15 | func (s *muxServer) MoveResourceState(ctx context.Context, req *tfprotov6.MoveResourceStateRequest) (*tfprotov6.MoveResourceStateResponse, error) { 16 | rpc := "MoveResourceState" 17 | ctx = logging.InitContext(ctx) 18 | ctx = logging.RpcContext(ctx, rpc) 19 | 20 | server, diags, err := s.getResourceServer(ctx, req.TargetTypeName) 21 | 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | if diagnosticsHasError(diags) { 27 | return &tfprotov6.MoveResourceStateResponse{ 28 | Diagnostics: diags, 29 | }, nil 30 | } 31 | 32 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 33 | 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.MoveResourceState(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_MoveResourceState_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerMoveResourceState(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_resource1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_resource2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().MoveResourceState(ctx, &tfprotov6.MoveResourceStateRequest{ 43 | TargetTypeName: "test_resource1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.MoveResourceStateCalled["test_resource1"] { 51 | t.Errorf("expected test_resource1 MoveResourceState to be called on server1") 52 | } 53 | 54 | if testServer2.MoveResourceStateCalled["test_resource1"] { 55 | t.Errorf("unexpected test_resource1 MoveResourceState called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().MoveResourceState(ctx, &tfprotov6.MoveResourceStateRequest{ 59 | TargetTypeName: "test_resource2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.MoveResourceStateCalled["test_resource2"] { 67 | t.Errorf("unexpected test_resource2 MoveResourceState called on server1") 68 | } 69 | 70 | if !testServer2.MoveResourceStateCalled["test_resource2"] { 71 | t.Errorf("expected test_resource2 MoveResourceState to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_OpenEphemeralResource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | func (s *muxServer) OpenEphemeralResource(ctx context.Context, req *tfprotov6.OpenEphemeralResourceRequest) (*tfprotov6.OpenEphemeralResourceResponse, error) { 15 | rpc := "OpenEphemeralResource" 16 | ctx = logging.InitContext(ctx) 17 | ctx = logging.RpcContext(ctx, rpc) 18 | 19 | server, diags, err := s.getEphemeralResourceServer(ctx, req.TypeName) 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | if diagnosticsHasError(diags) { 26 | return &tfprotov6.OpenEphemeralResourceResponse{ 27 | Diagnostics: diags, 28 | }, nil 29 | } 30 | 31 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 32 | logging.MuxTrace(ctx, "calling downstream server") 33 | 34 | return server.OpenEphemeralResource(ctx, req) 35 | } 36 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_OpenEphemeralResource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerOpenEphemeralResource(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_ephemeral_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_ephemeral_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 35 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 36 | 37 | if err != nil { 38 | t.Fatalf("unexpected error setting up factory: %s", err) 39 | } 40 | 41 | _, err = muxServer.ProviderServer().OpenEphemeralResource(ctx, &tfprotov6.OpenEphemeralResourceRequest{ 42 | TypeName: "test_ephemeral_resource_server1", 43 | }) 44 | 45 | if err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | 49 | if !testServer1.OpenEphemeralResourceCalled["test_ephemeral_resource_server1"] { 50 | t.Errorf("expected test_ephemeral_resource_server1 OpenEphemeralResource to be called on server1") 51 | } 52 | 53 | if testServer2.OpenEphemeralResourceCalled["test_ephemeral_resource_server1"] { 54 | t.Errorf("unexpected test_ephemeral_resource_server1 OpenEphemeralResource called on server2") 55 | } 56 | 57 | _, err = muxServer.ProviderServer().OpenEphemeralResource(ctx, &tfprotov6.OpenEphemeralResourceRequest{ 58 | TypeName: "test_ephemeral_resource_server2", 59 | }) 60 | 61 | if err != nil { 62 | t.Fatalf("unexpected error: %s", err) 63 | } 64 | 65 | if testServer1.OpenEphemeralResourceCalled["test_ephemeral_resource_server2"] { 66 | t.Errorf("unexpected test_ephemeral_resource_server2 OpenEphemeralResource called on server1") 67 | } 68 | 69 | if !testServer2.OpenEphemeralResourceCalled["test_ephemeral_resource_server2"] { 70 | t.Errorf("expected test_ephemeral_resource_server2 OpenEphemeralResource to be called on server2") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_PlanResourceChange.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | // PlanResourceChange calls the PlanResourceChange method, passing `req`, on 15 | // the provider that returned the resource specified by req.TypeName in its 16 | // schema. 17 | func (s *muxServer) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanResourceChangeRequest) (*tfprotov6.PlanResourceChangeResponse, error) { 18 | rpc := "PlanResourceChange" 19 | ctx = logging.InitContext(ctx) 20 | ctx = logging.RpcContext(ctx, rpc) 21 | 22 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 23 | 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | if diagnosticsHasError(diags) { 29 | return &tfprotov6.PlanResourceChangeResponse{ 30 | Diagnostics: diags, 31 | }, nil 32 | } 33 | 34 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 35 | 36 | // Prevent ServerCapabilities.PlanDestroy from sending destroy plans to 37 | // servers which do not enable the capability. 38 | if !serverSupportsPlanDestroy(s.resourceCapabilities[req.TypeName]) { 39 | if req.ProposedNewState == nil { 40 | logging.MuxTrace(ctx, "server does not enable destroy plans, returning without calling downstream server") 41 | 42 | resp := &tfprotov6.PlanResourceChangeResponse{ 43 | // Presumably, we must preserve any prior private state so it 44 | // is still available during ApplyResourceChange. 45 | PlannedPrivate: req.PriorPrivate, 46 | } 47 | 48 | return resp, nil 49 | } 50 | 51 | isDestroyPlan, err := req.ProposedNewState.IsNull() 52 | 53 | if err != nil { 54 | return nil, fmt.Errorf("unable to determine if request is destroy plan: %w", err) 55 | } 56 | 57 | if isDestroyPlan { 58 | logging.MuxTrace(ctx, "server does not enable destroy plans, returning without calling downstream server") 59 | 60 | resp := &tfprotov6.PlanResourceChangeResponse{ 61 | // Presumably, we must preserve any prior private state so it 62 | // is still available during ApplyResourceChange. 63 | PlannedPrivate: req.PriorPrivate, 64 | PlannedState: req.ProposedNewState, 65 | } 66 | 67 | return resp, nil 68 | } 69 | } 70 | 71 | logging.MuxTrace(ctx, "calling downstream server") 72 | 73 | return server.PlanResourceChange(ctx, req) 74 | } 75 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ReadDataSource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ReadDataSource calls the ReadDataSource method, passing `req`, on the 14 | // provider that returned the data source specified by req.TypeName in its 15 | // schema. 16 | func (s *muxServer) ReadDataSource(ctx context.Context, req *tfprotov6.ReadDataSourceRequest) (*tfprotov6.ReadDataSourceResponse, error) { 17 | rpc := "ReadDataSource" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getDataSourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov6.ReadDataSourceResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.ReadDataSource(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ReadDataSource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerReadDataSource(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | DataSourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_data_source_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | DataSourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_data_source_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().ReadDataSource(ctx, &tfprotov6.ReadDataSourceRequest{ 43 | TypeName: "test_data_source_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.ReadDataSourceCalled["test_data_source_server1"] { 51 | t.Errorf("expected test_data_source_server1 ReadDataSource to be called on server1") 52 | } 53 | 54 | if testServer2.ReadDataSourceCalled["test_data_source_server1"] { 55 | t.Errorf("unexpected test_data_source_server1 ReadDataSource called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().ReadDataSource(ctx, &tfprotov6.ReadDataSourceRequest{ 59 | TypeName: "test_data_source_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.ReadDataSourceCalled["test_data_source_server2"] { 67 | t.Errorf("unexpected test_data_source_server2 ReadDataSource called on server1") 68 | } 69 | 70 | if !testServer2.ReadDataSourceCalled["test_data_source_server2"] { 71 | t.Errorf("expected test_data_source_server2 ReadDataSource to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ReadResource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ReadResource calls the ReadResource method, passing `req`, on the provider 14 | // that returned the resource specified by req.TypeName in its schema. 15 | func (s *muxServer) ReadResource(ctx context.Context, req *tfprotov6.ReadResourceRequest) (*tfprotov6.ReadResourceResponse, error) { 16 | rpc := "ReadResource" 17 | ctx = logging.InitContext(ctx) 18 | ctx = logging.RpcContext(ctx, rpc) 19 | 20 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 21 | 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | if diagnosticsHasError(diags) { 27 | return &tfprotov6.ReadResourceResponse{ 28 | Diagnostics: diags, 29 | }, nil 30 | } 31 | 32 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 33 | logging.MuxTrace(ctx, "calling downstream server") 34 | 35 | return server.ReadResource(ctx, req) 36 | } 37 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ReadResource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerReadResource(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().ReadResource(ctx, &tfprotov6.ReadResourceRequest{ 43 | TypeName: "test_resource_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.ReadResourceCalled["test_resource_server1"] { 51 | t.Errorf("expected test_resource_server1 ReadResource to be called on server1") 52 | } 53 | 54 | if testServer2.ReadResourceCalled["test_resource_server1"] { 55 | t.Errorf("unexpected test_resource_server1 ReadResource called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().ReadResource(ctx, &tfprotov6.ReadResourceRequest{ 59 | TypeName: "test_resource_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.ReadResourceCalled["test_resource_server2"] { 67 | t.Errorf("unexpected test_resource_server2 ReadResource called on server1") 68 | } 69 | 70 | if !testServer2.ReadResourceCalled["test_resource_server2"] { 71 | t.Errorf("expected test_resource_server2 ReadResource to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_RenewEphemeralResource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | func (s *muxServer) RenewEphemeralResource(ctx context.Context, req *tfprotov6.RenewEphemeralResourceRequest) (*tfprotov6.RenewEphemeralResourceResponse, error) { 15 | rpc := "RenewEphemeralResource" 16 | ctx = logging.InitContext(ctx) 17 | ctx = logging.RpcContext(ctx, rpc) 18 | 19 | server, diags, err := s.getEphemeralResourceServer(ctx, req.TypeName) 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | if diagnosticsHasError(diags) { 26 | return &tfprotov6.RenewEphemeralResourceResponse{ 27 | Diagnostics: diags, 28 | }, nil 29 | } 30 | 31 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 32 | logging.MuxTrace(ctx, "calling downstream server") 33 | 34 | return server.RenewEphemeralResource(ctx, req) 35 | } 36 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_RenewEphemeralResource_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerRenewEphemeralResource(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_ephemeral_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_ephemeral_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 35 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 36 | 37 | if err != nil { 38 | t.Fatalf("unexpected error setting up factory: %s", err) 39 | } 40 | 41 | _, err = muxServer.ProviderServer().RenewEphemeralResource(ctx, &tfprotov6.RenewEphemeralResourceRequest{ 42 | TypeName: "test_ephemeral_resource_server1", 43 | }) 44 | 45 | if err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | 49 | if !testServer1.RenewEphemeralResourceCalled["test_ephemeral_resource_server1"] { 50 | t.Errorf("expected test_ephemeral_resource_server1 RenewEphemeralResource to be called on server1") 51 | } 52 | 53 | if testServer2.RenewEphemeralResourceCalled["test_ephemeral_resource_server1"] { 54 | t.Errorf("unexpected test_ephemeral_resource_server1 RenewEphemeralResource called on server2") 55 | } 56 | 57 | _, err = muxServer.ProviderServer().RenewEphemeralResource(ctx, &tfprotov6.RenewEphemeralResourceRequest{ 58 | TypeName: "test_ephemeral_resource_server2", 59 | }) 60 | 61 | if err != nil { 62 | t.Fatalf("unexpected error: %s", err) 63 | } 64 | 65 | if testServer1.RenewEphemeralResourceCalled["test_ephemeral_resource_server2"] { 66 | t.Errorf("unexpected test_ephemeral_resource_server2 RenewEphemeralResource called on server1") 67 | } 68 | 69 | if !testServer2.RenewEphemeralResourceCalled["test_ephemeral_resource_server2"] { 70 | t.Errorf("expected test_ephemeral_resource_server2 RenewEphemeralResource to be called on server2") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_StopProvider.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "strings" 10 | 11 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 12 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 13 | ) 14 | 15 | // StopProvider calls the StopProvider function for each provider associated 16 | // with the muxServer, one at a time. All Error fields will be joined 17 | // together and returned, but will not prevent the rest of the providers' 18 | // StopProvider methods from being called. 19 | func (s *muxServer) StopProvider(ctx context.Context, req *tfprotov6.StopProviderRequest) (*tfprotov6.StopProviderResponse, error) { 20 | rpc := "StopProvider" 21 | ctx = logging.InitContext(ctx) 22 | ctx = logging.RpcContext(ctx, rpc) 23 | var errs []string 24 | 25 | for _, server := range s.servers { 26 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 27 | logging.MuxTrace(ctx, "calling downstream server") 28 | 29 | resp, err := server.StopProvider(ctx, req) 30 | 31 | if err != nil { 32 | return resp, fmt.Errorf("error stopping %T: %w", server, err) 33 | } 34 | 35 | if resp.Error != "" { 36 | errs = append(errs, resp.Error) 37 | } 38 | } 39 | 40 | return &tfprotov6.StopProviderResponse{ 41 | Error: strings.Join(errs, "\n"), 42 | }, nil 43 | } 44 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_StopProvider_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/google/go-cmp/cmp" 11 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 12 | 13 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 14 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 15 | ) 16 | 17 | func TestMuxServerStopProvider(t *testing.T) { 18 | t.Parallel() 19 | 20 | ctx := context.Background() 21 | testServers := [5]*tf6testserver.TestServer{ 22 | {}, 23 | { 24 | StopProviderResponse: &tfprotov6.StopProviderResponse{ 25 | Error: "error in server2", 26 | }, 27 | }, 28 | {}, 29 | { 30 | StopProviderResponse: &tfprotov6.StopProviderResponse{ 31 | Error: "error in server4", 32 | }, 33 | }, 34 | {}, 35 | } 36 | 37 | servers := []func() tfprotov6.ProviderServer{ 38 | testServers[0].ProviderServer, 39 | testServers[1].ProviderServer, 40 | testServers[2].ProviderServer, 41 | testServers[3].ProviderServer, 42 | testServers[4].ProviderServer, 43 | } 44 | 45 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 46 | 47 | if err != nil { 48 | t.Fatalf("error setting up muxer: %s", err) 49 | } 50 | 51 | resp, err := muxServer.ProviderServer().StopProvider(ctx, &tfprotov6.StopProviderRequest{}) 52 | 53 | if err != nil { 54 | t.Fatalf("error calling StopProvider: %s", err) 55 | } 56 | 57 | expectedResp := &tfprotov6.StopProviderResponse{ 58 | Error: "error in server2\nerror in server4", 59 | } 60 | 61 | if diff := cmp.Diff(resp, expectedResp); diff != "" { 62 | t.Errorf("unexpected response Error difference: %s", diff) 63 | } 64 | 65 | for num, testServer := range testServers { 66 | if !testServer.StopProviderCalled { 67 | t.Errorf("StopProvider not called on server%d", num+1) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_UpgradeResourceIdentity.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // UpgradeResourceState calls the UpgradeResourceState method, passing `req`, 14 | // on the provider that returned the resource specified by req.TypeName in its 15 | // schema. 16 | func (s *muxServer) UpgradeResourceIdentity(ctx context.Context, req *tfprotov6.UpgradeResourceIdentityRequest) (*tfprotov6.UpgradeResourceIdentityResponse, error) { 17 | rpc := "UpgradeResourceIdentity" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov6.UpgradeResourceIdentityResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.UpgradeResourceIdentity(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_UpgradeResourceIdentity_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerUpgradeResourceIdentity(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ 22 | IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 27 | ResourceSchemas: map[string]*tfprotov6.Schema{ 28 | "test_resource_server1": {}, 29 | }, 30 | }, 31 | } 32 | testServer2 := &tf6testserver.TestServer{ 33 | GetResourceIdentitySchemasResponse: &tfprotov6.GetResourceIdentitySchemasResponse{ 34 | IdentitySchemas: map[string]*tfprotov6.ResourceIdentitySchema{ 35 | "test_resource_server2": {}, 36 | }, 37 | }, 38 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 39 | ResourceSchemas: map[string]*tfprotov6.Schema{ 40 | "test_resource_server2": {}, 41 | }, 42 | }, 43 | } 44 | 45 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 46 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 47 | 48 | if err != nil { 49 | t.Fatalf("unexpected error setting up factory: %s", err) 50 | } 51 | 52 | _, err = muxServer.ProviderServer().UpgradeResourceIdentity(ctx, &tfprotov6.UpgradeResourceIdentityRequest{ 53 | TypeName: "test_resource_server1", 54 | }) 55 | 56 | if err != nil { 57 | t.Fatalf("unexpected error: %s", err) 58 | } 59 | 60 | if !testServer1.UpgradeResourceIdentityCalled["test_resource_server1"] { 61 | t.Errorf("expected test_resource_server1 UpgradeResourceIdentity to be called on server1") 62 | } 63 | 64 | if testServer2.UpgradeResourceIdentityCalled["test_resource_server1"] { 65 | t.Errorf("unexpected test_resource_server1 UpgradeResourceIdentity called on server2") 66 | } 67 | 68 | _, err = muxServer.ProviderServer().UpgradeResourceIdentity(ctx, &tfprotov6.UpgradeResourceIdentityRequest{ 69 | TypeName: "test_resource_server2", 70 | }) 71 | 72 | if err != nil { 73 | t.Fatalf("unexpected error: %s", err) 74 | } 75 | 76 | if testServer1.UpgradeResourceIdentityCalled["test_resource_server2"] { 77 | t.Errorf("unexpected test_resource_server2 UpgradeResourceIdentity called on server1") 78 | } 79 | 80 | if !testServer2.UpgradeResourceIdentityCalled["test_resource_server2"] { 81 | t.Errorf("expected test_resource_server2 UpgradeResourceIdentity to be called on server2") 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_UpgradeResourceState.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // UpgradeResourceState calls the UpgradeResourceState method, passing `req`, 14 | // on the provider that returned the resource specified by req.TypeName in its 15 | // schema. 16 | func (s *muxServer) UpgradeResourceState(ctx context.Context, req *tfprotov6.UpgradeResourceStateRequest) (*tfprotov6.UpgradeResourceStateResponse, error) { 17 | rpc := "UpgradeResourceState" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov6.UpgradeResourceStateResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.UpgradeResourceState(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_UpgradeResourceState_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerUpgradeResourceState(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().UpgradeResourceState(ctx, &tfprotov6.UpgradeResourceStateRequest{ 43 | TypeName: "test_resource_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.UpgradeResourceStateCalled["test_resource_server1"] { 51 | t.Errorf("expected test_resource_server1 UpgradeResourceState to be called on server1") 52 | } 53 | 54 | if testServer2.UpgradeResourceStateCalled["test_resource_server1"] { 55 | t.Errorf("unexpected test_resource_server1 UpgradeResourceState called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().UpgradeResourceState(ctx, &tfprotov6.UpgradeResourceStateRequest{ 59 | TypeName: "test_resource_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.UpgradeResourceStateCalled["test_resource_server2"] { 67 | t.Errorf("unexpected test_resource_server2 UpgradeResourceState called on server1") 68 | } 69 | 70 | if !testServer2.UpgradeResourceStateCalled["test_resource_server2"] { 71 | t.Errorf("expected test_resource_server2 UpgradeResourceState to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ValidateDataResourceConfig.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ValidateDataResourceConfig calls the ValidateDataResourceConfig method, passing 14 | // `req`, on the provider that returned the data source specified by 15 | // req.TypeName in its schema. 16 | func (s *muxServer) ValidateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) { 17 | rpc := "ValidateDataResourceConfig" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getDataSourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov6.ValidateDataResourceConfigResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.ValidateDataResourceConfig(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ValidateDataResourceConfig_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerValidateDataResourceConfig(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | DataSourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_data_source_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | DataSourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_data_source_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().ValidateDataResourceConfig(ctx, &tfprotov6.ValidateDataResourceConfigRequest{ 43 | TypeName: "test_data_source_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.ValidateDataResourceConfigCalled["test_data_source_server1"] { 51 | t.Errorf("expected test_data_source_server1 ValidateDataResourceConfig to be called on server1") 52 | } 53 | 54 | if testServer2.ValidateDataResourceConfigCalled["test_data_source_server1"] { 55 | t.Errorf("unexpected test_data_source_server1 ValidateDataResourceConfig called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().ValidateDataResourceConfig(ctx, &tfprotov6.ValidateDataResourceConfigRequest{ 59 | TypeName: "test_data_source_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.ValidateDataResourceConfigCalled["test_data_source_server2"] { 67 | t.Errorf("unexpected test_data_source_server2 ValidateDataResourceConfig called on server1") 68 | } 69 | 70 | if !testServer2.ValidateDataResourceConfigCalled["test_data_source_server2"] { 71 | t.Errorf("expected test_data_source_server2 ValidateDataResourceConfig to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ValidateEphemeralResourceConfig.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | func (s *muxServer) ValidateEphemeralResourceConfig(ctx context.Context, req *tfprotov6.ValidateEphemeralResourceConfigRequest) (*tfprotov6.ValidateEphemeralResourceConfigResponse, error) { 15 | rpc := "ValidateEphemeralResourceTypeConfig" 16 | ctx = logging.InitContext(ctx) 17 | ctx = logging.RpcContext(ctx, rpc) 18 | 19 | server, diags, err := s.getEphemeralResourceServer(ctx, req.TypeName) 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | if diagnosticsHasError(diags) { 26 | return &tfprotov6.ValidateEphemeralResourceConfigResponse{ 27 | Diagnostics: diags, 28 | }, nil 29 | } 30 | 31 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 32 | logging.MuxTrace(ctx, "calling downstream server") 33 | 34 | return server.ValidateEphemeralResourceConfig(ctx, req) 35 | } 36 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ValidateEphemeralResourceConfig_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerValidateEphemeralResourceConfig(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_ephemeral_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_ephemeral_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 35 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 36 | 37 | if err != nil { 38 | t.Fatalf("unexpected error setting up factory: %s", err) 39 | } 40 | 41 | _, err = muxServer.ProviderServer().ValidateEphemeralResourceConfig(ctx, &tfprotov6.ValidateEphemeralResourceConfigRequest{ 42 | TypeName: "test_ephemeral_resource_server1", 43 | }) 44 | 45 | if err != nil { 46 | t.Fatalf("unexpected error: %s", err) 47 | } 48 | 49 | if !testServer1.ValidateEphemeralResourceConfigCalled["test_ephemeral_resource_server1"] { 50 | t.Errorf("expected test_ephemeral_resource_server1 ValidateEphemeralResourceConfig to be called on server1") 51 | } 52 | 53 | if testServer2.ValidateEphemeralResourceConfigCalled["test_ephemeral_resource_server1"] { 54 | t.Errorf("unexpected test_ephemeral_resource_server1 ValidateEphemeralResourceConfig called on server2") 55 | } 56 | 57 | _, err = muxServer.ProviderServer().ValidateEphemeralResourceConfig(ctx, &tfprotov6.ValidateEphemeralResourceConfigRequest{ 58 | TypeName: "test_ephemeral_resource_server2", 59 | }) 60 | 61 | if err != nil { 62 | t.Fatalf("unexpected error: %s", err) 63 | } 64 | 65 | if testServer1.ValidateEphemeralResourceConfigCalled["test_ephemeral_resource_server2"] { 66 | t.Errorf("unexpected test_ephemeral_resource_server2 ValidateEphemeralResourceConfig called on server1") 67 | } 68 | 69 | if !testServer2.ValidateEphemeralResourceConfigCalled["test_ephemeral_resource_server2"] { 70 | t.Errorf("expected test_ephemeral_resource_server2 ValidateEphemeralResourceConfig to be called on server2") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ValidateProviderConfig.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 12 | ) 13 | 14 | // ValidateProviderConfig calls the ValidateProviderConfig method on each server 15 | // in order, passing `req`. Response diagnostics are appended from all servers. 16 | // Response PreparedConfig must be equal across all servers with nil values 17 | // skipped. 18 | func (s *muxServer) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { 19 | rpc := "ValidateProviderConfig" 20 | ctx = logging.InitContext(ctx) 21 | ctx = logging.RpcContext(ctx, rpc) 22 | 23 | resp := &tfprotov6.ValidateProviderConfigResponse{ 24 | PreparedConfig: req.Config, // ignored by Terraform anyways 25 | } 26 | 27 | for _, server := range s.servers { 28 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 29 | logging.MuxTrace(ctx, "calling downstream server") 30 | 31 | res, err := server.ValidateProviderConfig(ctx, req) 32 | 33 | if err != nil { 34 | return resp, fmt.Errorf("error from %T validating provider config: %w", server, err) 35 | } 36 | 37 | if res == nil { 38 | continue 39 | } 40 | 41 | resp.Diagnostics = append(resp.Diagnostics, res.Diagnostics...) 42 | } 43 | 44 | return resp, nil 45 | } 46 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ValidateResourceConfig.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-mux/internal/logging" 11 | ) 12 | 13 | // ValidateResourceConfig calls the ValidateResourceConfig method, 14 | // passing `req`, on the provider that returned the resource specified by 15 | // req.TypeName in its schema. 16 | func (s *muxServer) ValidateResourceConfig(ctx context.Context, req *tfprotov6.ValidateResourceConfigRequest) (*tfprotov6.ValidateResourceConfigResponse, error) { 17 | rpc := "ValidateResourceConfig" 18 | ctx = logging.InitContext(ctx) 19 | ctx = logging.RpcContext(ctx, rpc) 20 | 21 | server, diags, err := s.getResourceServer(ctx, req.TypeName) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if diagnosticsHasError(diags) { 28 | return &tfprotov6.ValidateResourceConfigResponse{ 29 | Diagnostics: diags, 30 | }, nil 31 | } 32 | 33 | ctx = logging.Tfprotov6ProviderServerContext(ctx, server) 34 | logging.MuxTrace(ctx, "calling downstream server") 35 | 36 | return server.ValidateResourceConfig(ctx, req) 37 | } 38 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_ValidateResourceConfig_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | 12 | "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" 13 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 14 | ) 15 | 16 | func TestMuxServerValidateResourceConfig(t *testing.T) { 17 | t.Parallel() 18 | 19 | ctx := context.Background() 20 | testServer1 := &tf6testserver.TestServer{ 21 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 22 | ResourceSchemas: map[string]*tfprotov6.Schema{ 23 | "test_resource_server1": {}, 24 | }, 25 | }, 26 | } 27 | testServer2 := &tf6testserver.TestServer{ 28 | GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ 29 | ResourceSchemas: map[string]*tfprotov6.Schema{ 30 | "test_resource_server2": {}, 31 | }, 32 | }, 33 | } 34 | 35 | servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} 36 | muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) 37 | 38 | if err != nil { 39 | t.Fatalf("unexpected error setting up factory: %s", err) 40 | } 41 | 42 | _, err = muxServer.ProviderServer().ValidateResourceConfig(ctx, &tfprotov6.ValidateResourceConfigRequest{ 43 | TypeName: "test_resource_server1", 44 | }) 45 | 46 | if err != nil { 47 | t.Fatalf("unexpected error: %s", err) 48 | } 49 | 50 | if !testServer1.ValidateResourceConfigCalled["test_resource_server1"] { 51 | t.Errorf("expected test_resource_server1 ValidateResourceConfig to be called on server1") 52 | } 53 | 54 | if testServer2.ValidateResourceConfigCalled["test_resource_server1"] { 55 | t.Errorf("unexpected test_resource_server1 ValidateResourceConfig called on server2") 56 | } 57 | 58 | _, err = muxServer.ProviderServer().ValidateResourceConfig(ctx, &tfprotov6.ValidateResourceConfigRequest{ 59 | TypeName: "test_resource_server2", 60 | }) 61 | 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if testServer1.ValidateResourceConfigCalled["test_resource_server2"] { 67 | t.Errorf("unexpected test_resource_server2 ValidateResourceConfig called on server1") 68 | } 69 | 70 | if !testServer2.ValidateResourceConfigCalled["test_resource_server2"] { 71 | t.Errorf("expected test_resource_server2 ValidateResourceConfig to be called on server2") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tf6muxserver/mux_server_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver_test 5 | 6 | import ( 7 | "context" 8 | "log" 9 | 10 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 11 | "github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server" 12 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 13 | ) 14 | 15 | func ExampleNewMuxServer() { 16 | ctx := context.Background() 17 | providers := []func() tfprotov6.ProviderServer{ 18 | // Example terraform-plugin-framework ProviderServer function 19 | // func() tfprotov6.ProviderServer { 20 | // return tfsdk.NewProtocol6Server(frameworkprovider.New("version")()) 21 | // }, 22 | // 23 | // Example terraform-plugin-go ProviderServer function 24 | // goprovider.Provider(), 25 | } 26 | 27 | // requests will be routed to whichever server advertises support for 28 | // them in the GetSchema response. Only one server may advertise 29 | // support for any given resource, data source, or the provider or 30 | // provider_meta schemas. An error will be returned if more than one 31 | // server claims support. 32 | muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) 33 | 34 | if err != nil { 35 | log.Fatalln(err.Error()) 36 | } 37 | 38 | // Use the result to start a muxed provider 39 | err = tf6server.Serve("registry.terraform.io/namespace/example", muxServer.ProviderServer) 40 | 41 | if err != nil { 42 | log.Fatalln(err.Error()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tf6muxserver/schema_equality.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import ( 7 | "github.com/google/go-cmp/cmp" 8 | "github.com/google/go-cmp/cmp/cmpopts" 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | ) 11 | 12 | // schemaCmpOptions ensures comparisons of SchemaAttribute and 13 | // SchemaNestedBlock slices are considered equal despite ordering differences. 14 | var schemaCmpOptions = []cmp.Option{ 15 | cmpopts.SortSlices(func(i, j *tfprotov6.SchemaAttribute) bool { 16 | return i.Name < j.Name 17 | }), 18 | cmpopts.SortSlices(func(i, j *tfprotov6.SchemaNestedBlock) bool { 19 | return i.TypeName < j.TypeName 20 | }), 21 | cmpopts.IgnoreFields(tfprotov6.SchemaNestedBlock{}, "MinItems", "MaxItems"), 22 | } 23 | 24 | // schemaDiff outputs the difference between schemas while accounting for 25 | // inconsequential ordering differences in SchemaAttribute and 26 | // SchemaNestedBlock slices. 27 | func schemaDiff(i, j *tfprotov6.Schema) string { 28 | return cmp.Diff(i, j, schemaCmpOptions...) 29 | } 30 | 31 | // schemaEquals asserts equality between schemas by normalizing inconsequential 32 | // ordering differences in SchemaAttribute and SchemaNestedBlock slices. 33 | func schemaEquals(i, j *tfprotov6.Schema) bool { 34 | return cmp.Equal(i, j, schemaCmpOptions...) 35 | } 36 | -------------------------------------------------------------------------------- /tf6muxserver/server_capabilities.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tf6muxserver 5 | 6 | import "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | 8 | // serverCapabilities always announces all ServerCapabilities. Individual 9 | // capabilities are handled in their respective RPCs to protect downstream 10 | // servers if they are not compatible with a capability. 11 | var serverCapabilities = &tfprotov6.ServerCapabilities{ 12 | GetProviderSchemaOptional: true, 13 | MoveResourceState: true, 14 | PlanDestroy: true, 15 | } 16 | 17 | // serverSupportsPlanDestroy returns true if the given ServerCapabilities is not 18 | // nil and enables the PlanDestroy capability. 19 | func serverSupportsPlanDestroy(capabilities *tfprotov6.ServerCapabilities) bool { 20 | if capabilities == nil { 21 | return false 22 | } 23 | 24 | return capabilities.PlanDestroy 25 | } 26 | -------------------------------------------------------------------------------- /tf6to5server/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // Package tf6to5server translates a provider that implements protocol version 6, into one that implements protocol version 5. 5 | // 6 | // Supported protocol version 6 provider servers include any which implement 7 | // the tfprotov6.ProviderServer (https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov6#ProviderServer) 8 | // interface, such as: 9 | // 10 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework 11 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server 12 | // - https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf6muxserver 13 | // 14 | // Refer to the DowngradeServer() function for wrapping a server. 15 | package tf6to5server 16 | -------------------------------------------------------------------------------- /tools/copywrite.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:build generate 5 | 6 | package tools 7 | 8 | import ( 9 | // copywrite header generation 10 | _ "github.com/hashicorp/copywrite" 11 | ) 12 | 13 | //go:generate go run github.com/hashicorp/copywrite headers -d .. --config ../.copywrite.hcl 14 | -------------------------------------------------------------------------------- /tools/go.mod: -------------------------------------------------------------------------------- 1 | module tools 2 | 3 | go 1.23.7 4 | 5 | require github.com/hashicorp/copywrite v0.22.0 6 | 7 | require ( 8 | github.com/AlecAivazis/survey/v2 v2.3.7 // indirect 9 | github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect 10 | github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect 11 | github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect 12 | github.com/bradleyfalzon/ghinstallation/v2 v2.5.0 // indirect 13 | github.com/cli/go-gh/v2 v2.11.2 // indirect 14 | github.com/cli/safeexec v1.0.0 // indirect 15 | github.com/cloudflare/circl v1.3.7 // indirect 16 | github.com/fatih/color v1.13.0 // indirect 17 | github.com/fsnotify/fsnotify v1.5.4 // indirect 18 | github.com/go-openapi/errors v0.20.2 // indirect 19 | github.com/go-openapi/strfmt v0.21.3 // indirect 20 | github.com/golang-jwt/jwt/v4 v4.5.2 // indirect 21 | github.com/golang/protobuf v1.5.2 // indirect 22 | github.com/google/go-github/v45 v45.2.0 // indirect 23 | github.com/google/go-github/v53 v53.0.0 // indirect 24 | github.com/google/go-querystring v1.1.0 // indirect 25 | github.com/hashicorp/go-hclog v1.5.0 // indirect 26 | github.com/hashicorp/hcl v1.0.0 // indirect 27 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 28 | github.com/jedib0t/go-pretty v4.3.0+incompatible // indirect 29 | github.com/jedib0t/go-pretty/v6 v6.4.6 // indirect 30 | github.com/joho/godotenv v1.3.0 // indirect 31 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 32 | github.com/knadh/koanf v1.5.0 // indirect 33 | github.com/mattn/go-colorable v0.1.13 // indirect 34 | github.com/mattn/go-isatty v0.0.20 // indirect 35 | github.com/mattn/go-runewidth v0.0.15 // indirect 36 | github.com/mergestat/timediff v0.0.3 // indirect 37 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 38 | github.com/mitchellh/copystructure v1.2.0 // indirect 39 | github.com/mitchellh/go-homedir v1.1.0 // indirect 40 | github.com/mitchellh/mapstructure v1.5.0 // indirect 41 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 42 | github.com/oklog/ulid v1.3.1 // indirect 43 | github.com/rivo/uniseg v0.4.7 // indirect 44 | github.com/rogpeppe/go-internal v1.10.0 // indirect 45 | github.com/samber/lo v1.37.0 // indirect 46 | github.com/spf13/cobra v1.6.1 // indirect 47 | github.com/spf13/pflag v1.0.5 // indirect 48 | github.com/thanhpk/randstr v1.0.4 // indirect 49 | go.mongodb.org/mongo-driver v1.10.0 // indirect 50 | golang.org/x/crypto v0.36.0 // indirect 51 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect 52 | golang.org/x/net v0.38.0 // indirect 53 | golang.org/x/oauth2 v0.8.0 // indirect 54 | golang.org/x/sync v0.12.0 // indirect 55 | golang.org/x/sys v0.31.0 // indirect 56 | golang.org/x/term v0.30.0 // indirect 57 | golang.org/x/text v0.23.0 // indirect 58 | google.golang.org/appengine v1.6.7 // indirect 59 | google.golang.org/protobuf v1.33.0 // indirect 60 | gopkg.in/yaml.v3 v3.0.1 // indirect 61 | ) 62 | -------------------------------------------------------------------------------- /website/Makefile: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | # NOTE: This file is managed by the Digital Team's # 3 | # Terraform configuration @ hashicorp/mktg-terraform # 4 | ###################################################### 5 | 6 | .DEFAULT_GOAL := website 7 | 8 | # Set the preview mode for the website shell to "developer" or "io" 9 | PREVIEW_MODE ?= developer 10 | REPO ?= terraform-plugin-mux 11 | 12 | # Enable setting alternate docker tool, e.g. 'make DOCKER_CMD=podman' 13 | DOCKER_CMD ?= docker 14 | 15 | CURRENT_GIT_BRANCH=$$(git rev-parse --abbrev-ref HEAD) 16 | LOCAL_CONTENT_DIR=../docs 17 | PWD=$$(pwd) 18 | 19 | DOCKER_IMAGE="hashicorp/dev-portal" 20 | DOCKER_IMAGE_LOCAL="dev-portal-local" 21 | DOCKER_RUN_FLAGS=-it \ 22 | --publish "3000:3000" \ 23 | --rm \ 24 | --tty \ 25 | --volume "$(PWD)/docs:/app/docs" \ 26 | --volume "$(PWD)/img:/app/public" \ 27 | --volume "$(PWD)/data:/app/data" \ 28 | --volume "$(PWD)/redirects.js:/app/redirects.js" \ 29 | --volume "next-dir:/app/website-preview/.next" \ 30 | --volume "$(PWD)/.env:/app/.env" \ 31 | --volume "$(PWD)/.env.development:/app/website-preview/.env.development" \ 32 | --volume "$(PWD)/.env.local:/app/website-preview/.env.local" \ 33 | -e "REPO=$(REPO)" \ 34 | -e "PREVIEW_FROM_REPO=$(REPO)" \ 35 | -e "IS_CONTENT_PREVIEW=true" \ 36 | -e "LOCAL_CONTENT_DIR=$(LOCAL_CONTENT_DIR)" \ 37 | -e "CURRENT_GIT_BRANCH=$(CURRENT_GIT_BRANCH)" \ 38 | -e "PREVIEW_MODE=$(PREVIEW_MODE)" 39 | 40 | # Default: run this if working on the website locally to run in watch mode. 41 | .PHONY: website 42 | website: 43 | @echo "==> Downloading latest Docker image..." 44 | @$(DOCKER_CMD) pull $(DOCKER_IMAGE) 45 | @echo "==> Starting website..." 46 | @$(DOCKER_CMD) run $(DOCKER_RUN_FLAGS) $(DOCKER_IMAGE) 47 | 48 | # Use this if you have run `website/build-local` to use the locally built image. 49 | .PHONY: website/local 50 | website/local: 51 | @echo "==> Starting website from local image..." 52 | @$(DOCKER_CMD) run $(DOCKER_RUN_FLAGS) $(DOCKER_IMAGE_LOCAL) 53 | 54 | # Run this to generate a new local Docker image. 55 | .PHONY: website/build-local 56 | website/build-local: 57 | @echo "==> Building local Docker image" 58 | @$(DOCKER_CMD) build https://github.com/hashicorp/dev-portal.git\#main \ 59 | -t $(DOCKER_IMAGE_LOCAL) 60 | 61 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Terraform Documentation 2 | 3 | > [!IMPORTANT] 4 | > **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com. 5 | 6 | This directory contains the portions of [the Terraform website][terraform.io] that pertain to the Terraform Plugin Mux. 7 | 8 | The files in this directory are intended to be used in conjunction with 9 | [the `terraform-website` repository](https://github.com/hashicorp/terraform-website), which brings all of the 10 | different documentation sources together and contains the scripts for testing and building the site as 11 | a whole. 12 | 13 | ## Updating Sidebar Navigation 14 | 15 | You must update the sidebar navigation for the `terraform-plugin-mux` documentation any time that you add or delete a documentation page. The website builds the sidebar navigation menu from the [nav-data] JSON file. For more details about how to update this file, refer to https://github.com/hashicorp/terraform-website#editing-navigation-sidebars. 16 | 17 | ## Adding Redirects 18 | 19 | You must add a redirect when you move, rename, or delete documentation pages. Refer to https://github.com/hashicorp/terraform-website#redirects for details. 20 | 21 | ## Previewing Changes 22 | 23 | You should preview your changes locally to ensure that the content is rendering properly before you create a pull request. The build includes content from this repository and the [`terraform-website`](https://github.com/hashicorp/terraform-website/) repository, allowing you to preview the entire Terraform documentation site. 24 | 25 | To preview your content, complete the following steps: 26 | 27 | **Set Up Local Environment** 28 | 29 | 1. [Install Docker](https://docs.docker.com/get-docker/). 30 | 1. Restart your terminal or command line session. 31 | 32 | **Launch Site Locally** 33 | 34 | 1. Navigate into your local `terraform-plugin-mux` top-level directory and run `make website`. 35 | 1. Open `http://localhost:3000` in your web browser. While the preview is running, you can edit pages and Next.js will automatically rebuild them. 36 | 1. When you're done with the preview, press `ctrl-C` in your terminal to stop the server. 37 | 38 | ### Validating Content 39 | 40 | Content changes are automatically validated against a set of rules as part of the pull request process. If you want to run these checks locally to validate your content before committing your changes, you can run the following command: 41 | 42 | ``` 43 | npm run content-check 44 | ``` 45 | 46 | If the validation fails, actionable error messages will be displayed to help you address detected issues. 47 | 48 | ## Deployment 49 | 50 | The website reads content from release tags to generate documentation for all versions of `terraform-plugin-mux` documentation. Changes merged into `main` will be included in the documentation for the next product release. 51 | 52 | You cannot edit documentation for past versions of `terraform-plugin-mux` on the site. Documentation is an artifact of a product release. We push docs fixes forward for the next release, rather than retroactively fixing older versions. 53 | 54 | [nav-data]: ../website/data/plugin-mux-nav-data.json 55 | [terraform.io]: https://www.terraform.io/ 56 | -------------------------------------------------------------------------------- /website/data/plugin-mux-nav-data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "heading": "Combining and Translating" 4 | }, 5 | { 6 | "title": "Overview", 7 | "path": "" 8 | }, 9 | { 10 | "title": "Combining Protocol v5 Providers", 11 | "path": "combining-protocol-version-5-providers" 12 | }, 13 | { 14 | "title": "Combining Protocol v6 Providers", 15 | "path": "combining-protocol-version-6-providers" 16 | }, 17 | { 18 | "title": "Translating Protocol v5 to v6", 19 | "path": "translating-protocol-version-5-to-6" 20 | }, 21 | { 22 | "title": "Translating Protocol v6 to v5", 23 | "path": "translating-protocol-version-6-to-5" 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /website/docs/plugin/mux/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | page_title: "Plugin Development - Combining and Translating Providers: Overview" 3 | description: >- 4 | Use terraform-plugin-mux to combine and translate providers. It minimizes code changes by wrapping existing provider servers. 5 | --- 6 | 7 | > [!IMPORTANT] 8 | > **Documentation Update:** Product documentation previously located in `/website` has moved to the [`hashicorp/web-unified-docs`](https://github.com/hashicorp/web-unified-docs) repository, where all product documentation is now centralized. Please make contributions directly to `web-unified-docs`, since changes to `/website` in this repository will not appear on developer.hashicorp.com. 9 | 10 | # Combining and Translating Providers 11 | 12 | The [terraform-plugin-mux](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux) Go module is a collection of Go packages for combining (multiplexing) and translating provider servers. It helps minimize provider code changes by wrapping existing provider servers. This functionality is based on the [Terraform Plugin Protocol](/terraform/plugin/how-terraform-works#terraform-plugin-protocol) and [`terraform-plugin-go`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go) provider servers. 13 | 14 | ## Use Cases 15 | 16 | The `terraform-plugin-mux` Go module enables flexibility when you develop and maintain Terraform providers. It is especially useful when you need to upgrade an existing provider to use the plugin framework SDK or the latest version of the plugin protocol. We recommend using `terraform-plugin-mux` for the following use cases: 17 | 18 | - Combine providers to reduce maintenance burden while still supporting varying Terraform requirements or multiple provider SDK implementations. 19 | - Migrate resources and data sources from [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2) to [terraform-plugin-framework](/terraform/plugin/framework) over time. 20 | - Develop with [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2), but require Terraform CLI 1.0 or later. 21 | - Develop with [terraform-plugin-framework](/terraform/plugin/framework), but support Terraform CLI 0.12 or later. 22 | 23 | ## Combine Protocol 6 Providers 24 | 25 | Use the [tf6muxserver](/terraform/plugin/mux/combining-protocol-version-6-providers) package to combine any number of [protocol version 6 provider servers](/terraform/plugin/how-terraform-works#protocol-version-6) into a single server. 26 | 27 | ## Combine Protocol 5 Providers 28 | 29 | Use the [tf5muxserver](/terraform/plugin/mux/combining-protocol-version-5-providers) package to combine any number of [protocol version 5 provider servers](/terraform/plugin/how-terraform-works#protocol-version-5) into a single server. 30 | 31 | ## Translate Protocol 5 Providers to Protocol 6 32 | 33 | Use the [tf5to6server](/terraform/plugin/mux/translating-protocol-version-5-to-6) package to translate a [protocol version 5 provider server](/terraform/plugin/how-terraform-works#protocol-version-5) into a [protocol version 6 provider server](/terraform/plugin/how-terraform-works#protocol-version-6). 34 | 35 | ## Translate Protocol 6 Providers to Protocol 5 36 | 37 | Use the [tf6to5server](/terraform/plugin/mux/translating-protocol-version-6-to-5) package to translate a [protocol version 6 provider server](/terraform/plugin/how-terraform-works#protocol-version-6) into a [protocol version 5 provider server](/terraform/plugin/how-terraform-works#protocol-version-5). 38 | -------------------------------------------------------------------------------- /website/img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp/terraform-plugin-mux/101e9dfd0787e782fc4d0869931716180da5df09/website/img/.gitkeep -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terraform-plugin-mux-docs-preview", 3 | "private": "true", 4 | "scripts": { 5 | "build": "./scripts/website-build.sh", 6 | "content-check": "hc-content --config base-docs" 7 | }, 8 | "devDependencies": { 9 | "@hashicorp/platform-content-conformance": "^0.0.8", 10 | "next": "^13.5.4" 11 | }, 12 | "engines": { 13 | "npm": ">=7.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /website/scripts/should-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) HashiCorp, Inc. 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | ###################################################### 7 | # NOTE: This file is managed by the Digital Team's # 8 | # Terraform configuration @ hashicorp/mktg-terraform # 9 | ###################################################### 10 | 11 | # This is run during the website build step to determine if we should skip the build or not. 12 | # More information: https://vercel.com/docs/platform/projects#ignored-build-step 13 | 14 | if [[ "$VERCEL_GIT_COMMIT_REF" == "stable-website" ]] ; then 15 | # Proceed with the build if the branch is stable-website 16 | echo "✅ - Build can proceed" 17 | exit 1; 18 | else 19 | # Check for differences in the website directory 20 | git diff --quiet HEAD^ HEAD ./ 21 | fi -------------------------------------------------------------------------------- /website/scripts/website-build.sh: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | ###################################################### 5 | # NOTE: This file is managed by the Digital Team's # 6 | # Terraform configuration @ hashicorp/mktg-terraform # 7 | ###################################################### 8 | 9 | # Repo which we are cloning and executing npm run build:deploy-preview within 10 | REPO_TO_CLONE=dev-portal 11 | # Set the subdirectory name for the base project 12 | PREVIEW_DIR=website-preview 13 | # The directory we want to clone the project into 14 | CLONE_DIR=website-preview 15 | # The product for which we are building the deploy preview 16 | PRODUCT=terraform-plugin-mux 17 | # Preview mode, controls the UI rendered (either the product site or developer). Can be `io` or `developer` 18 | PREVIEW_MODE=developer 19 | 20 | # Get the git branch of the commit that triggered the deploy preview 21 | # This will power remote image assets in local and deploy previews 22 | CURRENT_GIT_BRANCH=$VERCEL_GIT_COMMIT_REF 23 | 24 | # This is where content files live, relative to the website-preview dir. If omitted, "../content" will be used 25 | LOCAL_CONTENT_DIR=../docs 26 | 27 | from_cache=false 28 | 29 | if [ -d "$PREVIEW_DIR" ]; then 30 | echo "$PREVIEW_DIR found" 31 | CLONE_DIR="$PREVIEW_DIR-tmp" 32 | from_cache=true 33 | fi 34 | 35 | # Clone the base project, if needed 36 | echo "⏳ Cloning the $REPO_TO_CLONE repo, this might take a while..." 37 | git clone --depth=1 "https://github.com/hashicorp/$REPO_TO_CLONE.git" "$CLONE_DIR" 38 | 39 | if [ "$from_cache" = true ]; then 40 | echo "Setting up $PREVIEW_DIR" 41 | cp -R "./$CLONE_DIR/." "./$PREVIEW_DIR" 42 | fi 43 | 44 | # cd into the preview directory project 45 | cd "$PREVIEW_DIR" 46 | 47 | # Run the build:deploy-preview start script 48 | PREVIEW_FROM_REPO=$PRODUCT \ 49 | IS_CONTENT_PREVIEW=true \ 50 | PREVIEW_MODE=$PREVIEW_MODE \ 51 | REPO=$PRODUCT \ 52 | HASHI_ENV=project-preview \ 53 | LOCAL_CONTENT_DIR=$LOCAL_CONTENT_DIR \ 54 | CURRENT_GIT_BRANCH=$CURRENT_GIT_BRANCH \ 55 | npm run build:deploy-preview -------------------------------------------------------------------------------- /website/scripts/website-start.sh: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | ###################################################### 5 | # NOTE: This file is managed by the Digital Team's # 6 | # Terraform configuration @ hashicorp/mktg-terraform # 7 | ###################################################### 8 | 9 | # Repo which we are cloning and executing npm run build:deploy-preview within 10 | REPO_TO_CLONE=dev-portal 11 | # Set the subdirectory name for the dev-portal app 12 | PREVIEW_DIR=website-preview 13 | # The product for which we are building the deploy preview 14 | PRODUCT=terraform-plugin-mux 15 | # Preview mode, controls the UI rendered (either the product site or developer). Can be `io` or `developer` 16 | PREVIEW_MODE=developer 17 | 18 | # Get the git branch of the commit that triggered the deploy preview 19 | # This will power remote image assets in local and deploy previews 20 | CURRENT_GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 21 | 22 | # This is where content files live, relative to the website-preview dir. If omitted, "../content" will be used 23 | LOCAL_CONTENT_DIR=../docs 24 | 25 | should_pull=true 26 | 27 | # Clone the dev-portal project, if needed 28 | if [ ! -d "$PREVIEW_DIR" ]; then 29 | echo "⏳ Cloning the $REPO_TO_CLONE repo, this might take a while..." 30 | git clone --depth=1 https://github.com/hashicorp/$REPO_TO_CLONE.git "$PREVIEW_DIR" 31 | should_pull=false 32 | fi 33 | 34 | cd "$PREVIEW_DIR" 35 | 36 | # If the directory already existed, pull to ensure the clone is fresh 37 | if [ "$should_pull" = true ]; then 38 | git pull origin main 39 | fi 40 | 41 | # Run the dev-portal content-repo start script 42 | REPO=$PRODUCT \ 43 | PREVIEW_FROM_REPO=$PRODUCT \ 44 | LOCAL_CONTENT_DIR=$LOCAL_CONTENT_DIR \ 45 | CURRENT_GIT_BRANCH=$CURRENT_GIT_BRANCH \ 46 | PREVIEW_MODE=$PREVIEW_MODE \ 47 | npm run start:local-preview --------------------------------------------------------------------------------