apache / apache/cloudstack-terraform-provider

Migrate resources, data sources from terraform-plugin-sdk/v2 to terraform-plugin-framework

Open
#333 0 comments 0 reactions 0 assignees View on GitHub
improvement
Dominant language
Go
Stars
53
Forks
67
Avg merge
3d 14h
Merged PRs (30d)
15

Description

## Summary

The provider is already a muxed provider serving both `terraform-plugin-sdk/v2` and
`terraform-plugin-framework` behind a single protocol-v6 endpoint, but only 3 of 95
resource types have been migrated to the framework. This issue tracks migrating the
remaining 63 resources and 29 data sources.

## Current state

`main.go` combines two provider servers via `tf6muxserver`:

- `providerserver.NewProtocol6(cloudstack.New())` — framework provider, defined in `cloudstack/provider_v6.go`
- `cloudstack.Provider().GRPCProvider` — SDKv2 provider, wrapped by `tf5to6server.UpgradeServer()` to bridge protocol 5 → 6

Split of resource types by SDK:

| | Resources | Data sources |
|---|---|---|
| SDKv2 (`cloudstack/provider.go`) | 63 | 29 |
| Framework (`CloudstackProvider.Resources()`) | 3 | 0 |

Already on the framework: `cloudstack_service_offering_constrained`,
`cloudstack_service_offering_fixed`, `cloudstack_service_offering_unconstrained`.

100 of the ~109 Go files under `cloudstack/` still import `terraform-plugin-sdk/v2`;
9 import the framework.

Relevant dependencies in `go.mod` — both SDKs and the mux are already present, so no
dependency work is needed:

| Module | Version |
|---|---|
| `terraform-plugin-framework` | v1.12.0 |
| `terraform-plugin-framework-validators` | v0.12.0 |
| `terraform-plugin-mux` | v0.16.0 |
| `terraform-plugin-go` | v0.24.0 |
| `terraform-plugin-sdk/v2` | v2.33.0 |
| `terraform-plugin-testing` | v1.7.0 |
| Go | 1.23.0 |

## References

