qontract-reconcile 0.10.1rc1048__py3-none-any.whl → 0.10.1rc1050__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.1rc1048
3
+ Version: 0.10.1rc1050
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
@@ -72,7 +72,7 @@ reconcile/openshift_network_policies.py,sha256=DyjaeJvSFHmslbM8nyHCxpF9EtU2m-MJo
72
72
  reconcile/openshift_prometheus_rules.py,sha256=onowXab248zmHH8SbYDTc1W1bl7JiqRFU1xdTkZyLFg,1332
73
73
  reconcile/openshift_resourcequotas.py,sha256=yUi56PiOn3inMMfq_x_FEHmaW-reGipzoorjdar372g,2415
74
74
  reconcile/openshift_resources.py,sha256=I2nO_C37mG3rfyGrd4cGwN3mVseVGuTAHAyhFzLyqF4,1518
75
- reconcile/openshift_resources_base.py,sha256=SKlJffnYjnc7AHIOvi7Ui9TmDA93_t81d5t54wnqCOA,40745
75
+ reconcile/openshift_resources_base.py,sha256=1A5_699p0rdsMwRQRPzePEfjhhq5eB2Obwxx4Ibr8jA,41205
76
76
  reconcile/openshift_rolebindings.py,sha256=9mlJ2FjWUoH-rsjtasreA_hV-K5Z_YR00qR_RR60OZM,6555
77
77
  reconcile/openshift_routes.py,sha256=fXvuPSjcjVw1X3j2EQvUAdbOepmIFdKk-M3qP8QzPiw,1075
78
78
  reconcile/openshift_saas_deploy.py,sha256=UZlm29JujJVS3MzSm6uehlC3y-jZxl6WVwMeKRdN11U,12773
@@ -571,7 +571,7 @@ reconcile/test/test_terraform_users.py,sha256=XOAfGvITCJPI1LTlISmHbA4ONMQMkxYUMT
571
571
  reconcile/test/test_terraform_vpc_peerings.py,sha256=bpjCjhmic07cw3XKSHf-2JvmLuWlyQG8laXlC-H7qtI,20796
572
572
  reconcile/test/test_terraform_vpc_peerings_build_desired_state.py,sha256=cHmr1_yhRgfdqlFX6TMw-aiKXebaRv0szl16M9YRJic,49988
573
573
  reconcile/test/test_three_way_diff_strategy.py,sha256=v3rNkQFNy5e1uyfeNSlNBA07fvrPGD0aXD91Lgv8oxc,4062
574
- reconcile/test/test_utils_jinja2.py,sha256=TpzQlpFnLGzNEZp5WOh0o7AuBiGEktqO4MuwiiJW2YY,3895
574
+ reconcile/test/test_utils_jinja2.py,sha256=rKugJEPl0qFC9joenJBXyk2qe-9md31-4EdxvQ2h5cs,4058
575
575
  reconcile/test/test_vault_replication.py,sha256=wlc4jm9f8P641UvvxIFFFc5_unJysNkOVrKJscjhQr0,16867
576
576
  reconcile/test/test_vault_utils.py,sha256=vbJnc89XAuE07qbTuWxHM5o9F6R9SO5aHXA38fwxT7A,1122
577
577
  reconcile/test/test_version_bump.py,sha256=q6-3Y1roriI6YWpFwaHOMN7emEP3yL33sh_0VdbmG7E,511
@@ -652,7 +652,7 @@ reconcile/unleash_feature_toggles/integration.py,sha256=nx7BhtzCsTfPbOp60vI5MkNw
652
652
  reconcile/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
653
653
  reconcile/utils/aggregated_list.py,sha256=km0xadW0jO4G_CqZPsXmoBURQ8c90FaTu5x4X1K1cZs,3357
654
654
  reconcile/utils/amtool.py,sha256=ngtBuVPETH6oAy5RnKzvreVbjwQCaATS_PYYwBprzjQ,2288
