qontract-reconcile 0.10.1rc601__py3-none-any.whl → 0.10.1rc603__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.1rc601
3
+ Version: 0.10.1rc603
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
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Requires-Python: >=3.11
14
- Requires-Dist: sretoolbox ~=2.5.1
14
+ Requires-Dist: sretoolbox ~=2.5.2
15
15
  Requires-Dist: Click <9.0,>=7.0
16
16
  Requires-Dist: gql ==3.1.0
17
17
  Requires-Dist: toml <0.11.0,>=0.10.0
@@ -451,7 +451,7 @@ reconcile/test/test_quay_repos.py,sha256=TdkcRF_a8PLp01Kti9eZZN-vGup2yPBT4Iba3k0
451
451
  reconcile/test/test_queries.py,sha256=SpH3RmNpBjEr_ne3VjAMCgKK8RE1z1zo7bypkT5uoO4,1946
452
452
  reconcile/test/test_repo_owners.py,sha256=uRYMLbMmh-9usF0TerabZTZV-Z1CS4I6ybT-LQqCLe8,1423
453
453
  reconcile/test/test_requests_sender.py,sha256=7fd9C2kEFS0-CYtlsif66N1kO9c44pzuBPAJKR9igqU,5385
454
- reconcile/test/test_saasherder.py,sha256=hCRwkmMSws8o-SCiaa68hdD2lDXzl21EZ_6v0CCG7vA,47064
454
+ reconcile/test/test_saasherder.py,sha256=1_GyiXxxNqKSKE7PrtFJL7tUFg77d1oQPZzNBZW-DLQ,47042
455
455
  reconcile/test/test_saasherder_allowed_secret_paths.py,sha256=5NHQwNJO66at6HiyMZ5sVRTQDwxdvlOQo0KmkBWCw5Q,4853
456
456
  reconcile/test/test_secret_reader.py,sha256=kz7nzcPjvA08cytnvcA_PMA98AEyqJWsESkYeRn5xCk,4994
457
457
  reconcile/test/test_slack_base.py,sha256=gpbWOLNxMMX6fyAbs1JakhLTnwfedb3f7WpUae4tQZE,5060
@@ -675,8 +675,8 @@ reconcile/utils/runtime/runner.py,sha256=72cc-I6yXyPov8UCLHpyERRy1eiMLpGite2roO0
675
675
  reconcile/utils/runtime/sharding.py,sha256=roCdbnBklhTK_g34zbgQYqzpKPaNQ8J6Xd9XLO9-t6Q,16258
676
676
  reconcile/utils/saasherder/__init__.py,sha256=J3MBZBFa5YmhqYm08QsjBXz8mFcVOCiOCkyIcw41t7E,343
677
677
  reconcile/utils/saasherder/interfaces.py,sha256=XXY35h8VWQ66z3LBPxaoUAMkIW50264DQiecrzyV6oA,9076
678
- reconcile/utils/saasherder/models.py,sha256=PBv8DuAb6KUw_ayn5Ufiya20cCAelBv6Iv--x7hbpa4,5449
679
- reconcile/utils/saasherder/saasherder.py,sha256=72b0u-cIHg62R2uQCqlGcQfW5TbCxWDKf0dfmgVMUbY,85733
678
+ reconcile/utils/saasherder/models.py,sha256=1DKXUmiTS_MejUfSpFCeuBLMTgR4ldv2N1tAz8qHAwc,5547
679
+ reconcile/utils/saasherder/saasherder.py,sha256=h3qihKpL-UDUqi8V-i1mhfofXmydeGuHhG_Y9pVKJjk,86162
680
680
  reconcile/utils/terraform/__init__.py,sha256=zNbiyTWo35AT1sFTElL2j_AA0jJ_yWE_bfFn-nD2xik,250
681
681
  reconcile/utils/terraform/config.py,sha256=5UVrd563TMcvi4ooa5JvWVDW1I3bIWg484u79evfV_8,164
682
682
  reconcile/utils/terraform/config_client.py,sha256=py-Ree-QUYD6Hvng6bM40VgSuttteehIKNgwOSoJO1o,4706
@@ -704,8 +704,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
704
704
  tools/test/test_qontract_cli.py,sha256=OvalpVRfY4pNmpMaWHHYqBjV68b1eGQjX8SCyTAXb1w,3501
