chore(provider): refactor sdk client creation to allow mock injection - #1663
Open
rubenhoenle wants to merge 7 commits into
Open
rubenhoenle wants to merge 7 commits into
rubenhoenle wants to merge 7 commits into
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. |
8 tasks
|
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. |
rubenhoenle
force-pushed
the
mock-refactor
branch
from
September 11, 2026 14:05
c63222a to
0785337
Compare
rubenhoenle
marked this pull request as ready for review
September 17, 2026 16:21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)