qontract-reconcile 0.10.1rc405__py3-none-any.whl → 0.10.1rc407__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qontract-reconcile
3
- Version: 0.10.1rc405
3
+ Version: 0.10.1rc407
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Home-page: https://github.com/app-sre/qontract-reconcile
6
6
  Author: Red Hat App-SRE Team
@@ -105,7 +105,7 @@ reconcile/status_board.py,sha256=xOKicOelyGv3h2sFJxa9FBj8HDroZyGhMaRWaNRGpQQ,810
105
105
  reconcile/template_tester.py,sha256=vZz8GM46waQUGd3OVnhW5OLTqctFMH_Hh1QXxT5hduM,2384
106
106
  reconcile/terraform_aws_route53.py,sha256=QJMlaYYwcw_N9TJu7VbgZfxrxxR8Wv62GuG05XVhSuE,10018
107
107
  reconcile/terraform_cloudflare_dns.py,sha256=auU4bzeLwd4S8D8oqpqJbrCUoEdELXrgi7vHOedjYFk,13332
108
- reconcile/terraform_cloudflare_resources.py,sha256=m4Xg3xmHa7XjbUEvAbg0a3BeRSnPLe-4CLUJPlQiTdc,15012
108
+ reconcile/terraform_cloudflare_resources.py,sha256=EbQQaoDnZ7brvRCpbFtwlD7KLk2hDVNcjhrJGaAywEk,15023
109
109
  reconcile/terraform_cloudflare_users.py,sha256=DfeSnYC9YQgXX6AbJh85tQbJUDv1e2FjiGXgcpVQlPg,13964
110
110
  reconcile/terraform_repo.py,sha256=fIVjcxqhG6DR6H8QtQM1RpE9ukanjkjp8iH0tQRhEl8,13042
111
111
  reconcile/terraform_resources.py,sha256=WUM0ZEpf2jgcXjfjy3QSwrzpuD6zMN7cyOxetx88f9g,17167
@@ -261,7 +261,7 @@ reconcile/gql_definitions/slack_usergroups/clusters.py,sha256=EsnwVAGFtuev787toH
261
261
  reconcile/gql_definitions/slack_usergroups/permissions.py,sha256=fhBQT2uwCi2N5ZEIZhzrzjVvNllp12R0ZS1AVVEM2dA,5519
262
262
  reconcile/gql_definitions/slack_usergroups/users.py,sha256=YS6PkZAHy2ZzECTvdO3F6iHGRcEdOswCzsRjifjpMvI,2877
263
263
  reconcile/gql_definitions/slo_documents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
264
- reconcile/gql_definitions/slo_documents/slo_documents.py,sha256=JYtOR4MYMA4nDYJcoWJfSqju_E7GVs8ooAiAr7k8Dq4,3558
264
+ reconcile/gql_definitions/slo_documents/slo_documents.py,sha256=RYbSXYER_0szI3ue0EnhtUeYQr8Yuce5oSyAcCYbrF0,3750
265
265
  reconcile/gql_definitions/status_board/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
266
266
  reconcile/gql_definitions/status_board/status_board.py,sha256=1UU0YDj1Y2mALlGOZb1ecHHDmNpO5BANFCqWXQukPiw,4746
267
267
  reconcile/gql_definitions/statuspage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -404,8 +404,8 @@ reconcile/test/test_sql_query.py,sha256=rC-lf1_isT9i2ZIV9W0hkUkLi2oBIjZMRMhk-6mV
404
404
  reconcile/test/test_status_board.py,sha256=WdAq4pFoWWqcOcfgMzssZD3xfvT1QLrEHJqUARldtvA,7875
405
405
  reconcile/test/test_terraform_aws_route53.py,sha256=xHggb8K1P76OyCfFcogbkmyKle-NlUylcbDnuv3IqvY,771
406
406
  reconcile/test/test_terraform_cloudflare_dns.py,sha256=aQTXX8Vr4h9aWvJZTnpZEhMGYoBpT2d45ZxU_ECIQ6o,3425
