qontract-reconcile 0.10.1rc460__py3-none-any.whl → 0.10.1rc462__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 (88) hide show
  1. {qontract_reconcile-0.10.1rc460.dist-info → qontract_reconcile-0.10.1rc462.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.1rc460.dist-info → qontract_reconcile-0.10.1rc462.dist-info}/RECORD +88 -88
  3. reconcile/aus/base.py +3 -3
  4. reconcile/aws_iam_keys.py +1 -1
  5. reconcile/aws_support_cases_sos.py +1 -1
  6. reconcile/change_owners/change_owners.py +2 -3
  7. reconcile/change_owners/diff.py +1 -1
  8. reconcile/change_owners/tester.py +3 -3
  9. reconcile/checkpoint.py +1 -1
  10. reconcile/cli.py +2 -1
  11. reconcile/closedbox_endpoint_monitoring_base.py +1 -1
  12. reconcile/cna/state.py +2 -2
  13. reconcile/dashdotdb_base.py +3 -3
  14. reconcile/dynatrace_token_provider.py +7 -8
  15. reconcile/gcr_mirror.py +2 -2
  16. reconcile/github_org.py +2 -2
  17. reconcile/github_owners.py +1 -1
  18. reconcile/gitlab_housekeeping.py +3 -3
  19. reconcile/gitlab_labeler.py +4 -5
  20. reconcile/glitchtip/reconciler.py +3 -3
  21. reconcile/glitchtip_project_alerts/integration.py +3 -3
  22. reconcile/gql_definitions/common/clusters.py +0 -2
  23. reconcile/gql_definitions/common/clusters_minimal.py +0 -2
  24. reconcile/ocm_clusters.py +9 -9
  25. reconcile/ocm_github_idp.py +1 -1
  26. reconcile/ocm_groups.py +1 -1
  27. reconcile/openshift_base.py +6 -6
  28. reconcile/openshift_clusterrolebindings.py +1 -1
  29. reconcile/openshift_groups.py +1 -1
  30. reconcile/openshift_namespace_labels.py +12 -12
  31. reconcile/openshift_resources_base.py +3 -3
  32. reconcile/openshift_rolebindings.py +1 -1
  33. reconcile/openshift_saas_deploy.py +1 -1
  34. reconcile/quay_mirror.py +2 -2
  35. reconcile/queries.py +0 -2
  36. reconcile/rhidp/common.py +2 -2
  37. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager.py +9 -9
  38. reconcile/slack_usergroups.py +9 -9
  39. reconcile/sql_query.py +3 -4
  40. reconcile/terraform_aws_route53.py +1 -1
  41. reconcile/terraform_cloudflare_users.py +7 -7
  42. reconcile/terraform_repo.py +3 -1
  43. reconcile/terraform_vpc_peerings.py +10 -10
  44. reconcile/test/fixtures.py +1 -1
  45. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +2 -2
  46. reconcile/test/test_jump_host.py +2 -2
  47. reconcile/test/test_quay_mirror.py +3 -1
  48. reconcile/test/test_quay_mirror_org.py +3 -1
  49. reconcile/test/test_terraform_repo.py +2 -2
  50. reconcile/typed_queries/saas_files.py +5 -5
  51. reconcile/utils/amtool.py +2 -2
  52. reconcile/utils/aws_api.py +5 -29
  53. reconcile/utils/config.py +1 -2
  54. reconcile/utils/environ.py +1 -1
  55. reconcile/utils/git.py +7 -3
  56. reconcile/utils/git_secrets.py +2 -2
  57. reconcile/utils/helm.py +1 -1
  58. reconcile/utils/jjb_client.py +7 -7
  59. reconcile/utils/jump_host.py +2 -2
  60. reconcile/utils/metrics.py +3 -3
  61. reconcile/utils/models.py +47 -51
  62. reconcile/utils/mr/aws_access.py +1 -1
  63. reconcile/utils/mr/base.py +1 -1
  64. reconcile/utils/mr/user_maintenance.py +1 -1
  65. reconcile/utils/oc.py +8 -8
  66. reconcile/utils/oc_connection_parameters.py +12 -13
  67. reconcile/utils/ocm/base.py +1 -1
  68. reconcile/utils/ocm/ocm.py +9 -9
  69. reconcile/utils/openshift_resource.py +8 -9
  70. reconcile/utils/parse_dhms_duration.py +1 -1
  71. reconcile/utils/runtime/sharding.py +1 -1
  72. reconcile/utils/saasherder/saasherder.py +5 -5
  73. reconcile/utils/slack_api.py +2 -2
  74. reconcile/utils/terraform/config_client.py +1 -1
  75. reconcile/utils/terraform_client.py +5 -5
  76. reconcile/utils/terrascript/cloudflare_client.py +3 -1
  77. reconcile/utils/terrascript_aws_client.py +40 -40
  78. reconcile/utils/three_way_diff_strategy.py +2 -2
  79. reconcile/utils/unleash.py +1 -1
  80. reconcile/utils/vault.py +1 -1
  81. reconcile/vpc_peerings_validator.py +6 -6
  82. release/version.py +7 -2
  83. tools/app_interface_reporter.py +3 -3
  84. tools/cli_commands/gpg_encrypt.py +2 -2
  85. tools/qontract_cli.py +7 -6
  86. {qontract_reconcile-0.10.1rc460.dist-info → qontract_reconcile-0.10.1rc462.dist-info}/WHEEL +0 -0
  87. {qontract_reconcile-0.10.1rc460.dist-info → qontract_reconcile-0.10.1rc462.dist-info}/entry_points.txt +0 -0
  88. {qontract_reconcile-0.10.1rc460.dist-info → qontract_reconcile-0.10.1rc462.dist-info}/top_level.txt +0 -0
