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
Motivation
Per HashiCorp's framework benefits page,
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)
Data sources (29)
Teardown once the above is complete
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.
Summary
The provider is already a muxed provider serving both
terraform-plugin-sdk/v2andterraform-plugin-frameworkbehind a single protocol-v6 endpoint, but only 3 of 95resource types have been migrated to the framework. This issue tracks migrating the
remaining 63 resources and 29 data sources.
Current state
main.gocombines two provider servers viatf6muxserver:providerserver.NewProtocol6(cloudstack.New())— framework provider, defined incloudstack/provider_v6.gocloudstack.Provider().GRPCProvider— SDKv2 provider, wrapped bytf5to6server.UpgradeServer()to bridge protocol 5 → 6Split of resource types by SDK:
cloudstack/provider.go)CloudstackProvider.Resources())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 importterraform-plugin-sdk/v2;9 import the framework.
Relevant dependencies in
go.mod— both SDKs and the mux are already present, so nodependency work is needed:
terraform-plugin-frameworkterraform-plugin-framework-validatorsterraform-plugin-muxterraform-plugin-goterraform-plugin-sdk/v2terraform-plugin-testingReferences
cloudstack/service_offering_*.go,cloudstack/provider_v6.go,main.goMotivation
Per HashiCorp's framework benefits page,
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:
"",0andfalse, so optionalattributes cannot distinguish "user did not set this" from "user set the zero value".
short-lived secrets (API keys, VM passwords, Kubernetes cluster configs) without
persisting them in state.
Scope
Port each remaining SDKv2 resource and data source to the framework: remove it from
ResourcesMap/DataSourcesMapand register it inCloudstackProvider.Resources()/DataSources(). Once the lists below are complete,terraform-plugin-sdk/v2,cloudstack/provider.goand thetf5to6serverbridge inmain.gocan 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_*.gotogether withcloudstack/provider_v6.go.Notes for implementers
with the framework while it remains in
ResourcesMapmakes the mux server fail atstartup with a duplicate-type error, so both changes belong in the same commit.
ForceNew: truemust be reproduced as aRequiresReplace()plan modifieron the same attribute. There are 296 across 59 files.
ConflictsWith(16 uses),ValidateFunc(11),CustomizeDiff(13) andDiffSuppressFunc(1) become framework validators and plan modifiers.ImportStatemust be written explicitly for the 45 files that currently have anImporter.StateUpgraders,MigrateState,Timeoutsblocks, partial state orresource.Retryuses anywhere in the SDKv2 surface.schema.TypeSetand 22schema.TypeListuses need a deliberate choice betweennested blocks and nested attributes; turning a block into an attribute is a
config-breaking change.
cloudstack-gocall sites port verbatim —NewCreate*Params/Set*are identicalon both sides. Only the surrounding plumbing changes.
terraform-plugin-testingis already adependency and is protocol-agnostic.
Resources (63)
cloudstack_accountcloudstack_affinity_groupcloudstack_attach_volumecloudstack_autoscale_policycloudstack_autoscale_vm_groupcloudstack_autoscale_vm_profilecloudstack_clustercloudstack_cni_configurationcloudstack_conditioncloudstack_configurationcloudstack_countercloudstack_diskcloudstack_disk_offeringcloudstack_domaincloudstack_egress_firewallcloudstack_firewallcloudstack_hostcloudstack_instancecloudstack_ipaddresscloudstack_kubernetes_clustercloudstack_kubernetes_versioncloudstack_limitscloudstack_loadbalancercloudstack_loadbalancer_rulecloudstack_networkcloudstack_network_aclcloudstack_network_acl_rulecloudstack_network_acl_rulesetcloudstack_network_offeringcloudstack_network_service_providercloudstack_network_service_provider_statecloudstack_niccloudstack_physical_networkcloudstack_podcloudstack_port_forwardcloudstack_private_gatewaycloudstack_projectcloudstack_quota_tariffcloudstack_rolecloudstack_role_permissioncloudstack_secondary_ipaddresscloudstack_secondary_storagecloudstack_security_groupcloudstack_security_group_rulecloudstack_service_offeringcloudstack_snapshot_policycloudstack_ssh_keypaircloudstack_static_natcloudstack_static_routecloudstack_storage_network_ip_rangecloudstack_storage_poolcloudstack_templatecloudstack_traffic_typecloudstack_usercloudstack_user_datacloudstack_vlan_ip_rangecloudstack_volumecloudstack_vpccloudstack_vpc_offeringcloudstack_vpn_connectioncloudstack_vpn_customer_gatewaycloudstack_vpn_gatewaycloudstack_zoneData sources (29)
cloudstack_autoscale_policycloudstack_autoscale_vm_groupcloudstack_autoscale_vm_profilecloudstack_clustercloudstack_conditioncloudstack_countercloudstack_domaincloudstack_instancecloudstack_ipaddresscloudstack_kubernetes_cluster_configcloudstack_limitscloudstack_network_offeringcloudstack_physical_networkcloudstack_podcloudstack_projectcloudstack_quotacloudstack_quota_enabledcloudstack_quota_tariffcloudstack_rolecloudstack_service_offeringcloudstack_ssh_keypaircloudstack_templatecloudstack_usercloudstack_user_datacloudstack_volumecloudstack_vpccloudstack_vpc_offeringcloudstack_vpn_connectioncloudstack_zoneTeardown once the above is complete
terraform-plugin-sdk/v2fromgo.modtf5to6serverbridge frommain.gocloudstack/provider.goand the SDKv2 test helpersRelated findings
cloudstack_service_offering(SDKv2) coexists with the three frameworkcloudstack_service_offering_*resources, giving two ways to create a serviceoffering. Needs a decision on whether the older one is deprecated.