qontract-reconcile 0.10.1rc528__py3-none-any.whl → 0.10.1rc530__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.1rc528
3
+ Version: 0.10.1rc530
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
@@ -303,7 +303,7 @@ reconcile/gql_definitions/terraform_repo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5
303
303
  reconcile/gql_definitions/terraform_repo/terraform_repo.py,sha256=pefTRhb0ZcWm_j6dYz6m6qNqZFteqgrQ25SgUqRAVaI,3173
304
304
  reconcile/gql_definitions/terraform_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
305
305
  reconcile/gql_definitions/terraform_resources/database_access_manager.py,sha256=yv0_YC-LmhaKD_gyGG3le1w5BtypBjlsO894-Zgdg4U,4813
306
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py,sha256=czniF5fsJLsJuILI5FYr38yqnr90J3VLRe4tB4hSqio,40573
306
+ reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py,sha256=GMV0sch1Mdznjd3Vwgj_gMS1fb56d5xa2dFtOM57yUA,41126
307
307
  reconcile/gql_definitions/terraform_tgw_attachments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
308
308
  reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py,sha256=GjCuLHOgm3eHbkpK7Q2i7l6tori5Y62uFlz3M89BYtA,2602
309
309
  reconcile/gql_definitions/vault_instances/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -535,7 +535,7 @@ reconcile/utils/filtering.py,sha256=zZnHH0u0SaTDyzuFXZ_mREURGLvjEqQIQy4z-7QBVlc,
535
535
  reconcile/utils/git.py,sha256=Qad7mfPuS9s7eKODeWSewehwSGgJPCbQuLda1qg_6GA,1522
536
536
  reconcile/utils/git_secrets.py,sha256=0wGNL5mvDtVPRuu3vEQgld1Am64gIDJHtmu1_ZKxMAI,1973
537
537
  reconcile/utils/github_api.py,sha256=_bttNxYKeam_tLVe27L7O4gKqSn6CeyuFnJn8tSaUVY,2488
538
- reconcile/utils/gitlab_api.py,sha256=4AYzYQYJtAYY_9uAU4lHnyKl9Xkreu5CDaqRkWMoOSQ,26121
538
+ reconcile/utils/gitlab_api.py,sha256=7gImzen-LzEcdyTbRNcvuLOczzm03sEI78NmKBSMAl4,27228
539
539
  reconcile/utils/gpg.py,sha256=EKG7_fdMv8BMlV5yUdPiqoTx-KrzmVSEAl2sLkaKwWI,1123
540
540
  reconcile/utils/gql.py,sha256=bzIYYYYGIO_4Db4sA-mnZUOPVNBELZD5EcIUSTbYG1o,13604
541
541
  reconcile/utils/grouping.py,sha256=kWKivD14eAkiDneH_VIl_XyUdcVVQgiaKA9sLsuD2dw,441
@@ -581,7 +581,7 @@ reconcile/utils/state.py,sha256=SAa6QLHu9lr0yqLCBy2AypNx1IPCJWlrRBrvlzAKsOU,1450
581
581
  reconcile/utils/structs.py,sha256=LcbLEg8WxfRqM6nW7NhcWN0YeqF7SQzxOgntmLs1SgY,352
582
582
  reconcile/utils/template.py,sha256=wTvRU4AnAV_o042tD4Mwls2dwWMuk7MKnde3MaCjaYg,331
583
583
  reconcile/utils/terraform_client.py,sha256=V7AMQOEU4tvUOT-LQN2cXLqcphD5L93PMGMfurQQyPY,31753
584
- reconcile/utils/terrascript_aws_client.py,sha256=XNhTfvPtaxWE0MpWDJxUW5_PtPaBaijcTNr7lD86L88,264470
584
+ reconcile/utils/terrascript_aws_client.py,sha256=wA6AcAqbS2tIuqh8soMfpdpuf0IbAJWjONMmffJ-0vA,265467
585
585
  reconcile/utils/three_way_diff_strategy.py,sha256=nyqeQsLCoPI6e16k2CF3b9KNgQLU-rPf5RtfdUfVMwE,4468