407
- reconcile/test/test_terraform_cloudflare_resources.py,sha256=cWNE2UIhz19rLSWdpJG8xRwuEEYoIZWEkDZY7e2QN_g,3426
408
- reconcile/test/test_terraform_cloudflare_users.py,sha256=BEC4RCuuR6b3L-KR7cakEcOasPR7XJ7Mca0BnVbawvg,27480
407
+ reconcile/test/test_terraform_cloudflare_resources.py,sha256=D-3yvANpQMW_hmxTthsUvRTuyguqCqLJoLaDX1THDks,9351
408
+ reconcile/test/test_terraform_cloudflare_users.py,sha256=RAFtMMdqZha3jNnNNsqbNQQUDSqUzdoM63rCw7fs4Fo,27456
409
409
  reconcile/test/test_terraform_repo.py,sha256=E8Ilo9KCJh4kkUebGUoHZKxYQXh83X84U32CfUAsa28,11109
410
410
  reconcile/test/test_terraform_resources.py,sha256=1ny_QSFuRjV9jxZY8EeT4NVJ5dMv7cLrEEIx_cBpjgk,9075
411
411
  reconcile/test/test_terraform_tgw_attachments.py,sha256=GgDA8hlQ1ujh5g8PtzbYQbJGpNScEgZ8PvDbMFbn68g,35493
@@ -611,7 +611,7 @@ tools/app_interface_metrics_exporter.py,sha256=zkwkxdAUAxjdc-pzx2_oJXG25fo0Fnyd5
611
611
  tools/app_interface_reporter.py,sha256=8HUH8kkW50-plKgiTgCiFINJNN_cl4ZFzmY4aSnRaNk,18270
612
612
  tools/glitchtip_access_reporter.py,sha256=oPBnk_YoDuljU3v0FaChzOwwnk4vap1xEE67QEjzdqs,2948
613
613
  tools/glitchtip_access_revalidation.py,sha256=8kbBJk04mkq28kWoRDDkfCGIF3GRg3pJrFAh1sW0dbk,2821
614
- tools/qontract_cli.py,sha256=xEMgcF90wD9gak8JbYemKog0alh_usFhwwvyB356Ukw,97319
614
+ tools/qontract_cli.py,sha256=RvUddiApdgZjDSw3bTm6jP4YxPHzEu-ojK2ZQ5qfZEk,98304
615
615
  tools/sd_app_sre_alert_report.py,sha256=e9vAdyenUz2f5c8-z-5WY0wv-SJ9aePKDH2r4IwB6pc,5063
616
616
  tools/cli_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
617
617
  tools/cli_commands/gpg_encrypt.py,sha256=JryinrDdvztN931enUY3FuDeLVnfs6y58mnK7itNK6Y,4940
@@ -622,8 +622,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=dmEcNwZltP1rd_4DbxIYakO
622
622
  tools/test/test_qontract_cli.py,sha256=awwTHEc2DWlykuqGIYM0WOBoSL0KRnOraCLk3C7izis,1401