@@ -14,7 +14,7 @@ class Fixtures:
14
14
  )
15
15
 
16
16
  def get(self, fixture):
17
- with open(self.path(fixture), "r") as f:
17
+ with open(self.path(fixture), "r", encoding="locale") as f:
18
18
  return f.read().strip()
19
19
 
20
20
  def get_anymarkup(self, fixture):
@@ -30,10 +30,10 @@ def file_contents() -> Callable[[str], tuple[str, str]]:
30
30
  "files",
31
31
  )
32
32
 
33
- with open(f"{path}/{case}.yml", "r") as f:
33
+ with open(f"{path}/{case}.yml", "r", encoding="locale") as f:
34
34
  a = f.read().strip()
35
35
 
36
- with open(f"{path}/{case}.result.yml", "r") as f:
36
+ with open(f"{path}/{case}.result.yml", "r", encoding="locale") as f:
37
37
  b = f.read().strip()
38
38
 
39
39
  return (a, b)
@@ -55,7 +55,7 @@ def test_base_jumphost(fs: Any, parameters: JumphostParameters, expected_port: i
55
55
  jumphost = JumpHostBase(parameters=parameters)
56
56
  assert os.path.exists(jumphost._identity_file)
57
57
 
58
- with open(jumphost._identity_file, "r") as f:
58
+ with open(jumphost._identity_file, "r", encoding="locale") as f:
59
59
  assert f.read() == parameters.key
60
60
 
61
61
  assert jumphost._port == expected_port
@@ -113,5 +113,5 @@ def test_ssh_jumphost(
113
113
  if local_port:
114
114
  assert jumphost._local_port == local_port
115
115
 
116
- with open(known_hosts_file, "r") as f:
116
+ with open(known_hosts_file, "r", encoding="locale") as f:
117
117
  assert f.read() == EXPECTED_KNOWN_HOSTS_CONTENT
@@ -51,7 +51,9 @@ class TestIsCompareTags:
51
51
  self.tmp_dir = (
52
52
  tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
53
53
  )
54
- with open(os.path.join(self.tmp_dir.name, CONTROL_FILE_NAME), "w") as fh:
54
+ with open(
55
+ os.path.join(self.tmp_dir.name, CONTROL_FILE_NAME), "w", encoding="locale"
56
+ ) as fh:
55
57
  fh.write(str(NOW - 100.0))
56
58
 
57
59
  def teardown_method(self):
@@ -33,7 +33,9 @@ class TestIsCompareTags:
33
33
  self.tmp_dir = (
34
34
  tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
35
35
  )
36
- with open(os.path.join(self.tmp_dir.name, CONTROL_FILE_NAME), "w") as fh:
36
+ with open(
37
+ os.path.join(self.tmp_dir.name, CONTROL_FILE_NAME), "w", encoding="locale"
38
+ ) as fh:
37
39
  fh.write(str(NOW - 100.0))
38
40
 
39
41
  def teardown_method(self):
@@ -339,7 +339,7 @@ def test_output_correct_statefile(
339
339
  assert diff
340
340
  integration.print_output(diff, True)
341
341
 
342
- with open(f"{tmp_path}/tf-repo.yaml", "r") as output:
342
+ with open(f"{tmp_path}/tf-repo.yaml", "r", encoding="locale") as output:
343
343
  yaml_rep = yaml.safe_load(output)
344
344
 
345
345
  assert expected_output == yaml_rep
@@ -366,7 +366,7 @@ def test_output_correct_no_statefile(
366
366
  assert diff
367
367
  integration.print_output(diff, True)
368
368
 
369
- with open(f"{tmp_path}/tf-repo.yaml", "r") as output:
369
+ with open(f"{tmp_path}/tf-repo.yaml", "r", encoding="locale") as output:
370
370
  yaml_rep = yaml.safe_load(output)
371
371
 
372
372
  assert expected_output == yaml_rep
@@ -95,9 +95,9 @@ class SaasResourceTemplate(ConfiguredBaseModel):
95
95
  provider: Optional[str] = Field(..., alias="provider")
96
96
  hash_length: Optional[int] = Field(..., alias="hash_length")
97
97
  parameters: Optional[Json] = Field(..., alias="parameters")
98
- secret_parameters: Optional[
99
- list[SaasResourceTemplateV2_SaasSecretParametersV1]
100
- ] = Field(..., alias="secretParameters")
98
+ secret_parameters: Optional[list[SaasResourceTemplateV2_SaasSecretParametersV1]] = (
99
+ Field(..., alias="secretParameters")
100
+ )
101
101
  targets: list[SaasResourceTemplateTarget] = Field(..., alias="targets")
102
102
 
103
103
 
@@ -279,7 +279,7 @@ class SaasFileList:
279
279
  if name is None and env_name is None and app_name is None:
280
280
  return self.saas_files
281
281
 
282
- if name == "" or env_name == "" or app_name == "":
282
+ if name == "" or env_name == "" or app_name == "": # noqa: PLC1901
283
283
  return []
284
284
 
285
285
  filtered: list[SaasFile] = []
@@ -308,7 +308,7 @@ class SaasFileList:
308
308
  def convert_parameters_to_json_string(root: dict[str, Any]) -> dict[str, Any]:
309
309
  """Find all parameter occurrences and convert them to a json string."""
310
310
  for key, value in root.items():
311
- if key in ["parameters", "labels"]:
311
+ if key in {"parameters", "labels"}:
312
312
  root[key] = json.dumps(value) if value is not None else None
313
313
  elif isinstance(value, dict):
314
314
  root[key] = convert_parameters_to_json_string(value)
reconcile/utils/amtool.py CHANGED
@@ -25,7 +25,7 @@ class AmtoolResult:
25
25
  def check_config(yaml_config: str) -> AmtoolResult:
26
26
  """Run amtool check rules on the given yaml string"""
27
27
 
28
- with tempfile.NamedTemporaryFile(mode="w+") as fp:
28
+ with tempfile.NamedTemporaryFile(mode="w+", encoding="locale") as fp:
29
29
  fp.write(yaml_config)
30
30
  fp.flush()
31
31
  cmd = ["amtool", "check-config", fp.name]
@@ -36,7 +36,7 @@ def check_config(yaml_config: str) -> AmtoolResult:
36
36
 
37
37
  def config_routes_test(yaml_config: str, labels: Mapping[str, str]) -> AmtoolResult:
38
38
  labels_lst = [f"{key}={value}" for key, value in labels.items()]
39
- with tempfile.NamedTemporaryFile(mode="w+") as fp:
39
+ with tempfile.NamedTemporaryFile(mode="w+", encoding="locale") as fp:
40
40
  fp.write(yaml_config)
41
41
  fp.flush()
42
42
  cmd = ["amtool", "config", "routes", "test", "--config.file", fp.name]
@@ -58,37 +58,13 @@ if TYPE_CHECKING:
58
58
  ResourceRecordTypeDef,
59
59
  )
60
60
  else:
61
- EC2Client = (
62
- EC2ServiceResource
63
- ) = (
64
- RouteTableTypeDef
65
- ) = (
66
- SubnetTypeDef
67
- ) = (
61
+ EC2Client = EC2ServiceResource = RouteTableTypeDef = SubnetTypeDef = (
68
62
  TransitGatewayTypeDef
69
- ) = (
70
- TransitGatewayVpcAttachmentTypeDef
71
- ) = (
72
- VpcTypeDef
73
- ) = (
74
- IAMClient
75
- ) = (
63
+ ) = TransitGatewayVpcAttachmentTypeDef = VpcTypeDef = IAMClient = (
76
64
  AccessKeyMetadataTypeDef
77
- ) = (
78
- ImageTypeDef
79
- ) = (
80
- TagTypeDef
81
- ) = (
82
- LaunchPermissionModificationsTypeDef
83
- ) = (
65
+ ) = ImageTypeDef = TagTypeDef = LaunchPermissionModificationsTypeDef = (
84
66
  FilterTypeDef
85
- ) = (
86
- Route53Client
87
- ) = (
88
- ResourceRecordSetTypeDef
89
- ) = (
90
- ResourceRecordTypeDef
91
- ) = (
67
+ ) = Route53Client = ResourceRecordSetTypeDef = ResourceRecordTypeDef = (
92
68
  HostedZoneTypeDef
93
69
  ) = RDSClient = DBInstanceMessageTypeDef = UpgradeTargetTypeDef = object
94
70
 
@@ -866,7 +842,7 @@ class AWSApi: # pylint: disable=too-many-public-methods
866
842
  self, account_name: str, assume_role: str, assume_region: str, client_type="ec2"
867
843
  ) -> EC2Client:
868
844
  session = self.get_session(account_name)
869
- if assume_role == "":
845
+ if not assume_role:
870
846
  return self.get_session_client(
871
847
  session, client_type, region_name=assume_region
872
848
  )
reconcile/utils/config.py CHANGED
@@ -12,12 +12,11 @@ class SecretNotFound(Exception):
12
12
 
13
13
 
14
14
  def get_config():
15
- global _config
16
15
  return _config
17
16
 
18
17
 
19
18
  def init(config):
20
- global _config
19
+ global _config # noqa: PLW0603
21
20
  _config = config
22
21
  return _config
23
22
 
@@ -11,7 +11,7 @@ def environ(variables=None):
11
11
  @wraps(f)
12
12
  def f_environ(*args, **kwargs):
13
13
  for e in variables:
14
- if e not in os.environ or os.environ[e] == "":
14
+ if not os.environ.get(e):
15
15
  raise KeyError("Could not find environment variable: {}".format(e))
16
16
  f(*args, **kwargs)
17
17
 
reconcile/utils/git.py CHANGED
@@ -9,7 +9,9 @@ class GitError(Exception):
9
9
  def clone(repo_url, wd):
10
10
  # pylint: disable=subprocess-run-check
11
11
  cmd = ["git", "clone", repo_url, wd]
12
- result = subprocess.run(cmd, cwd=wd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
12
+ result = subprocess.run(
13
+ cmd, cwd=wd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
14
+ )
13
15
  if result.returncode != 0:
14
16
  raise GitError(f"git clone failed: {repo_url}")
15
17
 
@@ -17,7 +19,9 @@ def clone(repo_url, wd):
17
19
  def checkout(commit, wd):
18
20
  # pylint: disable=subprocess-run-check
19
21
  cmd = ["git", "checkout", commit]
20
- result = subprocess.run(cmd, cwd=wd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
22
+ result = subprocess.run(
23
+ cmd, cwd=wd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
24
+ )
21
25
  if result.returncode != 0:
22
26
  raise GitError(f"git checkout failed: {commit}")
23
27
 
@@ -28,7 +32,7 @@ def is_file_in_git_repo(file_path):
28
32
  # pylint: disable=subprocess-run-check
29
33
  cmd = ["git", "rev-parse", "--is-inside-work-tree"]
30
34
  result = subprocess.run(
31
- cmd, cwd=dir_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE
35
+ cmd, cwd=dir_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
32
36
  )
33
37
  return result.returncode == 0
34
38
 
@@ -38,7 +38,7 @@ def scan_history(repo_url, existing_keys):
38
38
  def get_suspected_files(error):
39
39
  suspects = []
40
40
  for e in error.split("\n"):
41
- if e == "":
41
+ if not e:
42
42
  break
43
43
  if e.startswith("warning"):
44
44
  continue
@@ -55,7 +55,7 @@ def get_leaked_keys(repo_wd, suspected_files, existing_keys):
55
55
  commit, file_relative_path = s[0], s[1]
56
56
  git.checkout(commit, repo_wd)
57
57
  file_path = os.path.join(repo_wd, file_relative_path)
58
- with open(file_path, "r") as f:
58
+ with open(file_path, "r", encoding="locale") as f:
59
59
  content = f.read()
60
60
  leaked_keys = [key for key in existing_keys if key in content]
61
61
  all_leaked_keys.extend(leaked_keys)
reconcile/utils/helm.py CHANGED
@@ -25,7 +25,7 @@ class JSONEncoder(json.JSONEncoder):
25
25
 
26
26
  def template(values: Mapping[str, Any]) -> Mapping[str, Any]:
27
27
  try:
28
- with tempfile.NamedTemporaryFile(mode="w+") as values_file:
28
+ with tempfile.NamedTemporaryFile(mode="w+", encoding="locale") as values_file:
29
29
  values_file.write(json.dumps(values, cls=JSONEncoder))
30
30
  values_file.flush()
31
31
  cmd = [
@@ -64,7 +64,7 @@ class JJB: # pylint: disable=too-many-public-methods
64
64
  ini = ini.replace('"', "")
65
65
  ini = ini.replace("false", "False")
66
66
  ini_file_path = "{}/{}.ini".format(wd, name)
67
- with open(ini_file_path, "w") as f:
67
+ with open(ini_file_path, "w", encoding="locale") as f:
68
68
  f.write(ini)
69
69
  f.write("\n")
70
70
  working_dirs[name] = wd
@@ -81,12 +81,12 @@ class JJB: # pylint: disable=too-many-public-methods
81
81
  if c["type"] == "jobs":
82
82
  for item in content:
83
83
  item["project"]["app_name"] = c["app"]["name"]
84
- with open(config_file_path, "a") as f:
84
+ with open(config_file_path, "a", encoding="locale") as f:
85
85
  yaml.dump(content, f)
86
86
  f.write("\n")
87
87
  else:
88
88
  config = c["config_path"]["content"]
89
- with open(config_file_path, "a") as f:
89
+ with open(config_file_path, "a", encoding="locale") as f:
90
90
  f.write(config)
91
91
  f.write("\n")
92
92
 
@@ -100,7 +100,7 @@ class JJB: # pylint: disable=too-many-public-methods
100
100
  the supplied configs"""
101
101
  for name, wd in self.working_dirs.items():
102
102
  config_path = "{}/config.yaml".format(wd)
103
- with open(config_path, "w") as f:
103
+ with open(config_path, "w", encoding="locale") as f:
104
104
  f.write(configs[name])
105
105
 
106
106
  def sort(self, configs):
@@ -136,7 +136,7 @@ class JJB: # pylint: disable=too-many-public-methods
136
136
  configs = {}
137
137
  for name, wd in self.working_dirs.items():
138
138
  config_path = "{}/config.yaml".format(wd)
139
- with open(config_path, "r") as f:
139
+ with open(config_path, "r", encoding="locale") as f:
140
140
  configs[name] = f.read()
141
141
 
142
142
  return configs
@@ -196,7 +196,7 @@ class JJB: # pylint: disable=too-many-public-methods
196
196
  logging.info([action, item_type, instance, item])
197
197
 
198
198
  if action == "update":
199
- with open(ft) as c, open(f) as d:
199
+ with open(ft, encoding="locale") as c, open(f, encoding="locale") as d:
200
200
  clines = c.readlines()
201
201
  dlines = d.readlines()
202
202
 
@@ -247,7 +247,7 @@ class JJB: # pylint: disable=too-many-public-methods
247
247
 
248
248
  @staticmethod
249
249
  def get_jjb(args):
250
- from jenkins_jobs.cli.entry import JenkinsJobs
250
+ from jenkins_jobs.cli.entry import JenkinsJobs # noqa: PLC0415
251
251
 
252
252
  return JenkinsJobs(args)
253
253
 
@@ -45,7 +45,7 @@ class JumpHostBase:
45
45
  self._identity_dir = tempfile.mkdtemp()
46
46
 
47
47
  identity_file = self._identity_dir + "/id"
48
- with open(identity_file, "w") as f:
48
+ with open(identity_file, "w", encoding="locale") as f:
49
49
  f.write(self._identity)
50
50
  os.chmod(identity_file, 0o600)
51
51
  self._identity_file = identity_file
@@ -96,7 +96,7 @@ class JumpHostSSH(JumpHostBase):
96
96
 
97
97
  def _init_known_hosts_file(self) -> None:
98
98
  known_hosts_file = self._identity_dir + "/known_hosts"
99
- with open(known_hosts_file, "w") as f:
99
+ with open(known_hosts_file, "w", encoding="locale") as f:
100
100
  f.write(self._known_hosts)
101
101
  os.chmod(known_hosts_file, 0o600)
102
102
  self.known_hosts_file = known_hosts_file
@@ -187,9 +187,9 @@ class MetricsContainer:
187
187
  self._gauges: dict[Type[GaugeMetric], dict[Sequence[str], float]] = defaultdict(
188
188
  dict
189
189
  )
190
- self._counters: dict[
191
- Type[CounterMetric], dict[Sequence[str], float]
192
- ] = defaultdict(dict)
190
+ self._counters: dict[Type[CounterMetric], dict[Sequence[str], float]] = (
191
+ defaultdict(dict)
192
+ )
193
193
 
194
194
  self._scopes: dict[Hashable, MetricsContainer] = {}
195
195
 
reconcile/utils/models.py CHANGED
@@ -29,56 +29,52 @@ def data_default_none(
29
29
  # Settings defaults
30
30
  if field.allow_none:
31
31
  data[field.alias] = None
32
- else:
33
- if isinstance(field.type_, type) and issubclass(field.type_, str):
34
- data[field.alias] = DEFAULT_STRING
35
- elif isinstance(field.type_, type) and issubclass(field.type_, bool):
36
- data[field.alias] = False
37
- elif isinstance(field.type_, type) and issubclass(field.type_, int):
38
- data[field.alias] = DEFAULT_INT
39
- else:
40
- if isinstance(field.type_, type) and issubclass(field.type_, BaseModel):
41
- if isinstance(data[field.alias], dict):
42
- data[field.alias] = data_default_none(
43
- field.type_, data[field.alias]
44
- )
45
- if isinstance(data[field.alias], list):
46
- data[field.alias] = [
47
- data_default_none(field.type_, item)
48
- for item in data[field.alias]
49
- if isinstance(item, dict)
50
- ]
51
- elif field.sub_fields:
52
- if all(
53
- isinstance(sub_field.type_, type)
54
- and issubclass(sub_field.type_, BaseModel)
55
- for sub_field in field.sub_fields
56
- ):
57
- # Union[ClassA, ClassB] field
58
- for sub_field in field.sub_fields:
59
- if isinstance(data[field.alias], dict):
60
- try:
61
- d = dict(data[field.alias])
62
- d.update(data_default_none(sub_field.type_, d))
63
- # Lets confirm we found a matching union class
64
- sub_field.type_(**d)
65
- data[field.alias] = d
66
- break
67
- except ValidationError:
68
- continue
69
- elif isinstance(data[field.alias], list) and len(field.sub_fields) == 1:
70
- # list[Union[ClassA, ClassB]] field
71
- for sub_data in data[field.alias]:
72
- for sub_field in field.sub_fields[0].sub_fields or []:
73
- try:
74
- d = dict(sub_data)
75
- d.update(data_default_none(sub_field.type_, d))
76
- # Lets confirm we found a matching union class
77
- sub_field.type_(**d)
78
- sub_data.update(d)
79
- break
80
- except ValidationError:
81
- continue
32
+ elif isinstance(field.type_, type) and issubclass(field.type_, str):
33
+ data[field.alias] = DEFAULT_STRING
34
+ elif isinstance(field.type_, type) and issubclass(field.type_, bool):
35
+ data[field.alias] = False
36
+ elif isinstance(field.type_, type) and issubclass(field.type_, int):
37
+ data[field.alias] = DEFAULT_INT
38
+ elif isinstance(field.type_, type) and issubclass(field.type_, BaseModel):
39
+ if isinstance(data[field.alias], dict):
40
+ data[field.alias] = data_default_none(field.type_, data[field.alias])
41
+ if isinstance(data[field.alias], list):
42
+ data[field.alias] = [
43
+ data_default_none(field.type_, item)
44
+ for item in data[field.alias]
45
+ if isinstance(item, dict)
46
+ ]
47
+ elif field.sub_fields:
48
+ if all(
49
+ isinstance(sub_field.type_, type)
50
+ and issubclass(sub_field.type_, BaseModel)
51
+ for sub_field in field.sub_fields
52
+ ):
53
+ # Union[ClassA, ClassB] field
54
+ for sub_field in field.sub_fields:
55
+ if isinstance(data[field.alias], dict):
56
+ try:
57
+ d = dict(data[field.alias])
58
+ d.update(data_default_none(sub_field.type_, d))
59
+ # Lets confirm we found a matching union class
60
+ sub_field.type_(**d)
61
+ data[field.alias] = d
62
+ break
63
+ except ValidationError:
64
+ continue
65
+ elif isinstance(data[field.alias], list) and len(field.sub_fields) == 1:
66
+ # list[Union[ClassA, ClassB]] field
67
+ for sub_data in data[field.alias]:
68
+ for sub_field in field.sub_fields[0].sub_fields or []:
69
+ try:
70
+ d = dict(sub_data)
71
+ d.update(data_default_none(sub_field.type_, d))
72
+ # Lets confirm we found a matching union class
73
+ sub_field.type_(**d)
74
+ sub_data.update(d)
75
+ break
76
+ except ValidationError:
77
+ continue
82
78
 
83
79
  return data
84
80
 
@@ -90,7 +86,7 @@ class CSV(list[str]):
90
86
  """
91
87
 
92
88
  @classmethod
93
- def __get_validators__(cls) -> Generator[Callable, None, None]:
89
+ def __get_validators__(cls) -> Generator[Callable, None, None]: # noqa: PLW3201
94
90
  yield cls.validate
95
91
  yield cls.length_validator
96
92
 
@@ -57,7 +57,7 @@ class CreateDeleteAwsAccessKey(MergeRequestBase):
57
57
  )
58
58
 
59
59
  # add a new email to be picked up by email-sender
60
- with open(BODY_TEMPLATE) as file_obj:
60
+ with open(BODY_TEMPLATE, encoding="locale") as file_obj:
61
61
  body_template = Template(
62
62
  file_obj.read(), keep_trailing_newline=True, trim_blocks=True
63
63
  )
@@ -232,7 +232,7 @@ def app_interface_email(
232
232
  apps: Optional[list[str]] = None,
233
233
  ) -> str:
234
234
  """Render app-interface-email template."""
235
- with open(EMAIL_TEMPLATE) as file_obj:
235
+ with open(EMAIL_TEMPLATE, encoding="locale") as file_obj:
236
236
  email_template = Template(
237
237
  file_obj.read(), keep_trailing_newline=True, trim_blocks=True
238
238
  )
@@ -36,7 +36,7 @@ class CreateDeleteUserAppInterface(MergeRequestBase):
36
36
  for path_spec in self.paths:
37
37
  path_type = path_spec["type"]
38
38
  path = path_spec["path"]
39
- if path_type in [PathTypes.USER, PathTypes.REQUEST, PathTypes.QUERY]:
39
+ if path_type in {PathTypes.USER, PathTypes.REQUEST, PathTypes.QUERY}:
40
40
  gitlab_cli.delete_file(
41
41
  branch_name=self.branch, file_path=path, commit_message=self.title
42
42
  )
reconcile/utils/oc.py CHANGED
@@ -382,7 +382,7 @@ class OCCli: # pylint: disable=too-many-public-methods
382
382
 
383
383
  self.is_log_slow_oc_reconcile = os.environ.get(
384
384
  "LOG_SLOW_OC_RECONCILE", ""
385
- ).lower() in ["true", "yes"]
385
+ ).lower() in {"true", "yes"}
386
386
 
387
387
  def _init(
388
388
  self,
@@ -454,7 +454,7 @@ class OCCli: # pylint: disable=too-many-public-methods
454
454
 
455
455
  self.is_log_slow_oc_reconcile = os.environ.get(
456
456
  "LOG_SLOW_OC_RECONCILE", ""
457
- ).lower() in ["true", "yes"]
457
+ ).lower() in {"true", "yes"}
458
458
 
459
459
  def whoami(self):
460
460
  return self._run(["whoami"])
@@ -719,7 +719,7 @@ class OCCli: # pylint: disable=too-many-public-methods
719
719
  ready_pods = [
720
720
  pod
721
721
  for pod in pods
722
- if pod["status"].get("phase") in ("Running", "Succeeded")
722
+ if pod["status"].get("phase") in {"Running", "Succeeded"}
723
723
  ]
724
724
 
725
725
  if not ready_pods:
@@ -731,7 +731,7 @@ class OCCli: # pylint: disable=too-many-public-methods
731
731
  if follow:
732
732
  cmd.append("-f")
733
733
  # pylint: disable=consider-using-with
734
- output_file = open(os.path.join(output, name), "w")
734
+ output_file = open(os.path.join(output, name), "w", encoding="locale")
735
735
  # collect logs to file async
736
736
  Popen(self.oc_base_cmd + cmd, stdout=output_file)
737
737
 
@@ -983,7 +983,7 @@ class OCCli: # pylint: disable=too-many-public-methods
983
983
  kind: str,
984
984
  include_optional: bool = True,
985
985
  ) -> dict[str, set[str]]:
986
- if kind not in ("Secret", "ConfigMap"):
986
+ if kind not in {"Secret", "ConfigMap"}:
987
987
  raise KeyError(f"unsupported resource kind: {kind}")
988
988
  optional = "optional"
989
989
  if kind == "Secret":
@@ -1117,7 +1117,7 @@ class OCCli: # pylint: disable=too-many-public-methods
1117
1117
  find = False
1118
1118
  for gv in self.api_resources[kind]:
1119
1119
  if apigroup_override == gv.group:
1120
- if gv.group == "":
1120
+ if not gv.group:
1121
1121
  group_version = gv.api_version
1122
1122
  else:
1123
1123
  group_version = f"{gv.group}/{gv.api_version}"
@@ -1389,7 +1389,7 @@ class OC:
1389
1389
  use_native_env = os.environ.get("USE_NATIVE_CLIENT", "")
1390
1390
  use_native = True
1391
1391
  if len(use_native_env) > 0:
1392
- use_native = use_native_env.lower() in ["true", "yes"]
1392
+ use_native = use_native_env.lower() in {"true", "yes"}
1393
1393
  else:
1394
1394
  enable_toggle = "openshift-resources-native-client"
1395
1395
  use_native = get_feature_toggle_state(
@@ -1756,7 +1756,7 @@ def validate_labels(labels: dict[str, str]) -> Iterable[str]:
1756
1756
  f"Label key prefix is invalid, it needs to match "
1757
1757
  f"'{k_prefix_pattern}'': {prefix}"
1758
1758
  )
1759
- if prefix in ("kubernetes.io", "k8s.io"):
1759
+ if prefix in {"kubernetes.io", "k8s.io"}:
1760
1760
  err.append(f"Label key prefix is reserved: {prefix}")
1761
1761
 
1762
1762
  return err
@@ -153,20 +153,19 @@ class OCConnectionParameters:
153
153
  logging.debug(
154
154
  f"No admin automation token set for cluster '{cluster.name}', but privileged access requested."
155
155
  )
156
+ elif cluster.automation_token:
157
+ try:
158
+ automation_token = OCConnectionParameters._get_automation_token(
159
+ secret_reader, cluster.automation_token, cluster
160
+ )
161
+ except SecretNotFound:
162
+ logging.error(
163
+ f"[{cluster.name}] automation token {cluster.automation_token} not found"
164
+ )
156
165
  else:
157
- if cluster.automation_token:
158
- try:
159
- automation_token = OCConnectionParameters._get_automation_token(
160
- secret_reader, cluster.automation_token, cluster
161
- )
162
- except SecretNotFound:
163
- logging.error(
164
- f"[{cluster.name}] automation token {cluster.automation_token} not found"
165
- )
166
- else:
167
- # Note, that currently OCMap uses OCLogMsg if a token is missing, i.e.,
168
- # for now this is valid behavior.
169
- logging.debug(f"No automation token for cluster '{cluster.name}'.")
166
+ # Note, that currently OCMap uses OCLogMsg if a token is missing, i.e.,
167
+ # for now this is valid behavior.
168
+ logging.debug(f"No automation token for cluster '{cluster.name}'.")
170
169
 
171
170
  disabled_integrations = []
172
171
  if cluster.disable:
@@ -207,7 +207,7 @@ class OCMCluster(BaseModel):
207
207
  return (
208
208
  self.managed
209
209
  and self.state == OCMClusterState.READY
210
- and self.product.id in [PRODUCT_ID_OSD, PRODUCT_ID_ROSA]
210
+ and self.product.id in {PRODUCT_ID_OSD, PRODUCT_ID_ROSA}
211
211
  )
212
212
 
213
213
  @property