655
- reconcile/utils/aws_api.py,sha256=tYjTEW1uSkWsMWNFxwavJsI9xtq1dEeJ2HPlN5LAHzA,66861
655
+ reconcile/utils/aws_api.py,sha256=1EC9l2cxMW2IvnhsXaIcq6iohZ4rRgqyHJsXPs9Vtes,67398
656
656
  reconcile/utils/aws_helper.py,sha256=MDbv5jrNdqqJ5pfBxniGdJXBBO_EYc2_Uf2w9ZzeMNs,2854
657
657
  reconcile/utils/batches.py,sha256=TtEm64a8lWhFuNbUVpFEmXVdU2Q0sTBrP_I0Cjbgh7g,320
658
658
  reconcile/utils/binary.py,sha256=7MaAFBpzuBUTJ_aA6G6-eult_BPMVyiXbBLD0Y6F-DM,2301
@@ -761,8 +761,8 @@ reconcile/utils/internal_groups/client.py,sha256=V3uubJJHqW_wjwrwbC0halZrhOy7_yx
761
761
  reconcile/utils/internal_groups/models.py,sha256=y_IqBVqfGqNXiu0VudvBWFrm_-uafVm5KgLG-ca8XAs,2281
762
762
  reconcile/utils/jinja2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
763
763
  reconcile/utils/jinja2/extensions.py,sha256=7K-uo6G2eCWa98MHT8fRPYIKCLQB_5D2keqQ_LyAfHM,1293
764
- reconcile/utils/jinja2/filters.py,sha256=tmiaYMhji5fv4B66YtR7zc-mE3wQLyj5I5SeX0WA2l4,4754
765
- reconcile/utils/jinja2/utils.py,sha256=a4vpt8f0UHerPAZs5zFESkVzZdEZRJjZ4mzJ19nubjI,8048
764
+ reconcile/utils/jinja2/filters.py,sha256=JfO_14APySBPidsMvHXG-8dULNPddZCE15Umjk_aSBk,4830
765
+ reconcile/utils/jinja2/utils.py,sha256=adxVFY4WvBGIToEEr8KwqLzp6uDxSipTBzZWwnRqbNQ,8700
766
766
  reconcile/utils/jobcontroller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
767
767
  reconcile/utils/jobcontroller/controller.py,sha256=n9twSH_xZtp5vknM3qByp0eWCn9ED4U1a6yOFBS-GTg,14488
768
768
  reconcile/utils/jobcontroller/models.py,sha256=x9YIvWfYOOvXNKToFVx1H7qDrZb0Sa1KI_4Y0gl7rMM,6336
@@ -776,14 +776,14 @@ reconcile/utils/merge_request_manager/parser.py,sha256=5pGoz8Q6EuYXlUc1z-D0FahdR
776
776
  reconcile/utils/mr/__init__.py,sha256=hcfHDIIIsJT4C0BnzDnyeZEfZdamrqHzMLcBzIT1ibI,2578
777
777
  reconcile/utils/mr/app_interface_reporter.py,sha256=6Kpg93V9FvcOke9Jimkva359MQ-ZyBIkUpf8QIA6-to,1793
778
778
  reconcile/utils/mr/aws_access.py,sha256=w-UJutB_OfBJOvr-gsPzhtBPkDfKcNZZWGGuI9cN3HI,2605
779
- reconcile/utils/mr/base.py,sha256=TP6xaxznxsF_v2KcC_D3ut1cX_4KCcx4pjIC1-4eZUY,7307
779
+ reconcile/utils/mr/base.py,sha256=UwA_jm_2vMlWg-Y3oYlGWoXLgrTLBQGKtwIasJSK9_8,8105
780
780
  reconcile/utils/mr/clusters_updates.py,sha256=pcusPAwRUkvyk_-bixsRNTzSvpTLypJ1kflq5UEVgcM,2271
781
781
  reconcile/utils/mr/glitchtip_access_reporter.py,sha256=i5vo9jjBifX5wIcLwEMH5_VFVg-NY-pBKe0HysSw4CQ,5031
782
782
  reconcile/utils/mr/labels.py,sha256=9QRTRjZAtq45zELd9SwavaraczMjwjn5no3RK1YxFTg,825
783
783
  reconcile/utils/mr/notificator.py,sha256=cp80wFzu_ZzrJPye7L1pI0H6JRGb7hOGuNxJYUq4Yr8,2967