705
705
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
706
706
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
707
- qontract_reconcile-0.10.1rc601.dist-info/METADATA,sha256=xTaPAHRbNj68qwYjL7rWiII4D89lyuJWpmOg4fiBP_g,2349
708
- qontract_reconcile-0.10.1rc601.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
709
- qontract_reconcile-0.10.1rc601.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
710
- qontract_reconcile-0.10.1rc601.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
711
- qontract_reconcile-0.10.1rc601.dist-info/RECORD,,
707
+ qontract_reconcile-0.10.1rc603.dist-info/METADATA,sha256=KHZWVtIXDu8ZCJNsPliWFbsu58gjPsYV5hJQUzzyiL8,2349
708
+ qontract_reconcile-0.10.1rc603.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
709
+ qontract_reconcile-0.10.1rc603.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
710
+ qontract_reconcile-0.10.1rc603.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
711
+ qontract_reconcile-0.10.1rc603.dist-info/RECORD,,
@@ -651,19 +651,17 @@ class TestGetContainerImagesDiffSaasFile(TestCase):
651
651
  self.get_commit_sha_patcher = patch.object(
652
652
  SaasHerder, "_get_commit_sha", autospec=True
653
653
  )
654
- self.check_image_patcher = patch.object(
655
- SaasHerder, "_check_image", autospec=True
656
- )
654
+ self.get_image_patcher = patch.object(SaasHerder, "_get_image", autospec=True)
657
655
  self.initiate_gh = self.initiate_gh_patcher.start()
658
656
  self.get_commit_sha = self.get_commit_sha_patcher.start()
659
- self.check_image = self.check_image_patcher.start()
657
+ self.get_image = self.get_image_patcher.start()
660
658
  self.maxDiff = None
661
659
 
662
660
  def tearDown(self) -> None:
663
661
  for p in (
664
662
  self.initiate_gh_patcher,
665
663
  self.get_commit_sha_patcher,
666
- self.check_image_patcher,
664
+ self.get_image_patcher,
667
665
  ):
668
666
  p.stop()
669
667
 
@@ -680,7 +678,7 @@ class TestGetContainerImagesDiffSaasFile(TestCase):
680
678
  saasherder.state = MagicMock()
681
679
  saasherder.state.get.return_value = "asha"
682
680
  self.get_commit_sha.return_value = "abcd4242"
