qontract-reconcile 0.10.2.dev503__py3-none-any.whl → 0.10.2.dev505__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 (34) hide show
  1. {qontract_reconcile-0.10.2.dev503.dist-info → qontract_reconcile-0.10.2.dev505.dist-info}/METADATA +1 -4
  2. {qontract_reconcile-0.10.2.dev503.dist-info → qontract_reconcile-0.10.2.dev505.dist-info}/RECORD +16 -34
  3. reconcile/cli.py +0 -108
  4. reconcile/gql_definitions/common/saasherder_settings.py +10 -0
  5. reconcile/gql_definitions/integrations/integrations.py +1 -31
  6. reconcile/gql_definitions/introspection.json +0 -220
  7. reconcile/integrations_manager.py +0 -2
  8. reconcile/openshift_saas_deploy.py +8 -0
  9. reconcile/utils/external_resource_spec.py +1 -2
  10. reconcile/utils/runtime/sharding.py +0 -80
  11. reconcile/utils/saasherder/interfaces.py +1 -0
  12. reconcile/utils/saasherder/models.py +8 -0
  13. reconcile/utils/saasherder/saasherder.py +79 -1
  14. tools/cli_commands/systems_and_tools.py +0 -23
  15. reconcile/gql_definitions/terraform_cloudflare_dns/__init__.py +0 -0
  16. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +0 -62
  17. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +0 -193
  18. reconcile/gql_definitions/terraform_cloudflare_resources/__init__.py +0 -0
  19. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +0 -127
  20. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +0 -359
  21. reconcile/gql_definitions/terraform_cloudflare_users/__init__.py +0 -0
  22. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +0 -62
  23. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +0 -139
  24. reconcile/terraform_cloudflare_dns.py +0 -379
  25. reconcile/terraform_cloudflare_resources.py +0 -445
  26. reconcile/terraform_cloudflare_users.py +0 -374
  27. reconcile/typed_queries/cloudflare.py +0 -10
  28. reconcile/utils/terrascript/__init__.py +0 -0
  29. reconcile/utils/terrascript/cloudflare_client.py +0 -310
  30. reconcile/utils/terrascript/cloudflare_resources.py +0 -432
  31. reconcile/utils/terrascript/models.py +0 -26
  32. reconcile/utils/terrascript/resources.py +0 -43
  33. {qontract_reconcile-0.10.2.dev503.dist-info → qontract_reconcile-0.10.2.dev505.dist-info}/WHEEL +0 -0
  34. {qontract_reconcile-0.10.2.dev503.dist-info → qontract_reconcile-0.10.2.dev505.dist-info}/entry_points.txt +0 -0
@@ -70,6 +70,7 @@ from reconcile.utils.saasherder.interfaces import (
70
70
  from reconcile.utils.saasherder.models import (
71
71
  Channel,
72
72
  ImageAuth,
73
+ ImagePatternsBlockRule,
73
74
  Namespace,
74
75
  Promotion,
75
76
  SLOKey,
@@ -130,7 +131,8 @@ class SaasHerder:
130
131
  validate: bool = False,
131
132
  include_trigger_trace: bool = False,
132
133
  all_saas_files: Iterable[SaasFile] | None = None,
133
- ):
134
+ image_patterns_block_rules: list[ImagePatternsBlockRule] | None = None,
135
+ ) -> None:
134
136
  self.error_registered = False
135
137
  self.saas_files = saas_files
136
138
  self.repo_urls = self._collect_repo_urls()
@@ -156,6 +158,9 @@ class SaasHerder:
156
158
  self.images: set[str] = set()
157
159
  self.blocked_versions = self._collect_blocked_versions()
158
160
  self.hotfix_versions = self._collect_hotfix_versions()
161
+ self.image_patterns_block_rules: list[ImagePatternsBlockRule] = (
162
+ image_patterns_block_rules or []
163
+ )
159
164
 
160
165
  # each namespace is in fact a target,
161
166
  # so we can use it to calculate.
@@ -1187,6 +1192,73 @@ class SaasHerder:
1187
1192
  )
1188
1193
  return None