586
586
  reconcile/utils/throughput.py,sha256=iP4UWAe2LVhDo69mPPmgo9nQ7RxHD6_GS8MZe-aSiuM,344
587
587
  reconcile/utils/unleash.py,sha256=1D56CsZfE3ShDtN3IErE1T2eeIwNmxhK-yYbCotJ99E,3601
@@ -668,8 +668,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
668
668
  tools/test/test_qontract_cli.py,sha256=d18KrdhtUGqoC7_kWZU128U0-VJEj-0rjFkLVufcI6I,2755
669
669
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
670
670
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
671
- qontract_reconcile-0.10.1rc528.dist-info/METADATA,sha256=wbE04VOAGLnR3YKFZ9BmP08N32wMPLDE5nG2e6KEINo,2349
672
- qontract_reconcile-0.10.1rc528.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
673
- qontract_reconcile-0.10.1rc528.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
674
- qontract_reconcile-0.10.1rc528.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
675
- qontract_reconcile-0.10.1rc528.dist-info/RECORD,,
671
+ qontract_reconcile-0.10.1rc530.dist-info/METADATA,sha256=vBKtmSp2hYuK2N6ks8hGTMvpT7L7s6ZFD3yMu1NpS8w,2349
672
+ qontract_reconcile-0.10.1rc530.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
673
+ qontract_reconcile-0.10.1rc530.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
674
+ qontract_reconcile-0.10.1rc530.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
675
+ qontract_reconcile-0.10.1rc530.dist-info/RECORD,,
@@ -168,6 +168,11 @@ query TerraformResourcesNamespaces {
168
168
  role_policy
169
169
  output_resource_name
170
170
  annotations
171
+ lifecycle {
172
+ create_before_destroy
173
+ prevent_destroy
174
+ ignore_changes
175
+ }
171
176
  }