683
- self.check_image.return_value = None
681
+ self.get_image.return_value = MagicMock()
684
682
  expected = [
685
683
  TriggerSpecContainerImage(
686
684
  saas_file_name=self.saas_file.name,
@@ -717,7 +715,7 @@ class TestGetContainerImagesDiffSaasFile(TestCase):
717
715
  saasherder.state = MagicMock()
718
716
  saasherder.state.get.return_value = "asha"
719
717
  self.get_commit_sha.return_value = "abcd4242"
720
- self.check_image.return_value = None
718
+ self.get_image.return_value = MagicMock()
721
719
  expected = [
722
720
  TriggerSpecContainerImage(
723
721
  saas_file_name=self.saas_file.name,
@@ -192,19 +192,21 @@ class ImageAuth:
192
192
  username: Optional[str] = None
193
193
  password: Optional[str] = None
194
194
  auth_server: Optional[str] = None
195
+ docker_config: Optional[dict[str, dict[str, dict[str, str]]]] = None
195
196
 
196
197
  def getDockerConfigJson(self) -> dict:
197
- return {
198
- "auths": {
199
- self.auth_server: {
200
- "username": self.username,
201
- "password": self.password,
202
- "auth": base64.b64encode(
203
- f"{self.username}:{self.password}".encode()
204
- ).decode(),
198
+ if self.docker_config:
199
+ return self.docker_config
200
+ else:
201
+ return {
202
+ "auths": {
203
+ self.auth_server: {
204
+ "auth": base64.b64encode(
205
+ f"{self.username}:{self.password}".encode()
206
+ ).decode(),
207
+ }
205
208
  }
206
209
  }
207
- }
208
210
 
209
211
 
210
212
  @dataclass
@@ -898,19 +898,20 @@ class SaasHerder: # pylint: disable=too-many-public-methods
898
898
  return False
899
899
 
900
900
  def _process_template(
901
- self,
902
- saas_file_name: str,
903
- resource_template_name: str,
904
- image_auth: ImageAuth,
905
- url: str,
906
- path: str,
907
- provider: str,
908
- hash_length: int,
909
- target: SaasResourceTemplateTarget,
910
- parameters: dict[str, str],
911
- github: Github,
912
- target_config_hash: str,
901
+ self, spec: TargetSpec
913
902
  ) -> tuple[list[Any], str, Optional[Promotion]]:
903
+ saas_file_name = spec.saas_file_name
904
+ resource_template_name = spec.resource_template_name
905
+ image_auth = spec.image_auth
906
+ url = spec.url
907
+ path = spec.path
908
+ provider = spec.provider
909
+ hash_length = spec.hash_length
910
+ target = spec.target
911
+ parameters = spec.parameters
912
+ github = spec.github
913
+ target_config_hash = spec.target_config_hash
914
+
914
915
  if provider == "openshift-template":
915
916
  environment_parameters = self._collect_parameters(
916
917
  target.namespace.environment
@@ -997,25 +998,26 @@ class SaasHerder: # pylint: disable=too-many-public-methods
997
998
  + f"{str(e)}"
998
999
  )
999
1000
  raise
1000
- try:
1001
- image_uri = f"{registry_image}:{image_tag}"
1002
- img = Image(
1003
- url=image_uri,
1004
- username=image_auth.username,
1005
- password=image_auth.password,
1006
- auth_server=image_auth.auth_server,
1007
- )
1008
- if need_repo_digest:
1009
- consolidated_parameters["REPO_DIGEST"] = img.url_digest
1010
- if need_image_digest:
1011
- consolidated_parameters["IMAGE_DIGEST"] = img.digest
1012
- except (rqexc.ConnectionError, rqexc.HTTPError) as e:
1013
- logging.error(
1014
- f"[{saas_file_name}/{resource_template_name}] "
1015
- + f"{html_url}: error generating REPO_DIGEST for "
1016
- + f"{image_uri}: {str(e)}"
1001
+
1002
+ image_uri = f"{registry_image}:{image_tag}"
1003
+ error_prefix = (
1004
+ f"[{saas_file_name}/{resource_template_name}] {html_url}:"
1005
+ )
1006
+ img = self._get_image(
1007
+ image=image_uri,
1008
+ image_patterns=spec.image_patterns,
1009
+ image_auth=image_auth,
1010
+ error_prefix=error_prefix,
1011
+ )
1012
+ if not img:
1013
+ raise Exception(
1014
+ f"[{error_prefix}: error generating REPO_DIGEST for {image_uri}"
1017
1015
  )
1018
- raise
1016
+
1017
+ if need_repo_digest:
1018
+ consolidated_parameters["REPO_DIGEST"] = img.url_digest
1019
+ if need_image_digest:
1020
+ consolidated_parameters["IMAGE_DIGEST"] = img.digest
1019
1021
 
1020
1022
  oc = OCLocal("cluster", None, None, local=True)
1021
1023
  try:
@@ -1125,38 +1127,53 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1125
1127
  return images
1126
1128
 
1127
1129
  @staticmethod
1128
- def _check_image(
1130
+ def _get_image(
1129
1131
  image: str,
1130
1132
  image_patterns: Iterable[str],
1131
1133
  image_auth: ImageAuth,
1132
1134
  error_prefix: str,
1133
- ) -> bool:
1134
- error = False
1135
+ ) -> Optional[Image]:
1135
1136
  if not image_patterns:
1136
- error = True
1137
1137
  logging.error(
1138
1138
  f"{error_prefix} imagePatterns is empty (does not contain {image})"
1139
1139
  )
1140
+ return None
1140
1141
  if image_patterns and not any(image.startswith(p) for p in image_patterns):
1141
- error = True
1142
1142
  logging.error(f"{error_prefix} Image is not in imagePatterns: {image}")
1143
+ return None
1144
+
1145
+ # .dockerconfigjson
1146
+ if image_auth.docker_config:
1147
+ # we rely on the secret in vault being ordered
1148
+ # https://peps.python.org/pep-0468/
1149
+ for registry, auth in image_auth.docker_config["auths"].items():
1150
+ if not image.startswith(registry):
1151
+ continue
1152
+ username, password = (
1153
+ base64.b64decode(auth["auth"]).decode("utf-8").split(":")
1154
+ )
1155
+ with suppress(Exception):
1156
+ return Image(
1157
+ image,
1158
+ username=username,
1159
+ password=password,
1160
+ auth_server=image_auth.auth_server,
1161
+ )
1162
+
1163
+ # basic auth fallback for backwards compatibility
1143
1164
  try:
1144
- valid = Image(
1165
+ return Image(
1145
1166
  image,
1146
1167
  username=image_auth.username,
1147
1168
  password=image_auth.password,
1148
1169
  auth_server=image_auth.auth_server,
1149
1170
  )
1150
- if not valid:
1151
- error = True
1152
- logging.error(f"{error_prefix} Image does not exist: {image}")
1153
1171
  except Exception as e:
1154
- error = True
1155
1172
  logging.error(
1156
1173
  f"{error_prefix} Image is invalid: {image}. " + f"details: {str(e)}"
1157
1174
  )
1158
1175
 
1159
- return error
1176
+ return None
1160
1177
 
1161
1178
  def _check_images(
1162
1179
  self,
@@ -1175,15 +1192,15 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1175
1192
  self.images.update(images)
1176
1193
  if not images:
1177
1194
  return False # no errors
1178
- errors = threaded.run(
1179
- self._check_image,
1195
+ images = threaded.run(
1196
+ self._get_image,
1180
1197
  images,
1181
1198
  self.available_thread_pool_size,
1182
1199
  image_patterns=image_patterns,
1183
1200
  image_auth=image_auth,
1184
1201
  error_prefix=error_prefix,
1185
1202
  )
1186
- return any(errors)
1203
+ return None in images
1187
1204
 
1188
1205
  def _initiate_github(
1189
1206
  self, saas_file: SaasFile, base_url: Optional[str] = None
@@ -1216,20 +1233,24 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1216
1233
  return ImageAuth()
1217
1234
 
1218
1235
  creds = self.secret_reader.read_all_secret(saas_file.authentication.image)
1219
- required_keys = ["user", "token"]
1220
- ok = all(k in creds.keys() for k in required_keys)
1236
+ required_docker_config_keys = [".dockerconfigjson"]
1237
+ required_keys_basic_auth = ["user", "token"]
1238
+ ok = all(k in creds.keys() for k in required_keys_basic_auth) or all(
1239
+ k in creds.keys() for k in required_docker_config_keys
1240
+ )
1221
1241
  if not ok:
1222
1242
  logging.warning(
1223
1243
  "the specified image authentication secret "
1224
1244
  + f"found in path {saas_file.authentication.image.path} "
1225
- + f"does not contain all required keys: {required_keys}"
1245
+ + f"does not contain all required keys: {required_docker_config_keys} or {required_keys_basic_auth}"
1226
1246
  )
1227
1247
  return ImageAuth()
1228
1248
 
1229
1249
  return ImageAuth(
1230
- username=creds["user"],
1231
- password=creds["token"],
1250
+ username=creds.get("user"),
1251
+ password=creds.get("token"),
1232
1252
  auth_server=creds.get("url"),
1253
+ docker_config=json.loads(creds.get(".dockerconfigjson") or "{}"),
1233
1254
  )
1234
1255
 
1235
1256
  def populate_desired_state(self, ri: ResourceInventory) -> None:
@@ -1326,19 +1347,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1326
1347
  return None
1327
1348
 
1328
1349
  try:
1329
- resources, html_url, promotion = self._process_template(
1330
- saas_file_name=spec.saas_file_name,
1331
- resource_template_name=spec.resource_template_name,
1332
- image_auth=spec.image_auth,
1333
- url=spec.url,
1334
- path=spec.path,
1335
- provider=spec.provider,
1336
- hash_length=spec.hash_length,
1337
- target=spec.target,
1338
- parameters=spec.parameters,
1339
- github=spec.github,
1340
- target_config_hash=spec.target_config_hash,
1341
- )
1350
+ resources, html_url, promotion = self._process_template(spec)
1342
1351
  except Exception as e:
1343
1352
  # error log message send in _process_template. We log here debug to have a
1344
1353
  # safeguard in case something breaks there unexpectedly. We cannot just
@@ -1670,10 +1679,10 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1670
1679
  image_uri = f"{image_registry}:{desired_image_tag}"
1671
1680
  image_auth = self._initiate_image_auth(saas_file)
1672
1681
  error_prefix = f"[{saas_file.name}/{rt.name}] {target.ref}:"
1673
- error = self._check_image(
1682
+ image = self._get_image(
1674
1683
  image_uri, saas_file.image_patterns, image_auth, error_prefix
1675
1684
  )
1676
- if error:
1685
+ if not image:
1677
1686
  continue
1678
1687
 
1679
1688
  trigger_spec = TriggerSpecContainerImage(