1189
1194
 
1195
+ def _is_block_rule_violated(
1196
+ self,
1197
+ block_rule: ImagePatternsBlockRule,
1198
+ env_labels: dict[str, str] | None,
1199
+ images: set[str],
1200
+ spec: TargetSpec,
1201
+ ) -> bool:
1202
+ """Check if a block rule is violated for the given environment and images.
1203
+
1204
+ Args:
1205
+ block_rule: Block rule with environmentLabelSelector and imagePatterns
1206
+ env_labels: Environment labels dictionary
1207
+ images: Set of image URLs to check
1208
+ spec: TargetSpec for error reporting
1209
+
1210
+ Returns:
1211
+ True if rule is violated, False otherwise
1212
+ """
1213
+ if not env_labels:
1214
+ return False
1215
+
1216
+ # Check if environment labels match the selector
1217
+ env_selector = block_rule.environment_label_selector or {}
1218
+ if not all(env_labels.get(key) == value for key, value in env_selector.items()):
1219
+ return False
1220
+
1221
+ # Check if any images match blocked patterns
1222
+ blocked_images = [
1223
+ image
1224
+ for image in images
1225
+ if any(image.startswith(pattern) for pattern in block_rule.image_patterns)
1226
+ ]
1227
+
1228
+ if blocked_images:
1229
+ logging.error(
1230
+ f"{spec.error_prefix} Target contains blocked image patterns "
1231
+ f"({', '.join(block_rule.image_patterns)}) for environment matching "
1232
+ f"selector {env_selector}: {', '.join(blocked_images)}. "
1233
+ f"These images are not allowed in this environment."
1234
+ )
1235
+ return True
1236
+
1237
+ return False
1238
+
1239
+ def _check_blocked_image_patterns(
1240
+ self,
1241
+ spec: TargetSpec,
1242
+ images_set: set[str],
1243
+ ) -> bool:
1244
+ """Check if images violate any block rules for the given spec.
1245
+
1246
+ Args:
1247
+ spec: TargetSpec with target and environment information
1248
+ images_set: Set of image URLs to check
1249
+
1250
+ Returns:
1251
+ True if any violations are found, False otherwise
1252
+ """
1253
+ if not self.image_patterns_block_rules or not images_set:
1254
+ return False # no rules configured or no images, no violations
1255
+
1256
+ env_labels = spec.target.namespace.environment.labels
1257
+ return any(
1258
+ self._is_block_rule_violated(block_rule, env_labels, images_set, spec)
1259
+ for block_rule in self.image_patterns_block_rules
1260
+ )
1261
+
1190
1262
  def _check_images(
1191
1263
  self,
1192
1264
  spec: TargetSpec,
@@ -1199,6 +1271,12 @@ class SaasHerder:
1199
1271
  self.images.update(images_set)
1200
1272
  if not images_set:
1201
1273
  return False # no errors
1274
+
1275
+ # Check blocked image patterns
1276
+ if self._check_blocked_image_patterns(spec, images_set):
1277
+ return True # violations found
1278
+
1279
+ # imagePatterns validation
1202
1280
  images = threaded.run(
1203
1281
  self._get_image,
1204
1282
  images_set,
@@ -85,12 +85,6 @@ from reconcile.gql_definitions.statuspage.statuspages import (
85
85
  from reconcile.gql_definitions.statuspage.statuspages import (
86
86
  StatusPageV1,
87
87
  )
88
- from reconcile.gql_definitions.terraform_cloudflare_resources.terraform_cloudflare_accounts import (
89
- DEFINITION as CLOUDFLARE_ACCOUNTS_DEFINITION,
90
- )
91
- from reconcile.gql_definitions.terraform_cloudflare_resources.terraform_cloudflare_accounts import (
92
- CloudflareAccountV1,
93
- )
94
88
  from reconcile.gql_definitions.terraform_tgw_attachments.aws_accounts import (
95
89
  DEFINITION as AWS_ACCOUNTS_DEFINITION,
96
90
  )
@@ -110,7 +104,6 @@ from reconcile.gql_definitions.vault_instances.vault_instances import (
110
104
  VaultInstanceV1,
111
105
  )
112
106
  from reconcile.statuspage.integration import get_status_pages
113
- from reconcile.typed_queries.cloudflare import get_cloudflare_accounts
114
107
  from reconcile.typed_queries.clusters import get_clusters
115
108
  from reconcile.typed_queries.dynatrace import get_dynatrace_environments
116
109
  from reconcile.typed_queries.gitlab_instances import (
@@ -180,8 +173,6 @@ class SystemTool(BaseModel):
180
173
  return cls.init_from_unleash_instance(model, enumeration)
181
174
  case VaultInstanceV1():
182
175
  return cls.init_from_vault_instance(model, enumeration)
183
- case CloudflareAccountV1():
184
- return cls.init_from_cloudflare_account(model, enumeration)
185
176
  case AppCodeComponentsV1():
186
177
  return cls.init_from_code_component(model, enumeration)
187
178
  case _:
@@ -368,19 +359,6 @@ class SystemTool(BaseModel):
368
359
  enumeration=enumeration,
369
360
  )
370
361
 
371
- @classmethod
372
- def init_from_cloudflare_account(
373
- cls, a: CloudflareAccountV1, enumeration: Any
374
- ) -> Self:
375
- return cls(
376
- system_type="cloudflare",
377
- system_id=a.name,
378
- name=a.name,
379
- url="https://dash.cloudflare.com/",
380
- description=a.description,
381
- enumeration=enumeration,
382
- )
383
-
384
362
  @classmethod
385
363
  def init_from_code_component(cls, c: AppCodeComponentsV1, enumeration: Any) -> Self:
386
364
  return cls(
@@ -469,7 +447,6 @@ def get_systems_and_tools_inventory() -> SystemToolInventory:
469
447
  inventory.update(get_status_pages(), STATUS_PAGES_DEFINITION)
470
448
  inventory.update(get_unleash_instances(), UNLEASH_INSTANCES_DEFINITION)
471
449
  inventory.update(get_vault_instances(), VAULT_INSTANCES_DEFINITION)
472
- inventory.update(get_cloudflare_accounts(), CLOUDFLARE_ACCOUNTS_DEFINITION)
473
450
  inventory.update(
474
451
  [
475
452
  c
@@ -1,62 +0,0 @@
1
- """
2
- Generated by qenerate plugin=pydantic_v2. DO NOT MODIFY MANUALLY!
3
- """
4
- from collections.abc import Callable # noqa: F401 # pylint: disable=W0611
5
- from datetime import datetime # noqa: F401 # pylint: disable=W0611
6
- from enum import Enum # noqa: F401 # pylint: disable=W0611
7
- from typing import ( # noqa: F401 # pylint: disable=W0611
8
- Any,
9
- Optional,
10
- Union,
11
- )
12
-
13
- from pydantic import ( # noqa: F401 # pylint: disable=W0611
14
- BaseModel,
15
- ConfigDict,
16
- Field,
17
- Json,
18
- )
19
-
20
-
21
- DEFINITION = """
22
- query AppInterfaceSettingCloudflareDNS {
23
- settings: app_interface_settings_v1 {
24
- cloudflareDNSZoneMaxRecords
25
- vault
26
- }
27
- }
28
- """
29
-
30
-
31
- class ConfiguredBaseModel(BaseModel):
32
- model_config = ConfigDict(
33
- extra='forbid'
34
- )
35
-
36
-
37
- class AppInterfaceSettingsV1(ConfiguredBaseModel):
38
- cloudflare_dns_zone_max_records: Optional[int] = Field(..., alias="cloudflareDNSZoneMaxRecords")
39
- vault: bool = Field(..., alias="vault")
40
-
41
-
42
- class AppInterfaceSettingCloudflareDNSQueryData(ConfiguredBaseModel):
43
- settings: Optional[list[AppInterfaceSettingsV1]] = Field(..., alias="settings")
44
-
45
-
46
- def query(query_func: Callable, **kwargs: Any) -> AppInterfaceSettingCloudflareDNSQueryData:
47
- """
48
- This is a convenience function which queries and parses the data into
49
- concrete types. It should be compatible with most GQL clients.
50
- You do not have to use it to consume the generated data classes.
51
- Alternatively, you can also mime and alternate the behavior
52
- of this function in the caller.
53
-
54
- Parameters:
55
- query_func (Callable): Function which queries your GQL Server
56
- kwargs: optional arguments that will be passed to the query function
57
-
58
- Returns:
59
- AppInterfaceSettingCloudflareDNSQueryData: queried data parsed into generated classes
60
- """
61
- raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
62
- return AppInterfaceSettingCloudflareDNSQueryData(**raw_data)
@@ -1,193 +0,0 @@
1
- """
2
- Generated by qenerate plugin=pydantic_v2. DO NOT MODIFY MANUALLY!
3
- """
4
- from collections.abc import Callable # noqa: F401 # pylint: disable=W0611
5
- from datetime import datetime # noqa: F401 # pylint: disable=W0611
6
- from enum import Enum # noqa: F401 # pylint: disable=W0611
7
- from typing import ( # noqa: F401 # pylint: disable=W0611
8
- Any,
9
- Optional,
10
- Union,
11
- )
12
-
13
- from pydantic import ( # noqa: F401 # pylint: disable=W0611
14
- BaseModel,
15
- ConfigDict,
16
- Field,
17
- Json,
18
- )
19
-
20
- from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
21
-
22
-
23
- DEFINITION = """
24
- fragment VaultSecret on VaultSecret_v1 {
25
- path
26
- field
27
- version
28
- format
29
- }
30
-
31
- query CloudflareDnsZone {
32
- zones: cloudflare_dns_zone_v1 {
33
- identifier
34
- zone
35
- account {
36
- name
37
- type
38
- description
39
- providerVersion
40
- enforceTwofactor
41
- apiCredentials {
42
- ...VaultSecret
43
- }
44
- terraformStateAccount {
45
- name
46
- consoleUrl
47
- terraformUsername
48
- automationToken {
49
- ...VaultSecret
50
- }
51
- terraformState {
52
- provider
53
- bucket
54
- region
55
- integrations {
56
- integration
57
- key
58
- }
59
- }
60
- }
61
- deletionApprovals {
62
- expiration
63
- name
64
- type
65
- }
66
- }
67
- records {
68
- identifier
69
- name
70
- type
71
- ttl
72
- value
73
- priority
74
- proxied
75
- data {
76
- algorithm
77
- protocol
78
- public_key
79
- digest_type
80
- digest
81
- key_tag
82
- flags
83
- tag
84
- value
85
- }
86
- }
87
- type
88
- plan
89
- max_records
90
- delete
91
- }
92
- }
93
- """
94
-
95
-
96
- class ConfiguredBaseModel(BaseModel):
97
- model_config = ConfigDict(
98
- extra='forbid'
99
- )
100
-
101
-
102
- class AWSTerraformStateIntegrationsV1(ConfiguredBaseModel):
103
- integration: str = Field(..., alias="integration")
104
- key: str = Field(..., alias="key")
105
-
106
-
107
- class TerraformStateAWSV1(ConfiguredBaseModel):
108
- provider: str = Field(..., alias="provider")
109
- bucket: str = Field(..., alias="bucket")
110
- region: str = Field(..., alias="region")
111
- integrations: list[AWSTerraformStateIntegrationsV1] = Field(..., alias="integrations")
112
-
113
-
114
- class AWSAccountV1(ConfiguredBaseModel):
115
- name: str = Field(..., alias="name")
116
- console_url: str = Field(..., alias="consoleUrl")
117
- terraform_username: Optional[str] = Field(..., alias="terraformUsername")
118
- automation_token: VaultSecret = Field(..., alias="automationToken")
119
- terraform_state: Optional[TerraformStateAWSV1] = Field(..., alias="terraformState")
120
-
121
-
122
- class DeletionApprovalV1(ConfiguredBaseModel):
123
- expiration: str = Field(..., alias="expiration")
124
- name: str = Field(..., alias="name")
125
- q_type: str = Field(..., alias="type")
126
-
127
-
128
- class CloudflareAccountV1(ConfiguredBaseModel):
129
- name: str = Field(..., alias="name")
130
- q_type: Optional[str] = Field(..., alias="type")
131
- description: Optional[str] = Field(..., alias="description")
132
- provider_version: str = Field(..., alias="providerVersion")
133
- enforce_twofactor: Optional[bool] = Field(..., alias="enforceTwofactor")
134
- api_credentials: VaultSecret = Field(..., alias="apiCredentials")
135
- terraform_state_account: AWSAccountV1 = Field(..., alias="terraformStateAccount")
136
- deletion_approvals: Optional[list[DeletionApprovalV1]] = Field(..., alias="deletionApprovals")
137
-
138
-
139
- class CloudflareDnsRecordDataSettingsV1(ConfiguredBaseModel):
140
- algorithm: Optional[int] = Field(..., alias="algorithm")
141
- protocol: Optional[int] = Field(..., alias="protocol")
142
- public_key: Optional[str] = Field(..., alias="public_key")
143
- digest_type: Optional[int] = Field(..., alias="digest_type")
144
- digest: Optional[str] = Field(..., alias="digest")
145
- key_tag: Optional[int] = Field(..., alias="key_tag")
146
- flags: Optional[int] = Field(..., alias="flags")
147
- tag: Optional[str] = Field(..., alias="tag")
148
- value: Optional[str] = Field(..., alias="value")
149
-
150
-
151
- class CloudflareDnsRecordV1(ConfiguredBaseModel):
152
- identifier: str = Field(..., alias="identifier")
153
- name: str = Field(..., alias="name")
154
- q_type: str = Field(..., alias="type")
155
- ttl: int = Field(..., alias="ttl")
156
- value: Optional[str] = Field(..., alias="value")
157
- priority: Optional[int] = Field(..., alias="priority")
158
- proxied: Optional[bool] = Field(..., alias="proxied")
159
- data: Optional[CloudflareDnsRecordDataSettingsV1] = Field(..., alias="data")
160
-
161
-
162
- class CloudflareDnsZoneV1(ConfiguredBaseModel):
163
- identifier: str = Field(..., alias="identifier")
164
- zone: str = Field(..., alias="zone")
165
- account: CloudflareAccountV1 = Field(..., alias="account")
166
- records: Optional[list[CloudflareDnsRecordV1]] = Field(..., alias="records")
167
- q_type: Optional[str] = Field(..., alias="type")
168
- plan: Optional[str] = Field(..., alias="plan")
169
- max_records: Optional[int] = Field(..., alias="max_records")
170
- delete: Optional[bool] = Field(..., alias="delete")
171
-
172
-
173
- class CloudflareDnsZoneQueryData(ConfiguredBaseModel):
174
- zones: Optional[list[CloudflareDnsZoneV1]] = Field(..., alias="zones")
175
-
176
-
177
- def query(query_func: Callable, **kwargs: Any) -> CloudflareDnsZoneQueryData:
178
- """
179
- This is a convenience function which queries and parses the data into
180
- concrete types. It should be compatible with most GQL clients.
181
- You do not have to use it to consume the generated data classes.
182
- Alternatively, you can also mime and alternate the behavior
183
- of this function in the caller.
184
-
185
- Parameters:
186
- query_func (Callable): Function which queries your GQL Server
187
- kwargs: optional arguments that will be passed to the query function
188
-
189
- Returns:
190
- CloudflareDnsZoneQueryData: queried data parsed into generated classes
191
- """
192
- raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
193
- return CloudflareDnsZoneQueryData(**raw_data)
@@ -1,127 +0,0 @@
1
- """
2
- Generated by qenerate plugin=pydantic_v2. DO NOT MODIFY MANUALLY!
3
- """
4
- from collections.abc import Callable # noqa: F401 # pylint: disable=W0611
5
- from datetime import datetime # noqa: F401 # pylint: disable=W0611
6
- from enum import Enum # noqa: F401 # pylint: disable=W0611
7
- from typing import ( # noqa: F401 # pylint: disable=W0611
8
- Any,
9
- Optional,
10
- Union,
11
- )
12
-
13
- from pydantic import ( # noqa: F401 # pylint: disable=W0611
14
- BaseModel,
15
- ConfigDict,
16
- Field,
17
- Json,
18
- )
19
-
20
- from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
21
-
22
-
23
- DEFINITION = """
24
- fragment VaultSecret on VaultSecret_v1 {
25
- path
26
- field
27
- version
28
- format
29
- }
30
-
31
- query TerraformCloudflareAccounts {
32
- accounts: cloudflare_accounts_v1 {
33
- name
34
- description
35
- providerVersion
36
- apiCredentials {
37
- ...VaultSecret
38
- }
39
- terraformStateAccount {
40
- name
41
- automationToken {
42
- ...VaultSecret
43
- }
44
- terraformState {
45
- provider
46
- bucket
47
- region
48
- integrations {
49
- integration
50
- key
51
- }
52
- }
53
- }
54
- deletionApprovals {
55
- expiration
56
- name
57
- type
58
- }
59
- enforceTwofactor
60
- type
61
- }
62
- }
63
- """
64
-
65
-
66
- class ConfiguredBaseModel(BaseModel):
67
- model_config = ConfigDict(
68
- extra='forbid'
69
- )
70
-
71
-
72
- class AWSTerraformStateIntegrationsV1(ConfiguredBaseModel):
73
- integration: str = Field(..., alias="integration")
74
- key: str = Field(..., alias="key")
75
-
76
-
77
- class TerraformStateAWSV1(ConfiguredBaseModel):
78
- provider: str = Field(..., alias="provider")
79
- bucket: str = Field(..., alias="bucket")
80
- region: str = Field(..., alias="region")
81
- integrations: list[AWSTerraformStateIntegrationsV1] = Field(..., alias="integrations")
82
-
83
-
84
- class AWSAccountV1(ConfiguredBaseModel):
85
- name: str = Field(..., alias="name")
86
- automation_token: VaultSecret = Field(..., alias="automationToken")
87
- terraform_state: Optional[TerraformStateAWSV1] = Field(..., alias="terraformState")
88
-
89
-
90
- class DeletionApprovalV1(ConfiguredBaseModel):
91
- expiration: str = Field(..., alias="expiration")
92
- name: str = Field(..., alias="name")
93
- q_type: str = Field(..., alias="type")
94
-
95
-
96
- class CloudflareAccountV1(ConfiguredBaseModel):
97
- name: str = Field(..., alias="name")
98
- description: Optional[str] = Field(..., alias="description")
99
- provider_version: str = Field(..., alias="providerVersion")
100
- api_credentials: VaultSecret = Field(..., alias="apiCredentials")
101
- terraform_state_account: AWSAccountV1 = Field(..., alias="terraformStateAccount")
102
- deletion_approvals: Optional[list[DeletionApprovalV1]] = Field(..., alias="deletionApprovals")
103
- enforce_twofactor: Optional[bool] = Field(..., alias="enforceTwofactor")
104
- q_type: Optional[str] = Field(..., alias="type")
105
-
106
-
107
- class TerraformCloudflareAccountsQueryData(ConfiguredBaseModel):
108
- accounts: Optional[list[CloudflareAccountV1]] = Field(..., alias="accounts")
109
-
110
-
111
- def query(query_func: Callable, **kwargs: Any) -> TerraformCloudflareAccountsQueryData:
112
- """
113
- This is a convenience function which queries and parses the data into
114
- concrete types. It should be compatible with most GQL clients.
115
- You do not have to use it to consume the generated data classes.
116
- Alternatively, you can also mime and alternate the behavior
117
- of this function in the caller.
118
-
119
- Parameters:
120
- query_func (Callable): Function which queries your GQL Server
121
- kwargs: optional arguments that will be passed to the query function
122
-
123
- Returns:
124
- TerraformCloudflareAccountsQueryData: queried data parsed into generated classes
125
- """
126
- raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
127
- return TerraformCloudflareAccountsQueryData(**raw_data)