qontract-reconcile 0.10.2.dev504__py3-none-any.whl → 0.10.2.dev506__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {qontract_reconcile-0.10.2.dev504.dist-info → qontract_reconcile-0.10.2.dev506.dist-info}/METADATA +1 -4
  2. {qontract_reconcile-0.10.2.dev504.dist-info → qontract_reconcile-0.10.2.dev506.dist-info}/RECORD +11 -29
  3. reconcile/cli.py +0 -108
  4. reconcile/gql_definitions/integrations/integrations.py +1 -31
  5. reconcile/gql_definitions/introspection.json +530 -2107
  6. reconcile/integrations_manager.py +0 -2
  7. reconcile/utils/external_resource_spec.py +1 -2
  8. reconcile/utils/runtime/sharding.py +0 -80
  9. tools/cli_commands/systems_and_tools.py +0 -23
  10. reconcile/gql_definitions/terraform_cloudflare_dns/__init__.py +0 -0
  11. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +0 -62
  12. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +0 -193
  13. reconcile/gql_definitions/terraform_cloudflare_resources/__init__.py +0 -0
  14. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +0 -127
  15. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +0 -359
  16. reconcile/gql_definitions/terraform_cloudflare_users/__init__.py +0 -0
  17. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +0 -62
  18. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +0 -139
  19. reconcile/terraform_cloudflare_dns.py +0 -379
  20. reconcile/terraform_cloudflare_resources.py +0 -445
  21. reconcile/terraform_cloudflare_users.py +0 -374
  22. reconcile/typed_queries/cloudflare.py +0 -10
  23. reconcile/utils/terrascript/__init__.py +0 -0
  24. reconcile/utils/terrascript/cloudflare_client.py +0 -310
  25. reconcile/utils/terrascript/cloudflare_resources.py +0 -432
  26. reconcile/utils/terrascript/models.py +0 -26
  27. reconcile/utils/terrascript/resources.py +0 -43
  28. {qontract_reconcile-0.10.2.dev504.dist-info → qontract_reconcile-0.10.2.dev506.dist-info}/WHEEL +0 -0
  29. {qontract_reconcile-0.10.2.dev504.dist-info → qontract_reconcile-0.10.2.dev506.dist-info}/entry_points.txt +0 -0