- [Terraform Plugin Framework benefits](https://developer.hashicorp.com/terraform/plugin/framework-benefits)
- [Migrate from SDKv2 to the plugin framework](https://developer.hashicorp.com/terraform/plugin/framework/migrating)
- [Migrating providers](https://developer.hashicorp.com/terraform/plugin/framework/migrating/providers)
- [terraform-plugin-mux](https://github.com/hashicorp/terraform-plugin-mux)
- In-tree reference pattern: `cloudstack/service_offering_*.go`, `cloudstack/provider_v6.go`, `main.go`

## Motivation

Per [HashiCorp's framework benefits page](https://developer.hashicorp.com/terraform/plugin/framework-benefits),
HashiCorp has stopped most feature development on SDKv2 and recommends the framework for
new development and for migrating existing providers. Staying on SDKv2 for the bulk of
the provider costs us:

- **Null vs unknown vs zero.** SDKv2 conflates "unset", `""`, `0` and `false`, so optional
attributes cannot distinguish "user did not set this" from "user set the zero value".
- **No provider-defined functions.**
- **No ephemeral resources / write-only attributes**, the modern mechanism for handling
short-lived secrets (API keys, VM passwords, Kubernetes cluster configs) without
persisting them in state.
- **No list operations** for discovering unmanaged resources.
- Two schema idioms in one repo, so every new contribution has to pick a side.

## Scope

Port each remaining SDKv2 resource and data source to the framework: remove it from
`ResourcesMap`/`DataSourcesMap` and register it in
`CloudstackProvider.Resources()`/`DataSources()`. Once the lists below are complete,
`terraform-plugin-sdk/v2`, `cloudstack/provider.go` and the `tf5to6server` bridge in
`main.go` can be removed.

No user-facing HCL change is expected or intended: attribute names, block shapes and
semantics must be preserved, and upgrading from the last SDKv2 release should produce
an empty plan for every resource type.

The in-tree reference pattern is `cloudstack/service_offering_*.go` together with
`cloudstack/provider_v6.go`.

## Notes for implementers

- A resource type may be served by exactly **one** server in the mux. Registering a type
with the framework while it remains in `ResourcesMap` makes the mux server fail at
startup with a duplicate-type error, so both changes belong in the same commit.
- Every SDKv2 `ForceNew: true` must be reproduced as a `RequiresReplace()` plan modifier
on the same attribute. There are 296 across 59 files.
- `ConflictsWith` (16 uses), `ValidateFunc` (11), `CustomizeDiff` (13) and
`DiffSuppressFunc` (1) become framework validators and plan modifiers.
- `ImportState` must be written explicitly for the 45 files that currently have an
`Importer`.
- There are no `StateUpgraders`, `MigrateState`, `Timeouts` blocks, partial state or
`resource.Retry` uses anywhere in the SDKv2 surface.
- 35 `schema.TypeSet` and 22 `schema.TypeList` uses need a deliberate choice between
nested blocks and nested attributes; turning a block into an attribute is a
config-breaking change.
- The `cloudstack-go` call sites port verbatim — `NewCreate*Params` / `Set*` are identical
on both sides. Only the surrounding plumbing changes.
- Acceptance tests largely survive unchanged; `terraform-plugin-testing` is already a
dependency and is protocol-agnostic.

## Resources (63)

- [ ] `cloudstack_account`
- [ ] `cloudstack_affinity_group`
- [ ] `cloudstack_attach_volume`
- [ ] `cloudstack_autoscale_policy`
- [ ] `cloudstack_autoscale_vm_group`
- [ ] `cloudstack_autoscale_vm_profile`
- [ ] `cloudstack_cluster`
- [ ] `cloudstack_cni_configuration`
- [ ] `cloudstack_condition`
- [ ] `cloudstack_configuration`
- [ ] `cloudstack_counter`
- [ ] `cloudstack_disk`
- [ ] `cloudstack_disk_offering`
- [ ] `cloudstack_domain`
- [ ] `cloudstack_egress_firewall`
- [ ] `cloudstack_firewall`
- [ ] `cloudstack_host`
- [ ] `cloudstack_instance`
- [ ] `cloudstack_ipaddress`
- [ ] `cloudstack_kubernetes_cluster`
- [ ] `cloudstack_kubernetes_version`
- [ ] `cloudstack_limits`
- [ ] `cloudstack_loadbalancer`
- [ ] `cloudstack_loadbalancer_rule`
- [ ] `cloudstack_network`
- [ ] `cloudstack_network_acl`
- [ ] `cloudstack_network_acl_rule`
- [ ] `cloudstack_network_acl_ruleset`
- [ ] `cloudstack_network_offering`
- [ ] `cloudstack_network_service_provider`
- [ ] `cloudstack_network_service_provider_state`
- [ ] `cloudstack_nic`
- [ ] `cloudstack_physical_network`
- [ ] `cloudstack_pod`
- [ ] `cloudstack_port_forward`
- [ ] `cloudstack_private_gateway`
- [ ] `cloudstack_project`
- [ ] `cloudstack_quota_tariff`
- [ ] `cloudstack_role`
- [ ] `cloudstack_role_permission`
- [ ] `cloudstack_secondary_ipaddress`
- [ ] `cloudstack_secondary_storage`
- [ ] `cloudstack_security_group`
- [ ] `cloudstack_security_group_rule`
- [ ] `cloudstack_service_offering`
- [ ] `cloudstack_snapshot_policy`
- [ ] `cloudstack_ssh_keypair`
- [ ] `cloudstack_static_nat`
- [ ] `cloudstack_static_route`
- [ ] `cloudstack_storage_network_ip_range`
- [ ] `cloudstack_storage_pool`
- [ ] `cloudstack_template`
- [ ] `cloudstack_traffic_type`
- [ ] `cloudstack_user`
- [ ] `cloudstack_user_data`
- [ ] `cloudstack_vlan_ip_range`
- [ ] `cloudstack_volume`
- [ ] `cloudstack_vpc`
- [ ] `cloudstack_vpc_offering`
- [ ] `cloudstack_vpn_connection`
- [ ] `cloudstack_vpn_customer_gateway`
- [ ] `cloudstack_vpn_gateway`
- [ ] `cloudstack_zone`

## Data sources (29)

- [ ] `cloudstack_autoscale_policy`
- [ ] `cloudstack_autoscale_vm_group`
- [ ] `cloudstack_autoscale_vm_profile`
- [ ] `cloudstack_cluster`
- [ ] `cloudstack_condition`
- [ ] `cloudstack_counter`
- [ ] `cloudstack_domain`
- [ ] `cloudstack_instance`
- [ ] `cloudstack_ipaddress`
- [ ] `cloudstack_kubernetes_cluster_config`
- [ ] `cloudstack_limits`
- [ ] `cloudstack_network_offering`
- [ ] `cloudstack_physical_network`
- [ ] `cloudstack_pod`
- [ ] `cloudstack_project`
- [ ] `cloudstack_quota`
- [ ] `cloudstack_quota_enabled`
- [ ] `cloudstack_quota_tariff`
- [ ] `cloudstack_role`
- [ ] `cloudstack_service_offering`
- [ ] `cloudstack_ssh_keypair`
- [ ] `cloudstack_template`
- [ ] `cloudstack_user`
- [ ] `cloudstack_user_data`
- [ ] `cloudstack_volume`
- [ ] `cloudstack_vpc`
- [ ] `cloudstack_vpc_offering`
- [ ] `cloudstack_vpn_connection`
- [ ] `cloudstack_zone`

## Teardown once the above is complete

- [ ] Remove `terraform-plugin-sdk/v2` from `go.mod`
- [ ] Remove the `tf5to6server` bridge from `main.go`
- [ ] Delete `cloudstack/provider.go` and the SDKv2 test helpers

## Related findings

- `cloudstack_service_offering` (SDKv2) coexists with the three framework
`cloudstack_service_offering_*` resources, giving two ways to create a service
offering. Needs a decision on whether the older one is deprecated.

Contributor guide

Open the contributing guide

Research direction

Start with cloudstack/service_offering_*.go, cloudstack/provider_v6.go, and main.go to understand the existing framework and mux patterns, then choose a single listed resource or data source. Compare its SDKv2 implementation with the framework examples and its acceptance tests. Done means it is registered only on the framework server, preserves its schema and behavior, and passes the relevant tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, terraform
Domain
cloud, devops, infrastructure
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.