784
784
  reconcile/utils/mr/ocm_update_recommended_version.py,sha256=p_aVP0TGrlKk9WBwgQnYWqUDsED_Hg6G5Bqj0UvtRwA,1536
785
785
  reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py,sha256=ojnIjw-8vRnmCCxOGBOEgPZLH4nC1hcuef74LWw2Rpk,3004
786
- reconcile/utils/mr/promote_qontract.py,sha256=8cYAa2zW47ZsqqG8aQ8Akj31NQPc9ARN936_OY7t1pI,7016
786
+ reconcile/utils/mr/promote_qontract.py,sha256=704SOkLBioEwXITEr2mNdqpXA30J7kuUq1kQ4Q_mN8c,6693
787
787
  reconcile/utils/mr/user_maintenance.py,sha256=cHPBn8zrReWLHalyk-EFdkFJe9zjVjRoZhT4t2zZfGE,3956
788
788
  reconcile/utils/ocm/__init__.py,sha256=Y-bp8GomMpyCo0tFW6kJ78-ZG1UIupYRtBzbMWU0kwM,798
789
789
  reconcile/utils/ocm/addons.py,sha256=_LDdJ-gapM3s5exKlIUt-MlXZTAUoHezbYBU0QmvfWQ,7335
@@ -867,8 +867,8 @@ tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jr
867
867
  tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
868
868
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
869
869
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
870
- qontract_reconcile-0.10.1rc1048.dist-info/METADATA,sha256=0w3Sz5JM9zH3Sa3crv-Owf8f3uYAgeEafoXtXm0TdXg,2213
871
- qontract_reconcile-0.10.1rc1048.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
872
- qontract_reconcile-0.10.1rc1048.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
873
- qontract_reconcile-0.10.1rc1048.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
874
- qontract_reconcile-0.10.1rc1048.dist-info/RECORD,,
870
+ qontract_reconcile-0.10.1rc1050.dist-info/METADATA,sha256=jaTxzV1Ruwt2BUFyy5WMHueI5w-NoghTWsBX968vJqg,2213
871
+ qontract_reconcile-0.10.1rc1050.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
872
+ qontract_reconcile-0.10.1rc1050.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
873
+ qontract_reconcile-0.10.1rc1050.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
874
+ qontract_reconcile-0.10.1rc1050.dist-info/RECORD,,
@@ -1194,6 +1194,7 @@ def early_exit_monkey_patch() -> Generator:
1194
1194
  lookup_github_file_content=DEFAULT,
1195
1195
  url_makes_sense=DEFAULT,
1196
1196
  lookup_s3_object=DEFAULT,
1197
+ list_s3_objects=DEFAULT,
1197
1198
  ) as mocks:
1198
1199
  # mock lookup_secret