172
177
  ... on NamespaceTerraformResourceSQS_v1 {
173
178
  region
@@ -614,6 +619,12 @@ class AssumeRoleV1(ConfiguredBaseModel):
614
619
  federated: Optional[str] = Field(..., alias="Federated")
615
620
 
616
621
 
622
+ class NamespaceTerraformResourceLifecycleV1(ConfiguredBaseModel):
623
+ create_before_destroy: Optional[bool] = Field(..., alias="create_before_destroy")
624
+ prevent_destroy: Optional[bool] = Field(..., alias="prevent_destroy")
625
+ ignore_changes: Optional[list[str]] = Field(..., alias="ignore_changes")
626
+
627
+
617
628
  class NamespaceTerraformResourceRoleV1(NamespaceTerraformResourceAWSV1):
618
629
  identifier: str = Field(..., alias="identifier")
619
630
  assume_role: AssumeRoleV1 = Field(..., alias="assume_role")
@@ -623,6 +634,7 @@ class NamespaceTerraformResourceRoleV1(NamespaceTerraformResourceAWSV1):
623
634
  role_policy: Optional[str] = Field(..., alias="role_policy")
624
635
  output_resource_name: Optional[str] = Field(..., alias="output_resource_name")
625
636
  annotations: Optional[str] = Field(..., alias="annotations")
637
+ lifecycle: Optional[NamespaceTerraformResourceLifecycleV1] = Field(..., alias="lifecycle")
626
638
 
627
639
 
628
640
  class KeyValueV1(ConfiguredBaseModel):
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  import os
3
+ import re
3
4
  from collections.abc import (
4
5
  Iterable,
5
6
  Set,
@@ -12,6 +13,7 @@ from operator import (
12
13
  from typing import (
13
14
  Any,
14
15
  Optional,
16
+ TypedDict,
15
17
  )
16
18
  from urllib.parse import urlparse
17
19
 
@@ -19,6 +21,8 @@ import gitlab
19
21
  import urllib3
20
22
  from gitlab.v4.objects import (
21
23
  CurrentUser,
24
+ Group,
25
+ Project,
22
26
  ProjectIssue,
23
27
  ProjectMergeRequest,
24
28
  ProjectMergeRequestNote,
@@ -66,12 +70,19 @@ class MRStatus:
66
70
  CANNOT_BE_MERGED_RECHECK = "cannot_be_merged_recheck"
67
71
 
68
72
 
73
+ GROUP_BOT_NAME_REGEX = re.compile(r"group_.+_bot_.+")
74
+
75
+
76
+ class GLGroupMember(TypedDict):
77
+ user: str
78
+ access_level: str
79
+
80
+
69
81
  class GitLabApi: # pylint: disable=too-many-public-methods
70
82
  def __init__(
71
83
  self,
72
84
  instance,
73
85
  project_id=None,
74
- ssl_verify=True,
75
86
  settings=None,
76
87
  secret_reader=None,
77
88
  project_url=None,
@@ -205,14 +216,14 @@ class GitLabApi: # pylint: disable=too-many-public-methods
205
216
  gitlab_request.labels(integration=INTEGRATION_NAME).inc()
206
217
  return self.project.mergerequests.create(data)
207
218
 
208
- def mr_exists(self, title):
219
+ def mr_exists(self, title: str) -> bool:
209
220
  mrs = self.get_merge_requests(state=MRState.OPENED)
210
221
  # since we are using a naming convention for these MRs
211
222
  # we can determine if a pending MR exists based on the title
212
223
  return any(mr.title == title for mr in mrs)
213
224
 
214
225
  @retry()
215
- def get_project_maintainers(self, repo_url=None):
226
+ def get_project_maintainers(self, repo_url: str | None = None) -> list[str] | None:
216
227
  if repo_url is None:
217
228
  project = self.project
218
229
  else:
@@ -227,25 +238,42 @@ class GitLabApi: # pylint: disable=too-many-public-methods
227
238
  app_sre_group = self.gl.groups.get("app-sre")
228
239
  return self.get_items(app_sre_group.members.list)
229
240
 
230
- def get_group_if_exists(self, group_name):
241
+ def get_group_if_exists(self, group_name: str) -> Group | None:
231
242
  gitlab_request.labels(integration=INTEGRATION_NAME).inc()
232
243
  try:
233
244
  return self.gl.groups.get(group_name)
234
245
  except gitlab.exceptions.GitlabGetError:
235
246
  return None
236
247
 
237
- def get_group_members(self, group_name):
248
+ @staticmethod
249
+ def _is_bot_username(username: str) -> bool:
250
+ """crudely checking for the username
251
+
252
+ as gitlab-python require a major upgrade to use the billable members apis
253
+ https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#id11 lists the api
254
+ billable_membersis the attribute that provides billable members of groups
255
+
256
+ the second api is https://python-gitlab.readthedocs.io/en/stable/gl_objects/group_access_tokens.html
257
+ which provides a list of access tokens as well as their assigned users
258
+
259
+ those apis are not avaliable in python-gitlab v1.x
260
+ """
261
+ return GROUP_BOT_NAME_REGEX.match(username) is not None
262
+
263
+ def get_group_members(self, group_name: str) -> list[GLGroupMember]:
238
264
  group = self.get_group_if_exists(group_name)
239
- if not group:
265
+ if group is None:
240
266
  logging.error(group_name + " group not found")
241
267
  return []
242
- return [
243
- {
244
- "user": m.username,
245
- "access_level": self.get_access_level_string(m.access_level),
246
- }
247
- for m in self.get_items(group.members.list)
248
- ]
268
+ else:
269
+ return [
270
+ {
271
+ "user": m.username,
272
+ "access_level": self.get_access_level_string(m.access_level),
273
+ }
274
+ for m in self.get_items(group.members.list)
275
+ if not self._is_bot_username(m.username)
276
+ ]
249
277
 
250
278
  def add_project_member(self, repo_url, user, access="maintainer"):
251
279
  project = self.get_project(repo_url)
@@ -323,7 +351,7 @@ class GitLabApi: # pylint: disable=too-many-public-methods
323
351
  if access == "guest":
324
352
  return gitlab.GUEST_ACCESS
325
353
 
326
- def get_group_id_and_projects(self, group_name):
354
+ def get_group_id_and_projects(self, group_name: str) -> tuple[str, list[str]]:
327
355
  gitlab_request.labels(integration=INTEGRATION_NAME).inc()
328
356
  group = self.gl.groups.get(group_name)
329
357
  return group.id, [p.name for p in self.get_items(group.projects.list)]
@@ -336,7 +364,7 @@ class GitLabApi: # pylint: disable=too-many-public-methods
336
364
  return f"{self.server}/{group}/{project}"
337
365
 
338
366
  @retry()
339
- def get_project(self, repo_url):
367
+ def get_project(self, repo_url: str) -> Project | None:
340
368
  repo = repo_url.replace(self.server + "/", "")
341
369
  gitlab_request.labels(integration=INTEGRATION_NAME).inc()
342
370
  try:
@@ -748,5 +776,5 @@ class GitLabApi: # pylint: disable=too-many-public-methods
748
776
  self, repo_url: str, ref_from: str, ref_to: str
749
777
  ) -> list[dict[str, Any]]:
750
778
  project = self.get_project(repo_url)
751
- response = project.repository_compare(ref_from, ref_to)
779
+ response: Any = project.repository_compare(ref_from, ref_to)
752
780
  return response.get("commits", [])
@@ -143,6 +143,9 @@ import reconcile.openshift_resources_base as orb
143
143
  import reconcile.utils.aws_helper as awsh
144
144
  from reconcile import queries
145
145
  from reconcile.github_org import get_default_config
146
+ from reconcile.gql_definitions.terraform_resources.terraform_resources_namespaces import (
147
+ NamespaceTerraformResourceLifecycleV1,
148
+ )
146
149
  from reconcile.utils import gql
147
150
  from reconcile.utils.aws_api import (
148
151
  AmiTag,
@@ -238,6 +241,7 @@ VARIABLE_KEYS = [
238
241
  "subscriptions",
239
242
  "records",
240
243
  "extra_tags",
244
+ "lifecycle",
241
245
  ]
242
246
 
243
247
  EMAIL_REGEX = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
@@ -870,6 +874,23 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
870
874
  role_name = awsh.get_id_from_arn(account["assume_role"])
871
875
  return f"account-{uid}-{role_name}"
872
876
 
877
+ @staticmethod
878
+ def get_resource_lifecycle(
879
+ common_values: dict[str, Any],
880
+ ) -> Optional[dict[str, Any]]:
881
+ if lifecycle := common_values.get("lifecycle"):
882
+ lifecycle = NamespaceTerraformResourceLifecycleV1(**lifecycle)
883
+ if lifecycle.create_before_destroy is None:
884
+ lifecycle.create_before_destroy = False
885
+ if lifecycle.prevent_destroy is None:
886
+ lifecycle.prevent_destroy = False
887
+ if lifecycle.ignore_changes is None:
888
+ lifecycle.ignore_changes = []
889
+ if "all" in lifecycle.ignore_changes:
890
+ lifecycle.ignore_changes = "all"
891
+ return lifecycle.dict(by_alias=True)
892
+ return None
893
+
873
894
  def populate_additional_providers(self, infra_account_name: str, accounts):
874
895
  for account in accounts:
875
896
  account_name = account["name"]
@@ -2459,6 +2480,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2459
2480
  if inline_policy:
2460
2481
  values["inline_policy"] = {"name": identifier, "policy": inline_policy}
2461
2482
 
2483
+ if lifecycle := self.get_resource_lifecycle(common_values):
2484
+ values["lifecycle"] = lifecycle
2485
+
2462
2486
  role_tf_resource = aws_iam_role(identifier, **values)
2463
2487
  tf_resources.append(role_tf_resource)
2464
2488