Repository navigation
chore(provider): refactor sdk client creation to allow mock injection - #1663
Conversation
|
This PR was marked as stale after 7 days of inactivity and will be closed after another 7 days of further inactivity. If this PR should be kept open, just add a comment, remove the stale label or push new commits to it. |
|
This PR was marked as stale after 7 days of inactivity and will be closed after another 7 days of further inactivity. If this PR should be kept open, just add a comment, remove the stale label or push new commits to it. |
|
This PR was marked as stale after 7 days of inactivity and will be closed after another 7 days of further inactivity. If this PR should be kept open, just add a comment, remove the stale label or push new commits to it. |
c63222a to
0785337
Compare
|
This PR was marked as stale after 7 days of inactivity and will be closed after another 7 days of further inactivity. If this PR should be kept open, just add a comment, remove the stale label or push new commits to it. |
2f552ac to
57d015f
Compare
Description
Intention
The intention of this refactoring is
Note
Furthermore (and probably more important) this refactoring allows us to roll out automatic retries on e.g. HTTP 429 API errors more easily across the whole Terraform provider with all it's resource and datasource implementations. See #1764 and #1771 for reference.
What changed inside the resource and datasource implementations
Previously our resources looked like this: In each resource in the
Configurefunction a new SDK client was created. The client was created using a util function (boilerplate code which had to implemented for every service, also the tests were copied in boilerplate manner):Now instead we have a pre-populated struct
core.ClientCollectionwhich is part of the provider data and includes an instance of each SDK HTTP client.How this works internally
First of all we have the
core.ClientCollectionstruct which should be passed to every resource and datasource:terraform-provider-stackit/stackit/internal/core/core.go
Lines 137 to 178 in f7a2441
This client collection has to be initialized somehow with all the SDK clients. Therefore we have the
initClientCollectionfunction and theClientFactoryterraform-provider-stackit/stackit/internal/core/clientutils.go
Lines 56 to 99 in f7a2441
terraform-provider-stackit/stackit/internal/core/clientutils.go
Lines 101 to 196 in f7a2441
The
ClientFactoryinterface allows us to have aDefaultClientFactoryimplementation which is used in regular production use of the STACKIT Terraform provider.terraform-provider-stackit/stackit/internal/core/clientutils.go
Lines 198 to 800 in f7a2441
The
MockClientFactoryis another implementation of theClientFactoryinterface and allows us to inject mocked SDK clients into the provider for API-mocked testing of resource and datasource implementations.terraform-provider-stackit/stackit/internal/core/clienttestutils.go
Lines 46 to 409 in f7a2441
The injection of the mock client factory happens here:
terraform-provider-stackit/stackit/provider.go
Lines 161 to 178 in f7a2441
Splitting the provider data into two structs
In Terraform plugin framework you can have one struct which is passed as provider data to each resource and datasource.
In the past this struct looked like this:
terraform-provider-stackit/stackit/internal/core/core.go
Lines 37 to 85 in 0c8ee62
It e.g. included a round tripper for authentication of all the SDK clients which were created using the util functions in each resource/datasource configure method.
terraform-provider-stackit/stackit/internal/core/core.go
Line 38 in 0c8ee62
terraform-provider-stackit/stackit/internal/services/iaas/utils/util.go
Line 23 in 0c8ee62
Since to allow for easy mocking we need to pass pre-populated SDK clients to all resources/datasources in the future we need to add this client to provider data. The provider data struct would grow therefore massively with API clients for every STACKIT service and service version.
At the same we need to keep this provider data struct within our resource/datasource implementation structs to make the
regionhandling possible (using adefault_regionconfigured on provider level and allowing our TF users to optionally override it in each resource/datasource configuration).terraform-provider-stackit/stackit/internal/services/iaas/volume/resource.go
Lines 95 to 99 in 0c8ee62
Reminder: We can only pass one provider data struct to each resource/datasource. Therefore I decided to split the provider data. The provider data struct passed should consist of two parts: The formerly known provider data (including e.g. the
default_regionconfig value or the list of enabled provider experiments) and the client collection.The provider data struct is meant to be kept in each regional resource/datasource implementation in the same way we did until now. The client collection should only be used in the
Configuremethod of each resource/datasource implementation.Both structs are tied together using the
core.providerDataInternalstruct.terraform-provider-stackit/stackit/internal/core/core.go
Lines 131 to 135 in f7a2441
The
providerDataInternalstruct type isn't exposed by design from thecorepackage. This way the inside the resource and datasource implementations the provider data stays the same like we've previously known it (it even shrinked since we don't need to keep all custom endpoint configs, etc. in it any more).terraform-provider-stackit/stackit/internal/core/core.go
Lines 180 to 192 in f7a2441
Ensuring code quality using automation
Two measures were taken to assert the code quality after this implementation:
core.ClientCollectionstruct is only consumed fromcore.ParseProviderDatabut stored in the resource/datasource implementation structs or passed as a parameter to other functions called by the resource/datasourceConfiguremethod implementations.core.ClientCollectionstruct can't be package-private in thecorepackage because there's one exception to that: the generic IAM rolebinding resource/datasource implementations.core.ClientCollectionstruct is actually initialized using theClientFactoryinterface in theinitClientCollectionfunction: testFurthermore for testing all the client initialization implementations of the
DefaultClientFactorysome generic test handler was implemented to get rid of the insane amount of boilerplate code we maintained beforehand.Checklist
make fmtexamples/directory)make generate-docs(will be checked by CI)make test(will be checked by CI)make lint(will be checked by CI)