@@ -1,374 +0,0 @@
1
- import contextlib
2
- from collections.abc import (
3
- Iterable,
4
- Mapping,
5
- MutableMapping,
6
- )
7
- from dataclasses import dataclass
8
- from typing import Any
9
-
10
- from reconcile.gql_definitions.terraform_cloudflare_users import (
11
- app_interface_setting_cloudflare_and_vault,
12
- terraform_cloudflare_roles,
13
- )
14
- from reconcile.gql_definitions.terraform_cloudflare_users.app_interface_setting_cloudflare_and_vault import (
15
- AppInterfaceSettingCloudflareAndVaultQueryData,
16
- )
17
- from reconcile.gql_definitions.terraform_cloudflare_users.terraform_cloudflare_roles import (
18
- CloudflareAccountRoleQueryData,
19
- CloudflareAccountRoleV1,
20
- )
21
- from reconcile.utils import gql
22
- from reconcile.utils.external_resource_spec import ExternalResourceSpec
23
- from reconcile.utils.runtime.integration import (
24
- PydanticRunParams,
25
- QontractReconcileIntegration,
26
- )
27
- from reconcile.utils.secret_reader import (
28
- SecretReaderBase,
29
- create_secret_reader,
30
- )
31
- from reconcile.utils.semver_helper import make_semver
32
- from reconcile.utils.terraform import safe_resource_id
33
- from reconcile.utils.terraform.config_client import (
34
- ClientAlreadyRegisteredError,
35
- TerraformConfigClientCollection,
36
- )
37
- from reconcile.utils.terraform_client import (
38
- TerraformApplyFailedError,
39
- TerraformClient,
40
- TerraformDeletionDetectedError,
41
- TerraformPlanFailedError,
42
- )
43
- from reconcile.utils.terrascript.cloudflare_client import (
44
- AccountShardingStrategy,
45
- IntegrationUndefinedError,
46
- InvalidTerraformStateError,
47
- TerrascriptCloudflareClientFactory,
48
- )
49
- from reconcile.utils.terrascript.models import (
50
- CloudflareAccount,
51
- Integration,
52
- TerraformStateS3,
53
- )
54
-
55
- QONTRACT_INTEGRATION = "terraform_cloudflare_users"
56
- QONTRACT_INTEGRATION_VERSION = make_semver(0, 1, 0)
57
- QONTRACT_TF_PREFIX = "qrtfcfusers"
58
- CLOUDFLARE_EMAIL_DOMAIN_ALLOW_LIST_KEY = "cloudflareEmailDomainAllowList"
59
-
60
-
61
- @dataclass
62
- class CloudflareUser:
63
- email_address: str
64
- account_name: str
65
- org_username: str
66
- roles: set[str]
67
-
68
-
69
- class TerraformCloudflareUsersParams(PydanticRunParams):
70
- print_to_file: str | None
71
- account_name: str | None
72
- thread_pool_size: int
73
- enable_deletion: bool
74
-
75
-
76
- class TerraformCloudflareUsers(
77
- QontractReconcileIntegration[TerraformCloudflareUsersParams]
78
- ):
79
- @property
80
- def name(self) -> str:
81
- return QONTRACT_INTEGRATION.replace("_", "-")
82
-
83
- def get_early_exit_desired_state(
84
- self, *args: Any, **kwargs: Any
85
- ) -> dict[str, Any] | None:
86
- cloudflare_roles, settings = self._get_desired_state()
87
-
88
- if not settings.settings:
89
- raise RuntimeError("App interface setting not defined")
90
-
91
- early_exit_desired_state = cloudflare_roles.model_dump()
92
- early_exit_desired_state.update({
93
- CLOUDFLARE_EMAIL_DOMAIN_ALLOW_LIST_KEY: settings.settings
94
- })
95
- return early_exit_desired_state
96
-
97
- def _get_desired_state(
98
- self,
99
- ) -> tuple[
100
- CloudflareAccountRoleQueryData, AppInterfaceSettingCloudflareAndVaultQueryData
101
- ]:
102
- cloudflare_roles = terraform_cloudflare_roles.query(
103
- query_func=gql.get_api().query
104
- )
105
-
106
- settings = app_interface_setting_cloudflare_and_vault.query(
107
- query_func=gql.get_api().query
108
- )
109
-
110
- return cloudflare_roles, settings
111
-
112
- def run(self, dry_run: bool) -> None:
113
- print_to_file = self.params.print_to_file
114
- account_name = self.params.account_name
115
- thread_pool_size = self.params.thread_pool_size
116
- enable_deletion = self.params.enable_deletion
117
-
118
- cloudflare_roles, settings = self._get_desired_state()
119
-
120
- if not settings.settings:
121
- raise RuntimeError("App interface setting not defined")
122
-
123
- secret_reader = create_secret_reader(use_vault=settings.settings[0].vault)
124
-
125
- cf_clients = self._build_cloudflare_terraform_config_client_collection(
126
- cloudflare_roles, secret_reader, account_name
127
- )
128
-
129
- users = get_cloudflare_users(
130
- cloudflare_roles.cloudflare_account_roles,
131
- account_name,
132
- settings.settings[0].cloudflare_email_domain_allow_list,
133
- )
134
- specs = build_external_resource_spec_from_cloudflare_users(users)
135
-
136
- cf_clients.add_specs(specs)
137
- cf_clients.populate_resources()
138
-
139
- working_dirs = cf_clients.dump(print_to_file=print_to_file)
140
-
141
- if print_to_file:
142
- return
143
-
144
- # for storing unique CloudflareAccountV1 since cloudflare_account_role_v1 can contain duplicates due to schema
145
- account_names_to_account = {
146
- role.account.name: role.account
147
- for role in cloudflare_roles.cloudflare_account_roles or []
148
- if role.account.name in cf_clients.dump()
149
- }
150
-
151
- accounts = [
152
- acct.model_dump(by_alias=True)
153
- for _, acct in account_names_to_account.items()
154
- ]
155
-
156
- self._run_terraform(
157
- dry_run,
158
- enable_deletion,
159
- thread_pool_size,
160
- working_dirs,
161
- accounts,
162
- )
163
-
164
- def _run_terraform(
165
- self,
166
- dry_run: bool,
167
- enable_deletion: bool,
168
- thread_pool_size: int,
169
- working_dirs: Mapping[str, str],
170
- accounts: Iterable[Mapping[str, Any]],
171
- ) -> None:
172
- tf = TerraformClient(
173
- QONTRACT_INTEGRATION,
174
- QONTRACT_INTEGRATION_VERSION,
175
- QONTRACT_TF_PREFIX,
176
- accounts,
177
- working_dirs,
178
- thread_pool_size,
179
- )
180
-
181
- try:
182
- disabled_deletions_detected, err = tf.plan(enable_deletion)
183
- if err:
184
- raise TerraformPlanFailedError(
185
- f"Failed to run terraform plan for integration {QONTRACT_INTEGRATION}"
186
- )
187
- if disabled_deletions_detected:
188
- raise TerraformDeletionDetectedError(
189
- "Deletions detected but they are disabled"
190
- )
191
-
192
- if dry_run:
193
- return
194
-
195
- err = tf.apply()
196
- if err:
197
- raise TerraformApplyFailedError(
198
- f"Failed to run terraform apply for integration {QONTRACT_INTEGRATION}"
199
- )
200
- finally:
201
- tf.cleanup()
202
-
203
- def _build_cloudflare_terraform_config_client_collection(
204
- self,
205
- query_data: CloudflareAccountRoleQueryData,
206
- secret_reader: SecretReaderBase,
207
- account_name: str | None,
208
- ) -> TerraformConfigClientCollection:
209
- cf_clients = TerraformConfigClientCollection()
210
- for role in query_data.cloudflare_account_roles or []:
211
- if account_name and role.account.name != account_name:
212
- continue
213
- cf_account = CloudflareAccount(
214
- role.account.name,
215
- role.account.api_credentials,
216
- role.account.enforce_twofactor,
217
- role.account.q_type,
218
- role.account.provider_version,
219
- )
220
-
221
- tf_state = role.account.terraform_state_account.terraform_state
222
- if not tf_state:
223
- raise ValueError(
224
- f"AWS account {role.account.terraform_state_account.name} cannot be used for Cloudflare "
225
- f"account {cf_account.name} because it does not define a Terraform state "
226
- )
227
-
228
- bucket = tf_state.bucket
229
- region = tf_state.region
230
- integrations = tf_state.integrations
231
-
232
- if not bucket:
233
- raise InvalidTerraformStateError(
234
- "Terraform state must have bucket defined"
235
- )
236
- if not region:
237
- raise InvalidTerraformStateError(
238
- "Terraform state must have region defined"
239
- )
240
-
241
- integration = None
242
- for i in integrations:
243
- if i.integration.replace("-", "_") == QONTRACT_INTEGRATION:
244
- integration = i
245
- break
246
-
247
- if not integration:
248
- raise IntegrationUndefinedError(
249
- "Must declare integration name under Terraform state in app-interface"
250
- )
251
-
252
- tf_state_s3 = TerraformStateS3(
253
- role.account.terraform_state_account.automation_token,
254
- bucket,
255
- region,
256
- Integration(integration.integration, integration.key),
257
- )
258
-
259
- client = TerrascriptCloudflareClientFactory.get_client(
260
- tf_state_s3,
261
- cf_account,
262
- AccountShardingStrategy(cf_account),
263
- secret_reader,
264
- False,
265
- )
266
-
267
- with contextlib.suppress(ClientAlreadyRegisteredError):
268
- cf_clients.register_client(cf_account.name, client)
269
-
270
- return cf_clients
271
-
272
-
273
- def get_cloudflare_users(
274
- cloudflare_roles: Iterable[CloudflareAccountRoleV1] | None,
275
- account_name: str | None,
276
- email_domain_allow_list: Iterable[str] | None,
277
- ) -> dict[str, dict[str, CloudflareUser]]:
278
- """
279
- Returns a two-level dictionary of users with 1st level keys mapping to Cloudflare account names
280
- and 2nd level keys mapping to the user's email address.
281
- The method also takes into consideration :param account_name: and :param email_domain_allow_list: which can be
282
- used to filter users not matching these parameters
283
- """
284
- users: dict[str, dict[str, CloudflareUser]] = {}
285
-
286
- for cf_role in cloudflare_roles or []:
287
- if account_name and cf_role.account.name != account_name:
288
- continue
289
- for access_role in cf_role.access_roles or []:
290
- for user in access_role.users:
291
- if user.cloudflare_user is not None and (
292
- user.cloudflare_user.split("@")[1]
293
- in (email_domain_allow_list or [])
294
- ):
295
- temp = users.get(cf_role.account.name)
296
- if temp is not None:
297
- if temp.get(user.cloudflare_user) is not None:
298
- users[cf_role.account.name][
299
- user.cloudflare_user
300
- ].roles.update(set(cf_role.roles))
301
- else:
302
- users[cf_role.account.name][user.cloudflare_user] = (
303
- CloudflareUser(
304
- user.cloudflare_user,
305
- cf_role.account.name,
306
- user.org_username,
307
- set(cf_role.roles),
308
- )
309
- )
310
-
311
- else:
312
- users[cf_role.account.name] = {
313
- user.cloudflare_user: CloudflareUser(
314
- user.cloudflare_user,
315
- cf_role.account.name,
316
- user.org_username,
317
- set(cf_role.roles),
318
- )
319
- }
320
-
321
- return users
322
-
323
-
324
- def build_external_resource_spec_from_cloudflare_users(
325
- cloudflare_users: Mapping[str, Mapping[str, CloudflareUser]],
326
- ) -> Iterable[ExternalResourceSpec]:
327
- """
328
- This method transforms :param cloudflare_users: into a list of ExternalResourceSpec
329
- as TerrascriptCloudflareClient works only with the ExternalResourceSpec.
330
- """
331
- specs: list[ExternalResourceSpec] = []
332
-
333
- for v in cloudflare_users.values():
334
- for cf_user in v.values():
335
- data_source_cloudflare_account_roles = {
336
- "identifier": safe_resource_id(cf_user.account_name),
337
- "account_id": "${var.account_id}",
338
- }
339
-
340
- cloudflare_account_member = {
341
- "provider": "account_member",
342
- "identifier": safe_resource_id(cf_user.org_username),
343
- "email_address": cf_user.email_address,
344
- "account_id": "${var.account_id}",
345
- "role_ids": [
346
- # I know this is ugly :(
347
- # Terrascript doesn't support local values. Hence, we have to rely on string templating
348
- # (https://developer.hashicorp.com/terraform/language/expressions/strings#string-templates) to get
349
- # cloudflare role ids from role name.
350
- # This string template essentially uses cloudflare_account_roles (https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/data-sources/account_roles)
351
- # data source to get role id corresponding to a role name. We populate this string template for every role name listed.
352
- f'%{{ for role in data.cloudflare_account_roles.{safe_resource_id(cf_user.account_name)}.roles ~}} %{{if role.name == "{each}" ~}}${{role.id}}%{{ endif ~}} %{{ endfor ~}}'
353
- for each in cf_user.roles
354
- ],
355
- "cloudflare_account_roles": data_source_cloudflare_account_roles,
356
- }
357
- specs.append(
358
- _get_external_spec_from_resource(
359
- cloudflare_account_member, cf_user.account_name
360
- )
361
- )
362
-
363
- return specs
364
-
365
-
366
- def _get_external_spec_from_resource(
367
- resource: MutableMapping[Any, Any], account_name: str
368
- ) -> ExternalResourceSpec:
369
- return ExternalResourceSpec(
370
- provision_provider="cloudflare",
371
- provisioner={"name": f"{account_name}"},
372
- namespace={},
373
- resource=resource,
374
- )
@@ -1,10 +0,0 @@
1
- from reconcile.gql_definitions.terraform_cloudflare_resources.terraform_cloudflare_accounts import (
2
- CloudflareAccountV1,
3
- query,
4
- )
5
- from reconcile.utils import gql
6
-
7
-
8
- def get_cloudflare_accounts() -> list[CloudflareAccountV1]:
9
- data = query(gql.get_api().query)
10
- return list(data.accounts or [])
File without changes
@@ -1,310 +0,0 @@
1
- import tempfile
2
- from abc import (
3
- ABC,
4
- abstractmethod,
5
- )
6
- from collections.abc import Iterable
7
- from dataclasses import dataclass
8
-
9
- from terrascript import (
10
- Backend,
11
- Data,
12
- Output,
13
- Resource,
14
- Terraform,
15
- Terrascript,
16
- Variable,
17
- provider,
18
- )
19
-
20
- from reconcile.cli import TERRAFORM_VERSION
21
- from reconcile.utils.exceptions import SecretIncompleteError
22
- from reconcile.utils.external_resource_spec import (
23
- ExternalResourceSpec,
24
- ExternalResourceSpecInventory,
25
- )
26
- from reconcile.utils.secret_reader import SecretReaderBase
27
- from reconcile.utils.terraform.config import TerraformS3BackendConfig
28
- from reconcile.utils.terraform.config_client import TerraformConfigClient
29
- from reconcile.utils.terrascript.cloudflare_resources import (
30
- cloudflare_account,
31
- create_cloudflare_terrascript_resource,
32
- )
33
- from reconcile.utils.terrascript.models import (
34
- CloudflareAccount,
35
- Integration,
36
- TerraformStateS3,
37
- )
38
-
39
- TMP_DIR_PREFIX = "terrascript-cloudflare-"
40
-
41
- DEFAULT_CLOUDFLARE_ACCOUNT_TYPE = "standard"
42
- DEFAULT_CLOUDFLARE_ACCOUNT_2FA = False
43
- DEFAULT_IS_MANAGED_CLOUDFLARE_ACCOUNT = True
44
- DEFAULT_PROVIDER_RPS = 4
45
-
46
-
47
- @dataclass
48
- class CloudflareAccountConfig:
49
- """Configuration related to authenticating API calls to Cloudflare."""
50
-
51
- name: str
52
- api_token: str
53
- account_id: str
54
- enforce_twofactor: bool = DEFAULT_CLOUDFLARE_ACCOUNT_2FA
55
- type: str = DEFAULT_CLOUDFLARE_ACCOUNT_TYPE
56
- is_managed_account: bool = DEFAULT_IS_MANAGED_CLOUDFLARE_ACCOUNT
57
-
58
-
59
- def create_cloudflare_terrascript(
60
- account_config: CloudflareAccountConfig,
61
- backend_config: TerraformS3BackendConfig,
62
- provider_version: str,
63
- provider_rps: int = DEFAULT_PROVIDER_RPS,
64
- is_managed_account: bool = True,
65
- ) -> Terrascript:
66
- """
67
- Configures a Terrascript class with the required provider(s) and backend
68
- configuration.
69
-
70
- This is offloaded to a separate function to avoid mixing additional
71
- logic into TerrascriptCloudflareClient.
72
-
73
- :param account_config: CloudflareAccount configuration.
74
- :param backend_config: S3 as backend to store Terraform state.
75
- :param provider_version: Terraform Cloudflare provider version.
76
- :is_managed_account:
77
- If the target cloudflare account is being managed by the caller or not.
78
- Currently this is deferred to terraform-cloudflare-resources.
79
- Until further improvement(Tracked by APPSRE-7035),
80
- this argument can be set to False in other integrations.
81
- Defaults to True.
82
-
83
- :return: a Terrascript object that contains corresponding resources
84
- """
85
- terrascript = Terrascript()
86
-
87
- backend = Backend(
88
- "s3",
89
- access_key=backend_config.access_key,
90
- secret_key=backend_config.secret_key,
91
- bucket=backend_config.bucket,
92
- key=backend_config.key,
93
- region=backend_config.region,
94
- )
95
-
96
- required_providers = {
97
- "cloudflare": {
98
- "source": "cloudflare/cloudflare",
99
- "version": provider_version,
100
- }
101
- }
102
-
103
- terrascript += Terraform(
104
- backend=backend,
105
- required_providers=required_providers,
106
- required_version=TERRAFORM_VERSION[0],
107
- )
108
-
109
- cloudflare_provider_values = {
110
- "api_token": account_config.api_token,
111
- "rps": provider_rps,
112
- }
113
- if provider_version.startswith("3"):
114
- cloudflare_provider_values["account_id"] = (
115
- account_config.account_id
116
- ) # needed for some resources, see note below
117
-
118
- terrascript += provider.cloudflare(**cloudflare_provider_values)
119
-
120
- cloudflare_account_values = {
121
- "name": account_config.name,
122
- "enforce_twofactor": account_config.enforce_twofactor,
123
- "type": account_config.type,
124
- }
125
-
126
- if is_managed_account:
127
- terrascript += cloudflare_account(
128
- account_config.name,
129
- **cloudflare_account_values,
130
- )
131
-
132
- # Some resources need "account_id" to be set at the resource level
133
- # The cloudflare provider is being migrated from settings account_id at the provider
134
- # level to requiring it at the resource level, for resources that needs it.
135
- # This is also listed in version 4.x breaking changes:
136
- # https://github.com/cloudflare/terraform-provider-cloudflare/issues/1646
137
- terrascript += Variable(
138
- "account_id", type="string", default=account_config.account_id
139
- )
140
-
141
- return terrascript
142
-
143
-
144
- class TerrascriptCloudflareClient(TerraformConfigClient):
145
- """
146
- Build the Terrascript configuration, collect resources, and return Terraform JSON
147
- configuration.
148
-
149
- There's actually very little that's specific to Cloudflare in this class. This could
150
- become a more general TerrascriptClient that could in theory support any resource
151
- types with some minor modifications to how resource classes (self._resource_classes)
152
- are tracked.
153
- """
154
-
155
- def __init__(
156
- self,
157
- ts_client: Terrascript,
158
- ) -> None:
159
- self._terrascript = ts_client
160
- self._resource_specs: ExternalResourceSpecInventory = {}
161
-
162
- def add_spec(self, spec: ExternalResourceSpec) -> None:
163
- self._resource_specs[spec.id_object()] = spec
164
-
165
- def populate_resources(self) -> None:
166
- """
167
- Add the resource spec to Terrascript using the resource-specific classes
168
- to determine which resources to create.
169
- """
170
- for spec in self._resource_specs.values():
171
- resources_to_add = create_cloudflare_terrascript_resource(spec)
172
- self._add_resources(resources_to_add)
173
-
174
- def dump(self, existing_dir: str | None = None) -> str:
175
- """Write the Terraform JSON representation of the resources to disk"""
176
- if existing_dir is None:
177
- working_dir = tempfile.mkdtemp(prefix=TMP_DIR_PREFIX)
178
- else:
179
- working_dir = existing_dir
180
- with open(
181
- working_dir + "/config.tf.json", "w", encoding="locale"
182
- ) as terraform_config_file:
183
- terraform_config_file.write(self.dumps())
184
-
185
- return working_dir
186
-
187
- def dumps(self) -> str:
188
- """Return the Terraform JSON representation of the resources"""
189
- return str(self._terrascript)
190
-
191
- def _add_resources(self, tf_resources: Iterable[Resource | Output | Data]) -> None:
192
- for resource in tf_resources:
193
- self._terrascript.add(resource)
194
-
195
-
196
- class TerraformS3StateNamingStrategy(ABC):
197
- @abstractmethod
198
- def get_object_key(self, qr_integration: Integration) -> str:
199
- pass
200
-
201
-
202
- class Default(TerraformS3StateNamingStrategy):
203
- def get_object_key(self, qr_integration: Integration) -> str:
204
- return qr_integration.key
205
-
206
-
207
- class AccountShardingStrategy(TerraformS3StateNamingStrategy):
208
- """
209
- This strategy is in place until we solve for keyStrategy as specified in
210
- https://issues.redhat.com/browse/APPSRE-6933
211
- """
212
-
213
- def __init__(self, account: CloudflareAccount):
214
- super().__init__()
215
- self.account: CloudflareAccount = account
216
-
217
- def get_object_key(self, qr_integration: Integration) -> str:
218
- return f"{qr_integration.name}-{self.account.name}.tfstate"
219
-
220
-
221
- class DNSZoneShardingStrategy(TerraformS3StateNamingStrategy):
222
- def __init__(self, account: CloudflareAccount, zone_identifier: str):
223
- super().__init__()
224
- self.account: CloudflareAccount = account
225
- self.zone: str = zone_identifier
226
-
227
- def get_object_key(self, qr_integration: Integration) -> str:
228
- old_integration_key = qr_integration.name.replace(
229
- "-", "_"
230
- ) # This is because the state file was already created using this name before the refactoring
231
- return f"{old_integration_key}-{self.account.name}-{self.zone}.tfstate"
232
-
233
-
234
- class TerrascriptCloudflareClientFactory:
235
- @staticmethod
236
- def _create_backend_config(
237
- tf_state_s3: TerraformStateS3, key: str, secret_reader: SecretReaderBase
238
- ) -> TerraformS3BackendConfig:
239
- aws_acct_creds = secret_reader.read_all_secret(tf_state_s3.automation_token)
240
- aws_access_key_id = aws_acct_creds.get("aws_access_key_id")
241
- aws_secret_access_key = aws_acct_creds.get("aws_secret_access_key")
242
- if not aws_access_key_id or not aws_secret_access_key:
243
- raise SecretIncompleteError(
244
- f"secret {tf_state_s3.automation_token} incomplete: aws_access_key_id and/or aws_secret_access_key missing"
245
- )
246
-
247
- return TerraformS3BackendConfig(
248
- aws_access_key_id,
249
- aws_secret_access_key,
250
- tf_state_s3.bucket,
251
- key,
252
- tf_state_s3.region,
253
- )
254
-
255
- @staticmethod
256
- def _create_cloudflare_account_config(
257
- cf_acct: CloudflareAccount, secret_reader: SecretReaderBase
258
- ) -> CloudflareAccountConfig:
259
- cf_acct_creds = secret_reader.read_all_secret(cf_acct.api_credentials)
260
- cf_acct_config = CloudflareAccountConfig(
261
- cf_acct.name,
262
- cf_acct_creds["api_token"],
263
- cf_acct_creds["account_id"],
264
- cf_acct.enforce_twofactor or DEFAULT_CLOUDFLARE_ACCOUNT_2FA,
265
- cf_acct.type or DEFAULT_CLOUDFLARE_ACCOUNT_TYPE,
266
- )
267
- return cf_acct_config
268
-
269
- @classmethod
270
- def get_client(
271
- cls,
272
- tf_state_s3: TerraformStateS3,
273
- cf_acct: CloudflareAccount,
274
- sharding_strategy: TerraformS3StateNamingStrategy | None,
275
- secret_reader: SecretReaderBase,
276
- is_managed_account: bool,
277
- provider_rps: int = DEFAULT_PROVIDER_RPS,
278
- ) -> TerrascriptCloudflareClient:
279
- key = _get_terraform_s3_state_key_name(
280
- tf_state_s3.integration, sharding_strategy
281
- )
282
- backend_config = cls._create_backend_config(tf_state_s3, key, secret_reader)
283
- cf_acct_config = cls._create_cloudflare_account_config(cf_acct, secret_reader)
284
- ts_config = create_cloudflare_terrascript(
285
- cf_acct_config,
286
- backend_config,
287
- cf_acct.provider_version,
288
- provider_rps=provider_rps,
289
- is_managed_account=is_managed_account,
290
- )
291
- client = TerrascriptCloudflareClient(ts_config)
292
- return client
293
-
294
-
295
- def _get_terraform_s3_state_key_name(
296
- integration: Integration,
297
- sharding_strategy: TerraformS3StateNamingStrategy | None,
298
- ) -> str:
299
- if sharding_strategy is None:
300
- sharding_strategy = Default()
301
-
302
- return sharding_strategy.get_object_key(integration)
303
-
304
-
305
- class IntegrationUndefinedError(Exception):
306
- pass
307
-
308
-
309
- class InvalidTerraformStateError(Exception):
310
- pass