623
623
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
624
624
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
625
- qontract_reconcile-0.10.1rc405.dist-info/METADATA,sha256=bIqWFa2YEDDCInJCzun3qZNNLuo-Ok-3ib1HmGEXccg,2347
626
- qontract_reconcile-0.10.1rc405.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
627
- qontract_reconcile-0.10.1rc405.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
628
- qontract_reconcile-0.10.1rc405.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
629
- qontract_reconcile-0.10.1rc405.dist-info/RECORD,,
625
+ qontract_reconcile-0.10.1rc407.dist-info/METADATA,sha256=NcDwbiWeb552Q9aFx1UTWckd3qNwj3Mj0yuMKqzhCeo,2347
626
+ qontract_reconcile-0.10.1rc407.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
627
+ qontract_reconcile-0.10.1rc407.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
628
+ qontract_reconcile-0.10.1rc407.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
629
+ qontract_reconcile-0.10.1rc407.dist-info/RECORD,,
@@ -25,6 +25,9 @@ query SloDocuments {
25
25
  labels
26
26
  app {
27
27
  name
28
+ parentApp {
29
+ name
30
+ }
28
31
  }
29
32
  namespaces {
30
33
  namespace {
@@ -62,8 +65,13 @@ class ConfiguredBaseModel(BaseModel):
62
65
  extra = Extra.forbid
63
66
 
64
67
 
68
+ class AppV1_AppV1(ConfiguredBaseModel):
69
+ name: str = Field(..., alias="name")
70
+
71
+
65
72
  class AppV1(ConfiguredBaseModel):
66
73
  name: str = Field(..., alias="name")
74
+ parent_app: Optional[AppV1_AppV1] = Field(..., alias="parentApp")
67
75
 
68
76
 
69
77
  class ClusterV1(ConfiguredBaseModel):
@@ -342,7 +342,7 @@ def run(
342
342
  )
343
343
 
344
344
  if not cloudflare_namespaces:
345
- logging.info("No namespaces were detected, nothing to do.")
345
+ logging.info("No cloudflare namespaces were detected, nothing to do.")
346
346
  sys.exit(ExitCodes.SUCCESS)
347
347
 
348
348
  # Build Cloudflare clients
@@ -1,11 +1,31 @@
1
+ import logging
2
+
1
3
  import pytest
2
4
 
3
- from reconcile.gql_definitions.terraform_cloudflare_dns.terraform_cloudflare_zones import (
4
- CloudflareDnsRecordV1,
5
+ import reconcile.terraform_cloudflare_resources as integ
6
+ from reconcile.gql_definitions.common.app_interface_vault_settings import (
7
+ AppInterfaceSettingsV1,
8
+ )
9
+ from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
10
+ from reconcile.gql_definitions.terraform_cloudflare_resources.terraform_cloudflare_accounts import (
11
+ AWSAccountV1,
12
+ AWSTerraformStateIntegrationsV1,
13
+ )
14
+ from reconcile.gql_definitions.terraform_cloudflare_resources.terraform_cloudflare_accounts import (
15
+ CloudflareAccountV1 as CFAccountV1,
16
+ )
17
+ from reconcile.gql_definitions.terraform_cloudflare_resources.terraform_cloudflare_accounts import (
18
+ DeletionApprovalV1,
19
+ TerraformCloudflareAccountsQueryData,
20
+ TerraformStateAWSV1,
5
21
  )
6
22
  from reconcile.gql_definitions.terraform_cloudflare_resources.terraform_cloudflare_resources import (
7
23
  CloudflareAccountV1,
24
+ CloudflareDnsRecordV1,
25
+ CloudflareZoneArgoV1,
26
+ CloudflareZoneCacheReserveV1,
8
27
  CloudflareZoneCertificateV1,
28
+ CloudflareZoneTieredCacheV1,
9
29
  CloudflareZoneWorkerV1,
10
30
  ClusterV1,
11
31
  NamespaceTerraformProviderResourceCloudflareV1,
@@ -60,16 +80,14 @@ def external_resources(provisioner_config):
60
80
  plan="enterprise",
61
81
  type="full",
62
82
  settings='{"foo": "bar"}',
63
- argo={
64
- "tiered_caching": True,
65
- "smart_routing": True,
66
- },
67
- tiered_cache={
68
- "cache_type": "smart",
69
- },
70
- cache_reserve={
71
- "enabled": True,
72
- },
83
+ argo=CloudflareZoneArgoV1(
84
+ tiered_caching=True,
85
+ smart_routing=True,
86
+ ),
87
+ tiered_cache=CloudflareZoneTieredCacheV1(
88
+ cache_type="smart",
89
+ ),
90
+ cache_reserve=CloudflareZoneCacheReserveV1(enabled=True),
73
91
  records=[
74
92
  CloudflareDnsRecordV1(
75
93
  name="record",
@@ -78,8 +96,6 @@ def external_resources(provisioner_config):
78
96
  value="example.com",
79
97
  proxied=False,
80
98
  identifier="record",
81
- priority=None,
82
- data=None,
83
99
  ),
84
100
  ],
85
101
  workers=[
@@ -104,3 +120,154 @@ def external_resources(provisioner_config):
104
120
  ),
105
121
  ],
106
122
  )
123
+
124
+
125
+ @pytest.fixture
126
+ def mock_gql(mocker):
127
+ mocker.patch("reconcile.terraform_cloudflare_resources.gql", autospec=True)
128
+
129
+
130
+ @pytest.fixture
131
+ def mock_vault_secret(mocker):
132
+ mocked_vault_secret = mocker.patch(
133
+ "reconcile.terraform_cloudflare_resources.get_app_interface_vault_settings",
134
+ autospec=True,
135
+ )
136
+ mocked_vault_secret.return_value = AppInterfaceSettingsV1(vault=False)
137
+
138
+
139
+ @pytest.fixture
140
+ def mock_cloudflare_accounts(mocker):
141
+ mocked_cloudflare_accounts = mocker.patch(
142
+ "reconcile.terraform_cloudflare_resources.terraform_cloudflare_accounts",
143
+ autospec=True,
144
+ )
145
+ mocked_cloudflare_accounts.query.return_value = (
146
+ TerraformCloudflareAccountsQueryData(
147
+ accounts=[
148
+ CFAccountV1(
149
+ name="cfaccount",
150
+ providerVersion="0.33.x",
151
+ apiCredentials=VaultSecret(
152
+ path="somepath",
153
+ field="key",
154
+ version=1,
155
+ format="??",
156
+ ),
157
+ terraformStateAccount=AWSAccountV1(
158
+ name="awsaccoutn",
159
+ automationToken=VaultSecret(
160
+ path="someotherpath",
161
+ field="token",
162
+ version=1,
163
+ format="",
164
+ ),
165
+ terraformState=TerraformStateAWSV1(
166
+ provider="",
167
+ bucket="",
168
+ region="",
169
+ integrations=[
170
+ AWSTerraformStateIntegrationsV1(
171
+ integration="terraform-cloudflare-resources", key=""
172
+ )
173
+ ],
174
+ ),
175
+ ),
176
+ deletionApprovals=[
177
+ DeletionApprovalV1(expiration="", name="", type="")
178
+ ],
179
+ enforceTwofactor=False,
180
+ type="?????",
181
+ )
182
+ ]
183
+ )
184
+ )
185
+
186
+
187
+ @pytest.fixture
188
+ def mock_cloudflare_resources(mocker, external_resources):
189
+ mocked_cloudflare_resources = mocker.patch(
190
+ "reconcile.terraform_cloudflare_resources.terraform_cloudflare_resources",
191
+ autospec=True,
192
+ )
193
+ mocked_cloudflare_resources.query.return_value = external_resources
194
+
195
+
196
+ def test_cloudflare_accounts_validation(
197
+ mocker, caplog, mock_gql, mock_vault_secret, mock_cloudflare_resources
198
+ ):
199
+ # Mocking accounts with an empty response
200
+ mocked_cloudflare_accounts = mocker.patch(
201
+ "reconcile.terraform_cloudflare_resources.terraform_cloudflare_accounts",
202
+ autospec=True,
203
+ )
204
+ mocked_cloudflare_accounts.query.return_value = (
205
+ TerraformCloudflareAccountsQueryData(accounts=[])
206
+ )
207
+
208
+ with caplog.at_level(logging.INFO), pytest.raises(SystemExit) as sample:
209
+ integ.run(True, None, False, 10)
210
+ assert sample.value.code == 0
211
+ assert ["No Cloudflare accounts were detected, nothing to do."] == [
212
+ rec.message for rec in caplog.records
213
+ ]
214
+
215
+
216
+ def test_namespace_validation(
217
+ mocker, caplog, mock_gql, mock_vault_secret, mock_cloudflare_accounts
218
+ ):
219
+ # Mocking resources without namespaces
220
+ mocked_resources = mocker.patch(
221
+ "reconcile.terraform_cloudflare_resources.terraform_cloudflare_resources",
222
+ autospec=True,
223
+ )
224
+
225
+ mocked_resources.query.return_value = TerraformCloudflareResourcesQueryData(
226
+ namespaces=[],
227
+ )
228
+
229
+ with caplog.at_level(logging.INFO), pytest.raises(SystemExit) as sample:
230
+ integ.run(True, None, False, 10)
231
+ assert sample.value.code == 0
232
+ assert ["No namespaces were detected, nothing to do."] == [
233
+ rec.message for rec in caplog.records
234
+ ]
235
+
236
+
237
+ def test_cloudflare_namespace_validation(
238
+ mocker, caplog, mock_gql, mock_vault_secret, mock_cloudflare_accounts
239
+ ):
240
+ # Mocking resources without cloudflare namespaces
241
+ mocked_resources = mocker.patch(
242
+ "reconcile.terraform_cloudflare_resources.terraform_cloudflare_resources",
243
+ autospec=True,
244
+ )
245
+
246
+ mocked_resources.query.return_value = TerraformCloudflareResourcesQueryData(
247
+ namespaces=[
248
+ NamespaceV1(
249
+ name="namespace1",
250
+ clusterAdmin=True,
251
+ cluster=ClusterV1(
252
+ name="test-cluster",
253
+ serverUrl="http://localhost",
254
+ insecureSkipTLSVerify=None,
255
+ jumpHost=None,
256
+ automationToken=None,
257
+ clusterAdminAutomationToken=None,
258
+ spec=None,
259
+ internal=None,
260
+ disable=None,
261
+ ),
262
+ managedExternalResources=True,
263
+ externalResources=[],
264
+ )
265
+ ],
266
+ )
267
+
268
+ with caplog.at_level(logging.INFO), pytest.raises(SystemExit) as sample:
269
+ integ.run(True, None, False, 10)
270
+ assert sample.value.code == 0
271
+ assert ["No cloudflare namespaces were detected, nothing to do."] == [
272
+ rec.message for rec in caplog.records
273
+ ]
@@ -668,7 +668,6 @@ def test_external_spec_with_two_roles_from_different_account_one_user(
668
668
  ["redhat.com"],
669
669
  )
670
670
 
671
- print(actual_users)
672
671
  expected_users = {
673
672
  "cloudflare-account-1": {
674
673
  "user1@redhat.com": CloudflareUser(
tools/qontract_cli.py CHANGED
@@ -66,6 +66,7 @@ from reconcile.gql_definitions.common.app_interface_vault_settings import (
66
66
  from reconcile.gql_definitions.fragments.aus_organization import AUSOCMOrganization
67
67
  from reconcile.jenkins_job_builder import init_jjb
68
68
  from reconcile.slack_base import slackapi_from_queries
69
+ from reconcile.status_board import StatusBoardExporterIntegration
69
70
  from reconcile.typed_queries.alerting_services_settings import get_alerting_services
70
71
  from reconcile.typed_queries.app_interface_vault_settings import (
71
72
  get_app_interface_vault_settings,
@@ -73,6 +74,7 @@ from reconcile.typed_queries.app_interface_vault_settings import (
73
74
  from reconcile.typed_queries.clusters import get_clusters
74
75
  from reconcile.typed_queries.saas_files import get_saas_files
75
76
  from reconcile.typed_queries.slo_documents import get_slo_documents
77
+ from reconcile.typed_queries.status_board import get_status_board
76
78
  from reconcile.utils import (
77
79
  amtool,
78
80
  config,
@@ -2211,8 +2213,9 @@ def ec2_jenkins_workers(ctx, aws_access_key_id, aws_secret_access_key, aws_regio
2211
2213
 
2212
2214
 
2213
2215
  @get.command()
2216
+ @click.argument("status-board-instance")
2214
2217
  @click.pass_context
2215
- def slo_document_services(ctx):
2218
+ def slo_document_services(ctx, status_board_instance):
2216
2219
  """Print SLO Documents Services"""
2217
2220
  columns = [
2218
2221
  "slo_doc_name",
@@ -2224,15 +2227,37 @@ def slo_document_services(ctx):
2224
2227
  "statusBoardEnabled",
2225
2228
  ]
2226
2229
 
2230
+ try:
2231
+ [sb] = [sb for sb in get_status_board() if sb.name == status_board_instance]
2232
+ except ValueError:
2233
+ print(f"Status-board instance '{status_board_instance}' not found.")
2234
+ sys.exit(1)
2235
+
2236
+ desired_product_apps: dict[
2237
+ str, set[str]
2238
+ ] = StatusBoardExporterIntegration.get_product_apps(sb)
2239
+
2227
2240
  slodocs = []
2228
2241
  for slodoc in get_slo_documents():
2229
2242
  products = [ns.namespace.environment.product.name for ns in slodoc.namespaces]
2230
2243
  for slo in slodoc.slos:
2231
2244
  for product in products:
2245
+ if slodoc.app.parent_app:
2246
+ app = f"{slodoc.app.parent_app.name}-{slodoc.app.name}"
2247
+ else:
2248
+ app = slodoc.app.name
2249
+
2250
+ # Skip if the (product, app) is not being generated by the status-board inventory
2251
+ if (
2252
+ product not in desired_product_apps
2253
+ or app not in desired_product_apps[product]
2254
+ ):
2255
+ continue
2256
+
2232
2257
  item = {
2233
2258
  "slo_doc_name": slodoc.name,
2234
2259
  "product": product,
2235
- "app": slodoc.app.name,
2260
+ "app": app,
2236
2261
  "slo": slo.name,
2237
2262
  "target": slo.slo_target,
2238
2263
  "window": slo.slo_parameters.window,