1199
1200
  mocks["lookup_secret"].side_effect = (
@@ -1239,6 +1240,17 @@ def early_exit_monkey_patch() -> Generator:
1239
1240
  mocks["lookup_s3_object"].unsafe_callable = False
1240
1241
  mocks["lookup_s3_object"].alters_data = False
1241
1242
 
1243
+ # mock list_s3_objects
1244
+ mocks["list_s3_objects"].side_effect = (
1245
+ lambda account_name,
1246
+ bucket_name,
1247
+ path,
1248
+ region_name=None: f"list_s3_objects({account_name}, {bucket_name}, {path}, {region_name})"
1249
+ )
1250
+ # needed for jinja2 `is_safe_callable`
1251
+ mocks["list_s3_objects"].unsafe_callable = False
1252
+ mocks["list_s3_objects"].alters_data = False
1253
+
1242
1254
  with patch(
1243
1255
  "reconcile.openshift_resources_base.check_alertmanager_config",
1244
1256
  return_value=True,
@@ -6,6 +6,7 @@ from reconcile.utils.jinja2.filters import (
6
6
  hash_list,
7
7
  json_pointers,
8
8
  matches_jsonpath,
9
+ str_format,
9
10
  )
10
11
 
11
12
 
@@ -121,3 +122,9 @@ def test_json_pointers() -> None:
121
122
  assert json_pointers(input, "items[?@.name=='a']") == ["/items/0"]
122
123
  assert json_pointers(input, "items[?@.name=='b']") == ["/items/1", "/items/2"]
123
124
  assert json_pointers(input, "items[?@.name=='c']") == []
125
+
126
+
127
+ def test_str_format() -> None:
128
+ value = "path/to/object"
129
+ format = "s3://%s"
130
+ assert str_format(value, format) == "s3://path/to/object"
@@ -1665,6 +1665,24 @@ class AWSApi: # pylint: disable=too-many-public-methods
1665
1665
  s3.get_object(Bucket=bucket_name, Key=path)["Body"].read().decode("utf-8")
1666
1666
  )
1667
1667
 
1668
+ def list_s3_objects(
1669
+ self,
1670
+ account_name: str,
1671
+ bucket_name: str,
1672
+ path: str,
1673
+ region_name: str | None = None,
1674
+ ) -> list[str]:
1675
+ s3 = self._account_s3_client(account_name, region_name=region_name)
1676
+ objects = s3.list_objects_v2(Bucket=bucket_name, Prefix=path, Delimiter="/")[
1677
+ "Contents"
1678
+ ]
1679
+ return [
1680
+ obj["Key"]
1681
+ for obj in sorted(
1682
+ objects, key=lambda obj: obj["LastModified"], reverse=True
1683
+ )
1684
+ ]
1685
+
1668
1686
 
1669
1687
  def aws_config_file_path() -> str | None:
1670
1688
  config_file_path = os.path.expanduser(
@@ -136,3 +136,7 @@ def json_pointers(input: Any, jsonpath: str) -> list[str]:
136
136
  Finds the RFC6901 JSON pointers of the input elements matching the given jsonpath
137
137
  """
138
138
  return [_convert_pointer(str(i.full_path)) for i in _find_jsonpath(input, jsonpath)]
139
+
140
+
141
+ def str_format(value: str, format: str) -> str:
142
+ return format % value
@@ -21,6 +21,7 @@ from reconcile.utils.jinja2.filters import (
21
21
  json_pointers,
22
22
  json_to_dict,
23
23
  matches_jsonpath,
24
+ str_format,
24
25
  urlescape,
25
26
  urlunescape,
26
27
  yaml_to_dict,
@@ -89,6 +90,7 @@ def compile_jinja2_template(
89
90
  "extract_jsonpath": extract_jsonpath,
90
91
  "matches_jsonpath": matches_jsonpath,
91
92
  "json_pointers": json_pointers,
93
+ "str_format": str_format,
92
94
  })
93
95
 
94
96
  return jinja_env.from_string(body)
@@ -146,6 +148,26 @@ def lookup_s3_object(
146
148
  )
147
149
 
148
150
 
151
+ def list_s3_objects(
152
+ account_name: str,
153
+ bucket_name: str,
154
+ path: str,
155
+ region_name: str | None = None,
156
+ ) -> list[str]:
157
+ settings = queries.get_app_interface_settings()
158
+ accounts = queries.get_aws_accounts(name=account_name)
159
+ if not accounts:
160
+ raise Exception(f"aws account not found: {account_name}")
161
+
162
+ with AWSApi(1, accounts, settings=settings, init_users=False) as aws_api:
163
+ return aws_api.list_s3_objects(
164
+ account_name,
165
+ bucket_name,
166
+ path,
167
+ region_name=region_name,
168
+ )
169
+
170
+
149
171
  @retry()
150
172
  def lookup_secret(
151
173
  path: str,
@@ -214,6 +236,7 @@ def process_jinja2_template(
214
236
  "query": lookup_graphql_query_results,
215
237
  "url": url_makes_sense,
216
238
  "s3": lookup_s3_object,
239
+ "s3_ls": list_s3_objects,
217
240
  "flatten_dict": flatten,
218
241
  "yesterday": lambda: (datetime.datetime.now() - datetime.timedelta(1)).strftime(
219
242
  "%Y-%m-%d"
@@ -4,12 +4,15 @@ from abc import (
4
4
  ABC,
5
5
  abstractmethod,
6
6
  )
7
+ from collections.abc import Iterable
7
8
  from typing import Any
8
9
  from uuid import uuid4
9
10
 
10
11
  from gitlab.exceptions import GitlabError
11
12
  from jinja2 import Template
12
13
 
14
+ from reconcile import typed_queries
15
+ from reconcile.gql_definitions.fragments.user import User
13
16
  from reconcile.utils.constants import PROJ_ROOT
14
17
  from reconcile.utils.gitlab_api import GitLabApi
15
18
  from reconcile.utils.mr.labels import DO_NOT_MERGE_HOLD
@@ -108,6 +111,26 @@ class MergeRequestBase(ABC):
108
111
  **self.sqs_msg_data,
109
112
  }
110
113
 
114
+ def infer_author(
115
+ self, author_email: str | None, all_users: Iterable[User] | None = None
116
+ ) -> str | None:
117
+ if not author_email:
118
+ return None
119
+ if not all_users:
120
+ return None
121
+
122
+ username = author_email.split("@")[0]
123
+ users = None
124
+ if author_email.endswith(typed_queries.smtp.settings().mail_address):
125
+ users = [u for u in all_users if username == u.org_username]
126
+ elif author_email.endswith("users.noreply.github.com"):
127
+ users = [u for u in all_users if username == u.github_username]
128
+
129
+ if users:
130
+ return users[0].org_username
131
+
132
+ return None
133
+
111
134
  def submit_to_sqs(self, sqs_cli: SQSGateway) -> None:
112
135
  """
113
136
  Sends the MR message to SQS.
@@ -1,8 +1,6 @@
1
1
  from jsonpath_ng.ext import parser
2
2
  from ruamel.yaml.compat import StringIO
3
3
 
4
- from reconcile import typed_queries
5
- from reconcile.gql_definitions.fragments.user import User
6
4
  from reconcile.typed_queries.users import get_users
7
5
  from reconcile.utils.gitlab_api import GitLabApi
8
6
  from reconcile.utils.mr.base import MergeRequestBase
@@ -12,9 +10,10 @@ from reconcile.utils.ruamel import create_ruamel_instance
12
10
  class PromoteQontractSchemas(MergeRequestBase):
13
11
  name = "promote_qontract_schemas"
14
12
 
15
- def __init__(self, version: str):
13
+ def __init__(self, version: str, author_email: str | None = None):
16
14
  self.path = ".env"
17
15
  self.version = version
16
+ self.author_email = author_email
18
17
 
19
18
  super().__init__()
20
19
 
@@ -22,7 +21,16 @@ class PromoteQontractSchemas(MergeRequestBase):
22
21
 
23
22
  @property
24
23
  def title(self) -> str:
25
- return f"[{self.name}] promote qontract-schemas to version {self.version}"
24
+ author = self.infer_author(
25
+ author_email=self.author_email, all_users=get_users()
26
+ )
27
+ return f"[{self.name}] promote qontract-schemas to version {self.version}" + (
28
+ f" by @{author}"
29
+ if author
30
+ else f" by {self.author_email}"
31
+ if self.author_email
32
+ else ""
33
+ )
26
34
 
27
35
  @property
28
36
  def description(self) -> str:
@@ -60,27 +68,11 @@ class PromoteQontractReconcileCommercial(MergeRequestBase):
60
68
 
61
69
  self.labels = []
62
70
 
63
- def author(self, all_users: list[User] | None = None) -> str | None:
64
- if not self.author_email:
65
- return None
66
- if not all_users:
67
- return None
68
-
69
- username = self.author_email.split("@")[0]
70
- users = None
71
- if self.author_email.endswith(typed_queries.smtp.settings().mail_address):
72
- users = [u for u in all_users if username == u.org_username]
73
- elif self.author_email.endswith("users.noreply.github.com"):
74
- users = [u for u in all_users if username == u.github_username]
75
-
76
- if users:
77
- return users[0].org_username
78
-
79
- return None
80
-
81
71
  @property
82
72
  def title(self) -> str:
83
- author = self.author(all_users=get_users())
73
+ author = self.infer_author(
74
+ author_email=self.author_email, all_users=get_users()
75
+ )
84
76
  return f"[{self.name}] promote qontract-reconcile to version {self.version}" + (
85
77
  f" by @{author}"
86
78
  if author