qontract-reconcile 0.10.2.dev56__py3-none-any.whl → 0.10.2.dev58__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.
tools/qontract_cli.py CHANGED
@@ -10,7 +10,6 @@ import sys
10
10
  import tempfile
11
11
  import textwrap
12
12
  from collections import defaultdict
13
- from collections.abc import Callable, Mapping
14
13
  from datetime import (
15
14
  UTC,
16
15
  datetime,
@@ -55,6 +54,7 @@ from reconcile.change_owners.bundle import NoOpFileDiffResolver
55
54
  from reconcile.change_owners.change_log_tracking import (
56
55
  BUNDLE_DIFFS_OBJ,
57
56
  ChangeLog,
57
+ ChangeLogItem,
58
58
  )
59
59
  from reconcile.change_owners.change_owners import (
60
60
  fetch_change_type_processors,
@@ -81,7 +81,6 @@ from reconcile.gql_definitions.app_sre_tekton_access_revalidation.roles import (
81
81
  from reconcile.gql_definitions.common.app_interface_vault_settings import (
82
82
  AppInterfaceSettingsV1,
83
83
  )
84
- from reconcile.gql_definitions.common.clusters import ClusterSpecROSAV1
85
84
  from reconcile.gql_definitions.fragments.aus_organization import AUSOCMOrganization
86
85
  from reconcile.gql_definitions.integrations import integrations as integrations_gql
87
86
  from reconcile.gql_definitions.maintenance import maintenances as maintenances_gql
@@ -153,7 +152,6 @@ from reconcile.utils.oc_map import (
153
152
  init_oc_map_from_clusters,
154
153
  )
155
154
  from reconcile.utils.ocm import OCM_PRODUCT_ROSA, OCMMap
156
- from reconcile.utils.ocm.upgrades import get_upgrade_policies
157
155
  from reconcile.utils.ocm_base_client import init_ocm_base_client
158
156
  from reconcile.utils.output import print_output
159
157
  from reconcile.utils.saasherder.models import TargetSpec
@@ -192,7 +190,7 @@ else:
192
190
  CopySourceTypeDef = object
193
191
 
194
192
 
195
- def output(function: Callable) -> Callable:
193
+ def output(function):
196
194
  function = click.option(
197
195
  "--output",
198
196
  "-o",
@@ -203,14 +201,14 @@ def output(function: Callable) -> Callable:
203
201
  return function
204
202
 
205
203
 
206
- def sort(function: Callable) -> Callable:
204
+ def sort(function):
207
205
  function = click.option(
208
206
  "--sort", "-s", help="sort output", default=True, type=bool
209
207
  )(function)
210
208
  return function
211
209
 
212
210
 
213
- def to_string(function: Callable) -> Callable:
211
+ def to_string(function):
214
212
  function = click.option(
215
213
  "--to-string", help="stringify output", default=False, type=bool
216
214
  )(function)
@@ -220,14 +218,14 @@ def to_string(function: Callable) -> Callable:
220
218
  @click.group()
221
219
  @config_file
222
220
  @click.pass_context
223
- def root(ctx: click.Context, configfile: str) -> None:
221
+ def root(ctx, configfile):
224
222
  ctx.ensure_object(dict)
225
223
  config.init_from_toml(configfile)
226
224
  gql.init_from_config()
227
225
 
228
226
 
229
227
  @root.result_callback()
230
- def exit_cli(ctx: click.Context, configfile: str) -> None:
228
+ def exit_cli(ctx, configfile):
231
229
  GqlApiSingleton.close()
232
230
 
233
231
 
@@ -236,7 +234,7 @@ def exit_cli(ctx: click.Context, configfile: str) -> None:
236
234
  @sort
237
235
  @to_string
238
236
  @click.pass_context
239
- def get(ctx: click.Context, output: str, sort: bool, to_string: bool) -> None:
237
+ def get(ctx, output, sort, to_string):
240
238
  ctx.obj["options"] = {
241
239
  "output": output,
242
240
  "sort": sort,
@@ -247,7 +245,7 @@ def get(ctx: click.Context, output: str, sort: bool, to_string: bool) -> None:
247
245
  @root.group()
248
246
  @output
249
247
  @click.pass_context
250
- def describe(ctx: click.Context, output: str) -> None:
248
+ def describe(ctx, output):
251
249
  ctx.obj["options"] = {
252
250
  "output": output,
253
251
  }
@@ -255,7 +253,7 @@ def describe(ctx: click.Context, output: str) -> None:
255
253
 
256
254
  @get.command()
257
255
  @click.pass_context
258
- def settings(ctx: click.Context) -> None:
256
+ def settings(ctx):
259
257
  settings = queries.get_app_interface_settings()
260
258
  columns = ["vault", "kubeBinary", "mergeRequestGateway"]
261
259
  print_output(ctx.obj["options"], [settings], columns)
@@ -264,7 +262,7 @@ def settings(ctx: click.Context) -> None:
264
262
  @get.command()
265
263
  @click.argument("name", default="")
266
264
  @click.pass_context
267
- def aws_accounts(ctx: click.Context, name: str) -> None:
265
+ def aws_accounts(ctx, name):
268
266
  accounts = queries.get_aws_accounts(name=name)
269
267
  if not accounts:
270
268
  print("no aws accounts found")
@@ -276,7 +274,7 @@ def aws_accounts(ctx: click.Context, name: str) -> None:
276
274
  @get.command()
277
275
  @click.argument("name", default="")
278
276
  @click.pass_context
279
- def clusters(ctx: click.Context, name: str) -> None:
277
+ def clusters(ctx, name):
280
278
  clusters = queries.get_clusters()
281
279
  if name:
282
280
  clusters = [c for c in clusters if c["name"] == name]
@@ -293,7 +291,7 @@ def clusters(ctx: click.Context, name: str) -> None:
293
291
  @get.command()
294
292
  @click.argument("name", default="")
295
293
  @click.pass_context
296
- def cluster_upgrades(ctx: click.Context, name: str) -> None:
294
+ def cluster_upgrades(ctx, name):
297
295
  settings = queries.get_app_interface_settings()
298
296
 
299
297
  clusters = queries.get_clusters()
@@ -324,11 +322,12 @@ def cluster_upgrades(ctx: click.Context, name: str) -> None:
324
322
  if data.get("upgradePolicy") == "automatic":
325
323
  data["schedule"] = c["upgradePolicy"]["schedule"]
326
324
  ocm = ocm_map.get(c["name"])
327
- upgrade_policy = get_upgrade_policies(ocm.ocm_api, c["spec"]["id"])
328
- if upgrade_policy and len(upgrade_policy) > 0:
329
- next_run = upgrade_policy[0].get("next_run")
330
- if next_run:
331
- data["next_run"] = next_run
325
+ if ocm:
326
+ upgrade_policy = ocm.get_upgrade_policies(c["name"])
327
+ if upgrade_policy and len(upgrade_policy) > 0:
328
+ next_run = upgrade_policy[0].get("next_run")
329
+ if next_run:
330
+ data["next_run"] = next_run
332
331
  else:
333
332
  data["upgradePolicy"] = "manual"
334
333
 
@@ -342,7 +341,7 @@ def cluster_upgrades(ctx: click.Context, name: str) -> None:
342
341
  @get.command()
343
342
  @environ(["APP_INTERFACE_STATE_BUCKET", "APP_INTERFACE_STATE_BUCKET_ACCOUNT"])
344
343
  @click.pass_context
345
- def version_history(ctx: click.Context) -> None:
344
+ def version_history(ctx):
346
345
  import reconcile.aus.ocm_upgrade_scheduler as ous
347
346
 
348
347
  clusters = aus_clusters_query(query_func=gql.get_api().query).clusters or []
@@ -378,11 +377,11 @@ def version_history(ctx: click.Context) -> None:
378
377
 
379
378
  def get_upgrade_policies_data(
380
379
  org_upgrade_specs: list[OrganizationUpgradeSpec],
381
- md_output: bool,
382
- integration: str,
383
- workload: str | None = None,
384
- show_only_soaking_upgrades: bool = False,
385
- by_workload: bool = False,
380
+ md_output,
381
+ integration,
382
+ workload=None,
383
+ show_only_soaking_upgrades=False,
384
+ by_workload=False,
386
385
  ) -> list:
387
386
  if not org_upgrade_specs:
388
387
  return []
@@ -563,12 +562,12 @@ more than 6 hours will be highlighted.
563
562
  )
564
563
  @click.pass_context
565
564
  def cluster_upgrade_policies(
566
- ctx: click.Context,
567
- cluster: str | None = None,
568
- workload: str | None = None,
569
- show_only_soaking_upgrades: bool = False,
570
- by_workload: bool = False,
571
- ) -> None:
565
+ ctx,
566
+ cluster=None,
567
+ workload=None,
568
+ show_only_soaking_upgrades=False,
569
+ by_workload=False,
570
+ ):
572
571
  print(
573
572
  "https://grafana.app-sre.devshift.net/d/ukLXCSwVz/aus-cluster-upgrade-overview"
574
573
  )
@@ -583,7 +582,9 @@ def inherit_version_data_text(org: AUSOCMOrganization) -> str:
583
582
 
584
583
  @get.command()
585
584
  @click.pass_context
586
- def ocm_fleet_upgrade_policies(ctx: click.Context) -> None:
585
+ def ocm_fleet_upgrade_policies(
586
+ ctx,
587
+ ):
587
588
  from reconcile.aus.ocm_upgrade_scheduler_org import (
588
589
  OCMClusterUpgradeSchedulerOrgIntegration,
589
590
  )
@@ -616,12 +617,7 @@ def ocm_fleet_upgrade_policies(ctx: click.Context) -> None:
616
617
  help="Ignore STS clusters",
617
618
  )
618
619
  @click.pass_context
619
- def aus_fleet_upgrade_policies(
620
- ctx: click.Context,
621
- ocm_env: str | None,
622
- ocm_org_ids: str | None,
623
- ignore_sts_clusters: bool,
624
- ) -> None:
620
+ def aus_fleet_upgrade_policies(ctx, ocm_env, ocm_org_ids, ignore_sts_clusters):
625
621
  from reconcile.aus.advanced_upgrade_service import AdvancedUpgradeServiceIntegration
626
622
 
627
623
  parsed_ocm_org_ids = set(ocm_org_ids.split(",")) if ocm_org_ids else None
@@ -638,8 +634,8 @@ def aus_fleet_upgrade_policies(
638
634
 
639
635
 
640
636
  def generate_fleet_upgrade_policices_report(
641
- ctx: click.Context, aus_integration: AdvancedUpgradeSchedulerBaseIntegration
642
- ) -> None:
637
+ ctx, aus_integration: AdvancedUpgradeSchedulerBaseIntegration
638
+ ):
643
639
  md_output = ctx.obj["options"]["output"] == "md"
644
640
 
645
641
  org_upgrade_specs: dict[str, OrganizationUpgradeSpec] = {}
@@ -957,7 +953,7 @@ def upgrade_cluster_addon(
957
953
  )
958
954
 
959
955
 
960
- def has_cluster_account_access(cluster: dict[str, Any]) -> bool:
956
+ def has_cluster_account_access(cluster: dict[str, Any]):
961
957
  spec = cluster.get("spec") or {}
962
958
  account = spec.get("account")
963
959
  return account or cluster.get("awsInfrastructureManagementAccounts") is not None
@@ -966,7 +962,7 @@ def has_cluster_account_access(cluster: dict[str, Any]) -> bool:
966
962
  @get.command()
967
963
  @click.argument("name", default="")
968
964
  @click.pass_context
969
- def clusters_network(ctx: click.Context, name: str) -> None:
965
+ def clusters_network(ctx, name):
970
966
  settings = queries.get_app_interface_settings()
971
967
  clusters = [
972
968
  c
@@ -1029,7 +1025,7 @@ def clusters_network(ctx: click.Context, name: str) -> None:
1029
1025
 
1030
1026
  @get.command()
1031
1027
  @click.pass_context
1032
- def network_reservations(ctx: click.Context) -> None:
1028
+ def network_reservations(ctx) -> None:
1033
1029
  from reconcile.typed_queries.reserved_networks import get_networks
1034
1030
 
1035
1031
  columns = [
@@ -1042,10 +1038,11 @@ def network_reservations(ctx: click.Context) -> None:
1042
1038
  ]
1043
1039
  network_table = []
1044
1040
 
1045
- def md_link(url: str) -> str:
1041
+ def md_link(url) -> str:
1046
1042
  if ctx.obj["options"]["output"] == "md":
1047
1043
  return f"[{url}]({url})"
1048
- return url
1044
+ else:
1045
+ return url
1049
1046
 
1050
1047
  for network in get_networks():
1051
1048
  parentAddress = "none"
@@ -1086,7 +1083,7 @@ def network_reservations(ctx: click.Context) -> None:
1086
1083
  default=24,
1087
1084
  )
1088
1085
  @click.pass_context
1089
- def cidr_blocks(ctx: click.Context, for_cluster: int, mask: int) -> None:
1086
+ def cidr_blocks(ctx, for_cluster: int, mask: int) -> None:
1090
1087
  import ipaddress
1091
1088
 
1092
1089
  from reconcile.typed_queries.aws_vpcs import get_aws_vpcs
@@ -1212,7 +1209,7 @@ def ocm_aws_infrastructure_access_switch_role_links_data() -> list[dict]:
1212
1209
 
1213
1210
  @get.command()
1214
1211
  @click.pass_context
1215
- def ocm_aws_infrastructure_access_switch_role_links_flat(ctx: click.Context) -> None:
1212
+ def ocm_aws_infrastructure_access_switch_role_links_flat(ctx):
1216
1213
  results = ocm_aws_infrastructure_access_switch_role_links_data()
1217
1214
  columns = ["cluster", "user_arn", "access_level", "switch_role_link"]
1218
1215
  print_output(ctx.obj["options"], results, columns)
@@ -1220,11 +1217,11 @@ def ocm_aws_infrastructure_access_switch_role_links_flat(ctx: click.Context) ->
1220
1217
 
1221
1218
  @get.command()
1222
1219
  @click.pass_context
1223
- def ocm_aws_infrastructure_access_switch_role_links(ctx: click.Context) -> None:
1220
+ def ocm_aws_infrastructure_access_switch_role_links(ctx):
1224
1221
  if ctx.obj["options"]["output"] != "md":
1225
1222
  raise Exception(f"Unupported output: {ctx.obj['options']['output']}")
1226
1223
  results = ocm_aws_infrastructure_access_switch_role_links_data()
1227
- by_user: dict = {}
1224
+ by_user = {}
1228
1225
  for r in results:
1229
1226
  by_user.setdefault(r["user"], []).append(r)
1230
1227
  columns = ["cluster", "source_login", "access_level", "switch_role_link"]
@@ -1238,7 +1235,7 @@ def ocm_aws_infrastructure_access_switch_role_links(ctx: click.Context) -> None:
1238
1235
 
1239
1236
  @get.command()
1240
1237
  @click.pass_context
1241
- def clusters_aws_account_ids(ctx: click.Context) -> None:
1238
+ def clusters_aws_account_ids(ctx):
1242
1239
  settings = queries.get_app_interface_settings()
1243
1240
  clusters = [c for c in queries.get_clusters() if c.get("ocm") is not None]
1244
1241
  ocm_map = OCMMap(clusters=clusters, settings=settings)
@@ -1268,7 +1265,7 @@ def clusters_aws_account_ids(ctx: click.Context) -> None:
1268
1265
  @root.command()
1269
1266
  @click.argument("account_name")
1270
1267
  @click.pass_context
1271
- def user_credentials_migrate_output(ctx: click.Context, account_name: str) -> None:
1268
+ def user_credentials_migrate_output(ctx, account_name) -> None:
1272
1269
  accounts = queries.get_state_aws_accounts()
1273
1270
  state = init_state(integration="account-notifier")
1274
1271
  skip_accounts, appsre_pgp_key, _ = tfu.get_reencrypt_settings()
@@ -1310,7 +1307,7 @@ def user_credentials_migrate_output(ctx: click.Context, account_name: str) -> No
1310
1307
 
1311
1308
  @get.command()
1312
1309
  @click.pass_context
1313
- def aws_route53_zones(ctx: click.Context) -> None:
1310
+ def aws_route53_zones(ctx):
1314
1311
  zones = queries.get_dns_zones()
1315
1312
 
1316
1313
  results = []
@@ -1333,7 +1330,7 @@ def aws_route53_zones(ctx: click.Context) -> None:
1333
1330
  @click.argument("cluster_name")
1334
1331
  @click.option("--cluster-admin/--no-cluster-admin", default=False)
1335
1332
  @click.pass_context
1336
- def bot_login(ctx: click.Context, cluster_name: str, cluster_admin: bool) -> None:
1333
+ def bot_login(ctx, cluster_name, cluster_admin):
1337
1334
  settings = queries.get_app_interface_settings()
1338
1335
  secret_reader = SecretReader(settings=settings)
1339
1336
  clusters = queries.get_clusters()
@@ -1356,7 +1353,7 @@ def bot_login(ctx: click.Context, cluster_name: str, cluster_admin: bool) -> Non
1356
1353
  )
1357
1354
  @click.argument("org_name")
1358
1355
  @click.pass_context
1359
- def ocm_login(ctx: click.Context, org_name: str) -> None:
1356
+ def ocm_login(ctx, org_name):
1360
1357
  settings = queries.get_app_interface_settings()
1361
1358
  secret_reader = SecretReader(settings=settings)
1362
1359
  ocms = [
@@ -1383,7 +1380,7 @@ def ocm_login(ctx: click.Context, org_name: str) -> None:
1383
1380
  )
1384
1381
  @click.argument("account_name")
1385
1382
  @click.pass_context
1386
- def aws_creds(ctx: click.Context, account_name: str) -> None:
1383
+ def aws_creds(ctx, account_name):
1387
1384
  settings = queries.get_app_interface_settings()
1388
1385
  secret_reader = SecretReader(settings=settings)
1389
1386
  accounts = queries.get_aws_accounts(name=account_name)
@@ -1426,14 +1423,8 @@ def aws_creds(ctx: click.Context, account_name: str) -> None:
1426
1423
  )
1427
1424
  @click.pass_context
1428
1425
  def copy_tfstate(
1429
- ctx: click.Context,
1430
- source_bucket: str,
1431
- source_object_path: str,
1432
- account_uid: str,
1433
- rename: str | None,
1434
- region: str | None,
1435
- force: bool,
1436
- ) -> None:
1426
+ ctx, source_bucket, source_object_path, account_uid, rename, region, force
1427
+ ):
1437
1428
  settings = queries.get_app_interface_settings()
1438
1429
  secret_reader = SecretReader(settings=settings)
1439
1430
  accounts = queries.get_aws_accounts(uid=account_uid, terraform_state=True)
@@ -1454,6 +1445,7 @@ def copy_tfstate(
1454
1445
  )
1455
1446
  return
1456
1447
 
1448
+ dest_filename = ""
1457
1449
  if rename:
1458
1450
  dest_filename = rename.removesuffix(".tfstate")
1459
1451
  else:
@@ -1514,26 +1506,20 @@ def copy_tfstate(
1514
1506
  @get.command(short_help='obtain "rosa create cluster" command by cluster name')
1515
1507
  @click.argument("cluster_name")
1516
1508
  @click.pass_context
1517
- def rosa_create_cluster_command(ctx: click.Context, cluster_name: str) -> None:
1509
+ def rosa_create_cluster_command(ctx, cluster_name):
1518
1510
  clusters = [c for c in get_clusters() if c.name == cluster_name]
1519
- if not clusters:
1511
+ try:
1512
+ cluster = clusters[0]
1513
+ except IndexError:
1520
1514
  print(f"{cluster_name} not found.")
1521
1515
  sys.exit(1)
1522
- cluster = clusters[0]
1523
1516
 
1524
- if (
1525
- not cluster.spec
1526
- or cluster.spec.product != OCM_PRODUCT_ROSA
1527
- or not isinstance(cluster.spec, ClusterSpecROSAV1)
1528
- ):
1517
+ if cluster.spec.product != OCM_PRODUCT_ROSA:
1529
1518
  print("must be a rosa cluster.")
1530
1519
  sys.exit(1)
1531
1520
 
1532
1521
  settings = queries.get_app_interface_settings()
1533
1522
  account = cluster.spec.account
1534
- if not account:
1535
- print("account not found.")
1536
- sys.exit(1)
1537
1523
 
1538
1524
  if account.billing_account:
1539
1525
  billing_account = account.billing_account.uid
@@ -1543,19 +1529,6 @@ def rosa_create_cluster_command(ctx: click.Context, cluster_name: str) -> None:
1543
1529
  ) as aws_api:
1544
1530
  billing_account = aws_api.get_organization_billing_account(account.name)
1545
1531
 
1546
- if not cluster.spec.oidc_endpoint_url:
1547
- print("oidc_endpoint_url not set.")
1548
- sys.exit(1)
1549
- if not cluster.spec.subnet_ids:
1550
- print("subnet_ids not set.")
1551
- sys.exit(1)
1552
- if not cluster.network:
1553
- print("network not set.")
1554
- sys.exit(1)
1555
- if not cluster.machine_pools:
1556
- print("machine_pools not set.")
1557
- sys.exit(1)
1558
-
1559
1532
  print(
1560
1533
  " ".join([
1561
1534
  "rosa create cluster",
@@ -1609,9 +1582,7 @@ def rosa_create_cluster_command(ctx: click.Context, cluster_name: str) -> None:
1609
1582
  @click.argument("jumphost_hostname", required=False)
1610
1583
  @click.argument("cluster_name", required=False)
1611
1584
  @click.pass_context
1612
- def sshuttle_command(
1613
- ctx: click.Context, jumphost_hostname: str | None, cluster_name: str | None
1614
- ) -> None:
1585
+ def sshuttle_command(ctx, jumphost_hostname: str | None, cluster_name: str | None):
1615
1586
  jumphosts_query_data = queries.get_jumphosts(hostname=jumphost_hostname)
1616
1587
  jumphosts = jumphosts_query_data.jumphosts or []
1617
1588
  for jh in jumphosts:
@@ -1633,9 +1604,7 @@ def sshuttle_command(
1633
1604
  @click.argument("instance_name")
1634
1605
  @click.argument("job_name")
1635
1606
  @click.pass_context
1636
- def jenkins_job_vault_secrets(
1637
- ctx: click.Context, instance_name: str, job_name: str
1638
- ) -> None:
1607
+ def jenkins_job_vault_secrets(ctx, instance_name: str, job_name: str) -> None:
1639
1608
  secret_reader = SecretReader(queries.get_secret_reader_settings())
1640
1609
  jjb: JJB = init_jjb(secret_reader, instance_name, config_name=None, print_only=True)
1641
1610
  jobs = jjb.get_all_jobs([job_name], instance_name)[instance_name]
@@ -1660,7 +1629,7 @@ def jenkins_job_vault_secrets(
1660
1629
  @get.command()
1661
1630
  @click.argument("name", default="")
1662
1631
  @click.pass_context
1663
- def namespaces(ctx: click.Context, name: str) -> None:
1632
+ def namespaces(ctx, name):
1664
1633
  namespaces = queries.get_namespaces()
1665
1634
  if name:
1666
1635
  namespaces = [ns for ns in namespaces if ns["name"] == name]
@@ -1672,7 +1641,7 @@ def namespaces(ctx: click.Context, name: str) -> None:
1672
1641
  print_output(ctx.obj["options"], namespaces, columns)
1673
1642
 
1674
1643
 
1675
- def add_resource(item: dict, resource: Mapping, columns: list[str]) -> None:
1644
+ def add_resource(item, resource, columns):
1676
1645
  provider = resource["provider"]
1677
1646
  if provider not in columns:
1678
1647
  columns.append(provider)
@@ -1683,11 +1652,11 @@ def add_resource(item: dict, resource: Mapping, columns: list[str]) -> None:
1683
1652
 
1684
1653
  @get.command
1685
1654
  @click.pass_context
1686
- def cluster_openshift_resources(ctx: click.Context) -> None:
1655
+ def cluster_openshift_resources(ctx):
1687
1656
  gqlapi = gql.get_api()
1688
1657
  namespaces = gqlapi.query(orb.NAMESPACES_QUERY)["namespaces"]
1689
1658
  columns = ["name", "total"]
1690
- results: dict = {}
1659
+ results = {}
1691
1660
  for ns_info in namespaces:
1692
1661
  cluster_name = ns_info["cluster"]["name"]
1693
1662
  item = {"name": cluster_name, "total": 0}
@@ -1708,10 +1677,10 @@ def cluster_openshift_resources(ctx: click.Context) -> None:
1708
1677
 
1709
1678
  @get.command
1710
1679
  @click.pass_context
1711
- def aws_terraform_resources(ctx: click.Context) -> None:
1680
+ def aws_terraform_resources(ctx):
1712
1681
  namespaces = tfr.get_namespaces()
1713
1682
  columns = ["name", "total"]
1714
- results: dict = {}
1683
+ results = {}
1715
1684
  for ns_info in namespaces:
1716
1685
  specs = (
1717
1686
  get_external_resource_specs(
@@ -1763,7 +1732,7 @@ def rds_region(
1763
1732
 
1764
1733
  @get.command
1765
1734
  @click.pass_context
1766
- def rds(ctx: click.Context) -> None:
1735
+ def rds(ctx):
1767
1736
  namespaces = tfr.get_namespaces()
1768
1737
  accounts = {a["name"]: a for a in queries.get_aws_accounts()}
1769
1738
  results = []
@@ -1841,7 +1810,7 @@ You can view the source of this Markdown to extract the JSON data.
1841
1810
 
1842
1811
  @get.command
1843
1812
  @click.pass_context
1844
- def rds_recommendations(ctx: click.Context) -> None:
1813
+ def rds_recommendations(ctx):
1845
1814
  IGNORED_STATUSES = ("resolved",)
1846
1815
  IGNORED_SEVERITIES = ("informational",)
1847
1816
 
@@ -1920,7 +1889,7 @@ def rds_recommendations(ctx: click.Context) -> None:
1920
1889
 
1921
1890
  @get.command()
1922
1891
  @click.pass_context
1923
- def products(ctx: click.Context) -> None:
1892
+ def products(ctx):
1924
1893
  products = queries.get_products()
1925
1894
  columns = ["name", "description"]
1926
1895
  print_output(ctx.obj["options"], products, columns)
@@ -1929,7 +1898,7 @@ def products(ctx: click.Context) -> None:
1929
1898
  @describe.command()
1930
1899
  @click.argument("name")
1931
1900
  @click.pass_context
1932
- def product(ctx: click.Context, name: str) -> None:
1901
+ def product(ctx, name):
1933
1902
  products = queries.get_products()
1934
1903
  products = [p for p in products if p["name"].lower() == name.lower()]
1935
1904
  if len(products) != 1:
@@ -1944,7 +1913,7 @@ def product(ctx: click.Context, name: str) -> None:
1944
1913
 
1945
1914
  @get.command()
1946
1915
  @click.pass_context
1947
- def environments(ctx: click.Context) -> None:
1916
+ def environments(ctx):
1948
1917
  environments = queries.get_environments()
1949
1918
  columns = ["name", "description", "product.name"]
1950
1919
  # TODO(mafriedm): fix this
@@ -1956,7 +1925,7 @@ def environments(ctx: click.Context) -> None:
1956
1925
  @describe.command()
1957
1926
  @click.argument("name")
1958
1927
  @click.pass_context
1959
- def environment(ctx: click.Context, name: str) -> None:
1928
+ def environment(ctx, name):
1960
1929
  environments = queries.get_environments()
1961
1930
  environments = [e for e in environments if e["name"].lower() == name.lower()]
1962
1931
  if len(environments) != 1:
@@ -1974,7 +1943,7 @@ def environment(ctx: click.Context, name: str) -> None:
1974
1943
 
1975
1944
  @get.command()
1976
1945
  @click.pass_context
1977
- def services(ctx: click.Context) -> None:
1946
+ def services(ctx):
1978
1947
  apps = queries.get_apps()
1979
1948
  columns = ["name", "path", "onboardingStatus"]
1980
1949
  print_output(ctx.obj["options"], apps, columns)
@@ -1982,15 +1951,17 @@ def services(ctx: click.Context) -> None:
1982
1951
 
1983
1952
  @get.command()
1984
1953
  @click.pass_context
1985
- def repos(ctx: click.Context) -> None:
1954
+ def repos(ctx):
1986
1955
  repos = queries.get_repos()
1987
- print_output(ctx.obj["options"], [{"url": r} for r in repos], ["url"])
1956
+ repos = [{"url": r} for r in repos]
1957
+ columns = ["url"]
1958
+ print_output(ctx.obj["options"], repos, columns)
1988
1959
 
1989
1960
 
1990
1961
  @get.command()
1991
1962
  @click.argument("org_username")
1992
1963
  @click.pass_context
1993
- def roles(ctx: click.Context, org_username: str) -> None:
1964
+ def roles(ctx, org_username):
1994
1965
  users = queries.get_roles()
1995
1966
  users = [u for u in users if u["org_username"] == org_username]
1996
1967
 
@@ -2001,7 +1972,7 @@ def roles(ctx: click.Context, org_username: str) -> None:
2001
1972
  user = users[0]
2002
1973
 
2003
1974
  # type, name, resource, [ref]
2004
- roles: dict[tuple[str, str, str], set[str]] = defaultdict(set)
1975
+ roles: dict[(str, str, str), set] = defaultdict(set)
2005
1976
 
2006
1977
  for role in user["roles"]:
2007
1978
  role_name = role["path"]
@@ -2055,7 +2026,7 @@ def roles(ctx: click.Context, org_username: str) -> None:
2055
2026
  @get.command()
2056
2027
  @click.argument("org_username", default="")
2057
2028
  @click.pass_context
2058
- def users(ctx: click.Context, org_username: str) -> None:
2029
+ def users(ctx, org_username):
2059
2030
  users = queries.get_users()
2060
2031
  if org_username:
2061
2032
  users = [u for u in users if u["org_username"] == org_username]
@@ -2066,7 +2037,7 @@ def users(ctx: click.Context, org_username: str) -> None:
2066
2037
 
2067
2038
  @get.command()
2068
2039
  @click.pass_context
2069
- def integrations(ctx: click.Context) -> None:
2040
+ def integrations(ctx):
2070
2041
  environments = queries.get_integrations()
2071
2042
  columns = ["name", "description"]
2072
2043
  print_output(ctx.obj["options"], environments, columns)
@@ -2074,7 +2045,7 @@ def integrations(ctx: click.Context) -> None:
2074
2045
 
2075
2046
  @get.command()
2076
2047
  @click.pass_context
2077
- def quay_mirrors(ctx: click.Context) -> None:
2048
+ def quay_mirrors(ctx):
2078
2049
  apps = queries.get_quay_repos()
2079
2050
 
2080
2051
  mirrors = []
@@ -2112,9 +2083,7 @@ def quay_mirrors(ctx: click.Context) -> None:
2112
2083
  @click.argument("kind")
2113
2084
  @click.argument("name")
2114
2085
  @click.pass_context
2115
- def root_owner(
2116
- ctx: click.Context, cluster: str, namespace: str, kind: str, name: str
2117
- ) -> None:
2086
+ def root_owner(ctx, cluster, namespace, kind, name):
2118
2087
  settings = queries.get_app_interface_settings()
2119
2088
  clusters = [c for c in queries.get_clusters(minimal=True) if c["name"] == cluster]
2120
2089
  oc_map = OC_Map(
@@ -2144,9 +2113,7 @@ def root_owner(
2144
2113
  @click.argument("aws_account")
2145
2114
  @click.argument("identifier")
2146
2115
  @click.pass_context
2147
- def service_owners_for_rds_instance(
2148
- ctx: click.Context, aws_account: str, identifier: str
2149
- ) -> None:
2116
+ def service_owners_for_rds_instance(ctx, aws_account, identifier):
2150
2117
  namespaces = queries.get_namespaces()
2151
2118
  service_owners = []
2152
2119
  for namespace_info in namespaces:
@@ -2168,7 +2135,7 @@ def service_owners_for_rds_instance(
2168
2135
 
2169
2136
  @get.command()
2170
2137
  @click.pass_context
2171
- def sre_checkpoints(ctx: click.Context) -> None:
2138
+ def sre_checkpoints(ctx):
2172
2139
  apps = queries.get_apps()
2173
2140
 
2174
2141
  parent_apps = {app["parentApp"]["path"] for app in apps if app.get("parentApp")}
@@ -2192,14 +2159,13 @@ def sre_checkpoints(ctx: click.Context) -> None:
2192
2159
 
2193
2160
  @get.command()
2194
2161
  @click.pass_context
2195
- def app_interface_merge_queue(ctx: click.Context) -> None:
2162
+ def app_interface_merge_queue(ctx):
2196
2163
  import reconcile.gitlab_housekeeping as glhk
2197
2164
 
2198
2165
  settings = queries.get_app_interface_settings()
2199
2166
  instance = queries.get_gitlab_instance()
2200
2167
  gl = GitLabApi(instance, project_url=settings["repoUrl"], settings=settings)
2201
- state = init_state(integration=glhk.QONTRACT_INTEGRATION)
2202
- merge_requests = glhk.get_merge_requests(True, gl, state=state)
2168
+ merge_requests = glhk.get_merge_requests(True, gl, state=None)
2203
2169
 
2204
2170
  columns = [
2205
2171
  "id",
@@ -2234,7 +2200,7 @@ def app_interface_merge_queue(ctx: click.Context) -> None:
2234
2200
 
2235
2201
  @get.command()
2236
2202
  @click.pass_context
2237
- def app_interface_review_queue(ctx: click.Context) -> None:
2203
+ def app_interface_review_queue(ctx) -> None:
2238
2204
  import reconcile.gitlab_housekeeping as glhk
2239
2205
 
2240
2206
  settings = queries.get_app_interface_settings()
@@ -2251,7 +2217,7 @@ def app_interface_review_queue(ctx: click.Context) -> None:
2251
2217
  "labels",
2252
2218
  ]
2253
2219
 
2254
- def get_mrs(repo: str, url: str) -> list[dict[str, str]]:
2220
+ def get_mrs(repo, url) -> list[dict[str, str]]:
2255
2221
  gl = GitLabApi(instance, project_url=url, settings=settings)
2256
2222
  merge_requests = gl.get_merge_requests(state=MRState.OPENED)
2257
2223
  try:
@@ -2346,7 +2312,7 @@ def app_interface_review_queue(ctx: click.Context) -> None:
2346
2312
 
2347
2313
  @get.command()
2348
2314
  @click.pass_context
2349
- def app_interface_open_selfserviceable_mr_queue(ctx: click.Context) -> None:
2315
+ def app_interface_open_selfserviceable_mr_queue(ctx):
2350
2316
  settings = queries.get_app_interface_settings()
2351
2317
  instance = queries.get_gitlab_instance()
2352
2318
  gl = GitLabApi(instance, project_url=settings["repoUrl"], settings=settings)
@@ -2409,7 +2375,7 @@ def app_interface_open_selfserviceable_mr_queue(ctx: click.Context) -> None:
2409
2375
 
2410
2376
  @get.command()
2411
2377
  @click.pass_context
2412
- def change_types(ctx: click.Context) -> None:
2378
+ def change_types(ctx) -> None:
2413
2379
  """List all change types."""
2414
2380
  change_types = fetch_change_type_processors(gql.get_api(), NoOpFileDiffResolver())
2415
2381
 
@@ -2434,7 +2400,7 @@ def change_types(ctx: click.Context) -> None:
2434
2400
 
2435
2401
  @get.command()
2436
2402
  @click.pass_context
2437
- def app_interface_merge_history(ctx: click.Context) -> None:
2403
+ def app_interface_merge_history(ctx):
2438
2404
  settings = queries.get_app_interface_settings()
2439
2405
  instance = queries.get_gitlab_instance()
2440
2406
  gl = GitLabApi(instance, project_url=settings["repoUrl"], settings=settings)
@@ -2471,7 +2437,7 @@ def app_interface_merge_history(ctx: click.Context) -> None:
2471
2437
  )
2472
2438
  @use_jump_host()
2473
2439
  @click.pass_context
2474
- def selectorsyncset_managed_resources(ctx: click.Context, use_jump_host: bool) -> None:
2440
+ def selectorsyncset_managed_resources(ctx, use_jump_host):
2475
2441
  vault_settings = get_app_interface_vault_settings()
2476
2442
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
2477
2443
  clusters = get_clusters()
@@ -2529,9 +2495,7 @@ def selectorsyncset_managed_resources(ctx: click.Context, use_jump_host: bool) -
2529
2495
  )
2530
2496
  @use_jump_host()
2531
2497
  @click.pass_context
2532
- def selectorsyncset_managed_hypershift_resources(
2533
- ctx: click.Context, use_jump_host: bool
2534
- ) -> None:
2498
+ def selectorsyncset_managed_hypershift_resources(ctx, use_jump_host):
2535
2499
  vault_settings = get_app_interface_vault_settings()
2536
2500
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
2537
2501
  clusters = get_clusters()
@@ -2609,12 +2573,7 @@ def selectorsyncset_managed_hypershift_resources(
2609
2573
  default=os.environ.get("QONTRACT_CLI_EC2_JENKINS_WORKER_AWS_REGION", "us-east-1"),
2610
2574
  )
2611
2575
  @click.pass_context
2612
- def ec2_jenkins_workers(
2613
- ctx: click.Context,
2614
- aws_access_key_id: str,
2615
- aws_secret_access_key: str,
2616
- aws_region: str,
2617
- ) -> None:
2576
+ def ec2_jenkins_workers(ctx, aws_access_key_id, aws_secret_access_key, aws_region):
2618
2577
  """Prints a list of jenkins workers and their status."""
2619
2578
  if not aws_access_key_id or not aws_secret_access_key:
2620
2579
  raise click.ClickException(
@@ -2661,9 +2620,9 @@ def ec2_jenkins_workers(
2661
2620
  url = ""
2662
2621
  for t in instance.tags:
2663
2622
  if t.get("Key") == "os":
2664
- os = t["Value"]
2623
+ os = t.get("Value")
2665
2624
  if t.get("Key") == "jenkins_controller":
2666
- url = f"https://{t['Value'].replace('-', '.')}.devshift.net/computer/{instance.id}"
2625
+ url = f"https://{t.get('Value').replace('-', '.')}.devshift.net/computer/{instance.id}"
2667
2626
  image = ec2.Image(instance.image_id)
2668
2627
  commit_url = ""
2669
2628
  for t in image.tags:
@@ -2690,7 +2649,7 @@ def ec2_jenkins_workers(
2690
2649
  @get.command()
2691
2650
  @click.argument("status-board-instance")
2692
2651
  @click.pass_context
2693
- def slo_document_services(ctx: click.Context, status_board_instance: str) -> None:
2652
+ def slo_document_services(ctx, status_board_instance):
2694
2653
  """Print SLO Documents Services"""
2695
2654
  columns = [
2696
2655
  "slo_doc_name",
@@ -2719,7 +2678,7 @@ def slo_document_services(ctx: click.Context, status_board_instance: str) -> Non
2719
2678
  slodocs = []
2720
2679
  for slodoc in get_slo_documents():
2721
2680
  products = [ns.namespace.environment.product.name for ns in slodoc.namespaces]
2722
- for slo in slodoc.slos or []:
2681
+ for slo in slodoc.slos:
2723
2682
  for product in products:
2724
2683
  if slodoc.app.parent_app:
2725
2684
  app = f"{slodoc.app.parent_app.name}-{slodoc.app.name}"
@@ -2745,7 +2704,7 @@ def slo_document_services(ctx: click.Context, status_board_instance: str) -> Non
2745
2704
  "target_unit": slo.slo_target_unit,
2746
2705
  "window": slo.slo_parameters.window,
2747
2706
  "statusBoardService": f"{product}/{slodoc.app.name}/{slo.name}",
2748
- "statusBoardEnabled": "statusBoard" in (slodoc.labels or {}),
2707
+ "statusBoardEnabled": "statusBoard" in slodoc.labels,
2749
2708
  }
2750
2709
  slodocs.append(item)
2751
2710
 
@@ -2755,7 +2714,7 @@ def slo_document_services(ctx: click.Context, status_board_instance: str) -> Non
2755
2714
  @get.command()
2756
2715
  @click.argument("file_path")
2757
2716
  @click.pass_context
2758
- def alerts(ctx: click.Context, file_path: str) -> None:
2717
+ def alerts(ctx, file_path):
2759
2718
  BIG_NUMBER = 10
2760
2719
 
2761
2720
  def sort_by_threshold(item: dict[str, str]) -> int:
@@ -2829,7 +2788,7 @@ def alerts(ctx: click.Context, file_path: str) -> None:
2829
2788
  @get.command()
2830
2789
  @click.pass_context
2831
2790
  @thread_pool_size(default=5)
2832
- def aws_cost_report(ctx: click.Context, thread_pool_size: int) -> None:
2791
+ def aws_cost_report(ctx, thread_pool_size):
2833
2792
  command = AwsCostReportCommand.create(thread_pool_size=thread_pool_size)
2834
2793
  print(command.execute())
2835
2794
 
@@ -2837,7 +2796,7 @@ def aws_cost_report(ctx: click.Context, thread_pool_size: int) -> None:
2837
2796
  @get.command()
2838
2797
  @click.pass_context
2839
2798
  @thread_pool_size(default=5)
2840
- def openshift_cost_report(ctx: click.Context, thread_pool_size: int) -> None:
2799
+ def openshift_cost_report(ctx, thread_pool_size):
2841
2800
  command = OpenShiftCostReportCommand.create(thread_pool_size=thread_pool_size)
2842
2801
  print(command.execute())
2843
2802
 
@@ -2845,9 +2804,7 @@ def openshift_cost_report(ctx: click.Context, thread_pool_size: int) -> None:
2845
2804
  @get.command()
2846
2805
  @click.pass_context
2847
2806
  @thread_pool_size(default=5)
2848
- def openshift_cost_optimization_report(
2849
- ctx: click.Context, thread_pool_size: int
2850
- ) -> None:
2807
+ def openshift_cost_optimization_report(ctx, thread_pool_size):
2851
2808
  command = OpenShiftCostOptimizationReportCommand.create(
2852
2809
  thread_pool_size=thread_pool_size
2853
2810
  )
@@ -2856,7 +2813,7 @@ def openshift_cost_optimization_report(
2856
2813
 
2857
2814
  @get.command()
2858
2815
  @click.pass_context
2859
- def osd_component_versions(ctx: click.Context) -> None:
2816
+ def osd_component_versions(ctx):
2860
2817
  osd_environments = [
2861
2818
  e["name"] for e in queries.get_environments() if e["product"]["name"] == "OSDv4"
2862
2819
  ]
@@ -2892,7 +2849,7 @@ def osd_component_versions(ctx: click.Context) -> None:
2892
2849
 
2893
2850
  @get.command()
2894
2851
  @click.pass_context
2895
- def maintenances(ctx: click.Context) -> None:
2852
+ def maintenances(ctx):
2896
2853
  now = datetime.now(UTC)
2897
2854
  maintenances = maintenances_gql.query(gql.get_api().query).maintenances or []
2898
2855
  data = [
@@ -2955,7 +2912,7 @@ class MigrationStatusCount:
2955
2912
 
2956
2913
  @get.command()
2957
2914
  @click.pass_context
2958
- def hcp_migration_status(ctx: click.Context) -> None:
2915
+ def hcp_migration_status(ctx):
2959
2916
  counts: dict[str, MigrationStatusCount] = {}
2960
2917
  total_count = MigrationStatusCount("total")
2961
2918
  saas_files = get_saas_files()
@@ -2994,7 +2951,7 @@ def hcp_migration_status(ctx: click.Context) -> None:
2994
2951
 
2995
2952
  @get.command()
2996
2953
  @click.pass_context
2997
- def systems_and_tools(ctx: click.Context) -> None:
2954
+ def systems_and_tools(ctx):
2998
2955
  print(
2999
2956
  f"This report is obtained from app-interface Graphql endpoint available at: {config.get_config()['graphql']['server']}"
3000
2957
  )
@@ -3008,7 +2965,7 @@ def systems_and_tools(ctx: click.Context) -> None:
3008
2965
  "--environment_name", default="production", help="environment to get logs from"
3009
2966
  )
3010
2967
  @click.pass_context
3011
- def logs(ctx: click.Context, integration_name: str, environment_name: str) -> None:
2968
+ def logs(ctx, integration_name: str, environment_name: str):
3012
2969
  integrations = [
3013
2970
  i
3014
2971
  for i in integrations_gql.query(query_func=gql.get_api().query).integrations
@@ -3047,7 +3004,7 @@ def logs(ctx: click.Context, integration_name: str, environment_name: str) -> No
3047
3004
 
3048
3005
  @get.command
3049
3006
  @click.pass_context
3050
- def jenkins_jobs(ctx: click.Context) -> None:
3007
+ def jenkins_jobs(ctx):
3051
3008
  jenkins_configs = queries.get_jenkins_configs()
3052
3009
 
3053
3010
  # stats dicts
@@ -3117,9 +3074,9 @@ You can view the source of this Markdown to extract the JSON data.
3117
3074
 
3118
3075
  @get.command
3119
3076
  @click.pass_context
3120
- def container_image_details(ctx: click.Context) -> None:
3077
+ def container_image_details(ctx):
3121
3078
  apps = get_apps_quay_repos_escalation_policies()
3122
- data: list[dict[str, str | list[str]]] = []
3079
+ data: list[dict[str, str]] = []
3123
3080
  for app in apps:
3124
3081
  app_name = f"{app.parent_app.name}/{app.name}" if app.parent_app else app.name
3125
3082
  ep_channels = app.escalation_policy.channels
@@ -3131,7 +3088,7 @@ def container_image_details(ctx: click.Context) -> None:
3131
3088
  if repo.mirror:
3132
3089
  continue
3133
3090
  repository = f"quay.io/{org_name}/{repo.name}"
3134
- item: dict[str, str | list[str]] = {
3091
+ item = {
3135
3092
  "app": app_name,
3136
3093
  "repository": repository,
3137
3094
  "email": email,
@@ -3144,25 +3101,27 @@ def container_image_details(ctx: click.Context) -> None:
3144
3101
 
3145
3102
  @get.command
3146
3103
  @click.pass_context
3147
- def change_log_tracking(ctx: click.Context) -> None:
3104
+ def change_log_tracking(ctx):
3148
3105
  repo_url = get_app_interface_repo_url()
3149
3106
  change_types = fetch_change_type_processors(gql.get_api(), NoOpFileDiffResolver())
3150
3107
  state = init_state(integration=cl.QONTRACT_INTEGRATION)
3151
3108
  change_log = ChangeLog(**state.get(BUNDLE_DIFFS_OBJ))
3152
3109
  data: list[dict[str, str]] = []
3153
- for change_log_item in change_log.items:
3110
+ for item in change_log.items:
3111
+ change_log_item = ChangeLogItem(**item)
3154
3112
  commit = change_log_item.commit
3155
3113
  covered_change_types_descriptions = [
3156
3114
  ct.description
3157
3115
  for ct in change_types
3158
3116
  if ct.name in change_log_item.change_types
3159
3117
  ]
3160
- data.append({
3118
+ item = {
3161
3119
  "commit": f"[{commit[:7]}]({repo_url}/commit/{commit})",
3162
3120
  "merged_at": change_log_item.merged_at,
3163
3121
  "apps": ", ".join(change_log_item.apps),
3164
3122
  "changes": ", ".join(covered_change_types_descriptions),
3165
- })
3123
+ }
3124
+ data.append(item)
3166
3125
 
3167
3126
  # TODO(mafriedm): Fix this
3168
3127
  ctx.obj["options"]["sort"] = False
@@ -3173,7 +3132,7 @@ def change_log_tracking(ctx: click.Context) -> None:
3173
3132
  @root.group(name="set")
3174
3133
  @output
3175
3134
  @click.pass_context
3176
- def set_command(ctx: click.Context, output: str) -> None:
3135
+ def set_command(ctx, output):
3177
3136
  ctx.obj["output"] = output
3178
3137
 
3179
3138
 
@@ -3182,9 +3141,7 @@ def set_command(ctx: click.Context, output: str) -> None:
3182
3141
  @click.argument("usergroup")
3183
3142
  @click.argument("username")
3184
3143
  @click.pass_context
3185
- def slack_usergroup(
3186
- ctx: click.Context, workspace: str, usergroup: str, username: str
3187
- ) -> None:
3144
+ def slack_usergroup(ctx, workspace, usergroup, username):
3188
3145
  """Update users in a slack usergroup.
3189
3146
  Use an org_username as the username.
3190
3147
  To empty a slack usergroup, pass '' (empty string) as the username.
@@ -3192,8 +3149,6 @@ def slack_usergroup(
3192
3149
  settings = queries.get_app_interface_settings()
3193
3150
  slack = slackapi_from_queries("qontract-cli")
3194
3151
  ugid = slack.get_usergroup_id(usergroup)
3195
- if not ugid:
3196
- raise click.ClickException(f"Usergroup {usergroup} not found.")
3197
3152
  if username:
3198
3153
  mail_address = settings["smtp"]["mailAddress"]
3199
3154
  users = [slack.get_user_id_by_name(username, mail_address)]
@@ -3202,17 +3157,33 @@ def slack_usergroup(
3202
3157
  slack.update_usergroup_users(ugid, users)
3203
3158
 
3204
3159
 
3160
+ @set_command.command()
3161
+ @click.argument("org_name")
3162
+ @click.argument("cluster_name")
3163
+ @click.pass_context
3164
+ def cluster_admin(ctx, org_name, cluster_name):
3165
+ settings = queries.get_app_interface_settings()
3166
+ ocms = [
3167
+ o for o in queries.get_openshift_cluster_managers() if o["name"] == org_name
3168
+ ]
3169
+ ocm_map = OCMMap(ocms=ocms, settings=settings)
3170
+ ocm = ocm_map[org_name]
3171
+ enabled = ocm.is_cluster_admin_enabled(cluster_name)
3172
+ if not enabled:
3173
+ ocm.enable_cluster_admin(cluster_name)
3174
+
3175
+
3205
3176
  @root.group()
3206
3177
  @environ(["APP_INTERFACE_STATE_BUCKET"])
3207
3178
  @click.pass_context
3208
- def state(ctx: click.Context) -> None:
3179
+ def state(ctx):
3209
3180
  pass
3210
3181
 
3211
3182
 
3212
3183
  @state.command()
3213
3184
  @click.argument("integration", default="")
3214
3185
  @click.pass_context
3215
- def ls(ctx: click.Context, integration: str) -> None:
3186
+ def ls(ctx, integration):
3216
3187
  state = init_state(integration=integration)
3217
3188
  keys = state.ls()
3218
3189
  # if integration in not defined the 2th token will be the integration name
@@ -3233,7 +3204,7 @@ def ls(ctx: click.Context, integration: str) -> None:
3233
3204
  @click.argument("integration")
3234
3205
  @click.argument("key")
3235
3206
  @click.pass_context
3236
- def state_get(ctx: click.Context, integration: str, key: str) -> None:
3207
+ def state_get(ctx, integration, key):
3237
3208
  state = init_state(integration=integration)
3238
3209
  value = state.get(key)
3239
3210
  print(value)
@@ -3243,7 +3214,7 @@ def state_get(ctx: click.Context, integration: str, key: str) -> None:
3243
3214
  @click.argument("integration")
3244
3215
  @click.argument("key")
3245
3216
  @click.pass_context
3246
- def add(ctx: click.Context, integration: str, key: str) -> None:
3217
+ def add(ctx, integration, key):
3247
3218
  state = init_state(integration=integration)
3248
3219
  state.add(key)
3249
3220
 
@@ -3253,7 +3224,7 @@ def add(ctx: click.Context, integration: str, key: str) -> None:
3253
3224
  @click.argument("key")
3254
3225
  @click.argument("value")
3255
3226
  @click.pass_context
3256
- def state_set(ctx: click.Context, integration: str, key: str, value: str) -> None:
3227
+ def state_set(ctx, integration, key, value):
3257
3228
  state = init_state(integration=integration)
3258
3229
  state.add(key, value=value, force=True)
3259
3230
 
@@ -3262,7 +3233,7 @@ def state_set(ctx: click.Context, integration: str, key: str, value: str) -> Non
3262
3233
  @click.argument("integration")
3263
3234
  @click.argument("key")
3264
3235
  @click.pass_context
3265
- def rm(ctx: click.Context, integration: str, key: str) -> None:
3236
+ def rm(ctx, integration, key):
3266
3237
  state = init_state(integration=integration)
3267
3238
  state.rm(key)
3268
3239
 
@@ -3270,7 +3241,7 @@ def rm(ctx: click.Context, integration: str, key: str) -> None:
3270
3241
  @root.group()
3271
3242
  @environ(["APP_INTERFACE_STATE_BUCKET"])
3272
3243
  @click.pass_context
3273
- def early_exit_cache(ctx: click.Context) -> None:
3244
+ def early_exit_cache(ctx):
3274
3245
  pass
3275
3246
 
3276
3247
 
@@ -3306,13 +3277,13 @@ def early_exit_cache(ctx: click.Context) -> None:
3306
3277
  )
3307
3278
  @click.pass_context
3308
3279
  def early_exit_cache_head(
3309
- ctx: click.Context,
3310
- integration: str,
3311
- integration_version: str,
3312
- dry_run: bool,
3313
- cache_source: str,
3314
- shard: str,
3315
- ) -> None:
3280
+ ctx,
3281
+ integration,
3282
+ integration_version,
3283
+ dry_run,
3284
+ cache_source,
3285
+ shard,
3286
+ ):
3316
3287
  with EarlyExitCache.build() as cache:
3317
3288
  cache_key = CacheKey(
3318
3289
  integration=integration,
@@ -3358,13 +3329,13 @@ def early_exit_cache_head(
3358
3329
  )
3359
3330
  @click.pass_context
3360
3331
  def early_exit_cache_get(
3361
- ctx: click.Context,
3362
- integration: str,
3363
- integration_version: str,
3364
- dry_run: bool,
3365
- cache_source: str,
3366
- shard: str,
3367
- ) -> None:
3332
+ ctx,
3333
+ integration,
3334
+ integration_version,
3335
+ dry_run,
3336
+ cache_source,
3337
+ shard,
3338
+ ):
3368
3339
  with EarlyExitCache.build() as cache:
3369
3340
  cache_key = CacheKey(
3370
3341
  integration=integration,
@@ -3441,18 +3412,18 @@ def early_exit_cache_get(
3441
3412
  )
3442
3413
  @click.pass_context
3443
3414
  def early_exit_cache_set(
3444
- ctx: click.Context,
3445
- integration: str,
3446
- integration_version: str,
3447
- dry_run: bool,
3448
- cache_source: str,
3449
- shard: str,
3450
- payload: str,
3451
- log_output: str,
3452
- applied_count: int,
3453
- ttl: int,
3454
- latest_cache_source_digest: str,
3455
- ) -> None:
3415
+ ctx,
3416
+ integration,
3417
+ integration_version,
3418
+ dry_run,
3419
+ cache_source,
3420
+ shard,
3421
+ payload,
3422
+ log_output,
3423
+ applied_count,
3424
+ ttl,
3425
+ latest_cache_source_digest,
3426
+ ):
3456
3427
  with EarlyExitCache.build() as cache:
3457
3428
  cache_key = CacheKey(
3458
3429
  integration=integration,
@@ -3501,13 +3472,13 @@ def early_exit_cache_set(
3501
3472
  )
3502
3473
  @click.pass_context
3503
3474
  def early_exit_cache_delete(
3504
- ctx: click.Context,
3505
- integration: str,
3506
- integration_version: str,
3507
- dry_run: bool,
3508
- cache_source_digest: str,
3509
- shard: str,
3510
- ) -> None:
3475
+ ctx,
3476
+ integration,
3477
+ integration_version,
3478
+ dry_run,
3479
+ cache_source_digest,
3480
+ shard,
3481
+ ):
3511
3482
  with EarlyExitCache.build() as cache:
3512
3483
  cache_key_with_digest = CacheKeyWithDigest(
3513
3484
  integration=integration,
@@ -3538,33 +3509,25 @@ def early_exit_cache_delete(
3538
3509
  type=click.Choice(["config", "vault"]),
3539
3510
  )
3540
3511
  @click.pass_context
3541
- def template(
3542
- ctx: click.Context,
3543
- cluster: str,
3544
- namespace: str,
3545
- kind: str,
3546
- name: str,
3547
- path: str,
3548
- secret_reader: str,
3549
- ) -> None:
3512
+ def template(ctx, cluster, namespace, kind, name, path, secret_reader):
3550
3513
  gqlapi = gql.get_api()
3551
3514
  namespaces = gqlapi.query(orb.NAMESPACES_QUERY)["namespaces"]
3552
- namespaces_info = [
3515
+ namespace_info = [
3553
3516
  n
3554
3517
  for n in namespaces
3555
3518
  if n["cluster"]["name"] == cluster and n["name"] == namespace
3556
3519
  ]
3557
- if len(namespaces_info) != 1:
3520
+ if len(namespace_info) != 1:
3558
3521
  print(f"{cluster}/{namespace} error")
3559
3522
  sys.exit(1)
3560
3523
 
3561
- namespace_info = namespaces_info[0]
3562
3524
  settings = queries.get_app_interface_settings()
3563
3525
  settings["vault"] = secret_reader == "vault"
3564
3526
 
3565
3527
  if path and path.startswith("resources"):
3566
3528
  path = path.replace("resources", "", 1)
3567
3529
 
3530
+ [namespace_info] = namespace_info
3568
3531
  ob.aggregate_shared_resources(namespace_info, "openshiftResources")
3569
3532
  openshift_resources = namespace_info.get("openshiftResources")
3570
3533
  for r in openshift_resources:
@@ -3605,9 +3568,7 @@ def template(
3605
3568
  type=click.Choice(["config", "vault"]),
3606
3569
  )
3607
3570
  @click.pass_context
3608
- def run_prometheus_test(
3609
- ctx: click.Context, path: str, cluster: str, namespace: str, secret_reader: str
3610
- ) -> None:
3571
+ def run_prometheus_test(ctx, path, cluster, namespace, secret_reader):
3611
3572
  """Run prometheus tests for the rule associated with the test in the PATH from given
3612
3573
  CLUSTER/NAMESPACE"""
3613
3574
 
@@ -3693,17 +3654,17 @@ def run_prometheus_test(
3693
3654
  )
3694
3655
  @click.pass_context
3695
3656
  def alert_to_receiver(
3696
- ctx: click.Context,
3697
- cluster: str,
3698
- namespace: str,
3699
- rules_path: str,
3700
- alert_name: str,
3701
- alertmanager_secret_path: str,
3702
- alertmanager_namespace: str,
3703
- alertmanager_secret_key: str,
3704
- secret_reader: str,
3705
- additional_label: list[str],
3706
- ) -> None:
3657
+ ctx,
3658
+ cluster,
3659
+ namespace,
3660
+ rules_path,
3661
+ alert_name,
3662
+ alertmanager_secret_path,
3663
+ alertmanager_namespace,
3664
+ alertmanager_secret_key,
3665
+ secret_reader,
3666
+ additional_label,
3667
+ ):
3707
3668
  additional_labels = {}
3708
3669
  for al in additional_label:
3709
3670
  try:
@@ -3795,12 +3756,12 @@ def alert_to_receiver(
3795
3756
  print(f"Cannot find alert {alert_name} in rules {rules_path}")
3796
3757
  sys.exit(1)
3797
3758
 
3798
- for label in alert_labels:
3799
- result = amtool.config_routes_test(am_config, label)
3759
+ for al in alert_labels:
3760
+ result = amtool.config_routes_test(am_config, al)
3800
3761
  if not result:
3801
3762
  print(f"Error running amtool: {result}")
3802
3763
  sys.exit(1)
3803
- print("|".join([label["alertname"], str(result)]))
3764
+ print("|".join([al["alertname"], str(result)]))
3804
3765
 
3805
3766
 
3806
3767
  @root.command()
@@ -3808,12 +3769,7 @@ def alert_to_receiver(
3808
3769
  @click.option("--saas-file-name", default=None, help="saas-file to act on.")
3809
3770
  @click.option("--env-name", default=None, help="environment to use for parameters.")
3810
3771
  @click.pass_context
3811
- def saas_dev(
3812
- ctx: click.Context,
3813
- app_name: str | None = None,
3814
- saas_file_name: str | None = None,
3815
- env_name: str | None = None,
3816
- ) -> None:
3772
+ def saas_dev(ctx, app_name=None, saas_file_name=None, env_name=None) -> None:
3817
3773
  if not env_name:
3818
3774
  print("env-name must be defined")
3819
3775
  return
@@ -3861,7 +3817,7 @@ def saas_dev(
3861
3817
  @click.option("--app-name", default=None, help="app to act on.")
3862
3818
  @click.pass_context
3863
3819
  def saas_targets(
3864
- ctx: click.Context, saas_file_name: str | None = None, app_name: str | None = None
3820
+ ctx, saas_file_name: str | None = None, app_name: str | None = None
3865
3821
  ) -> None:
3866
3822
  """Resolve namespaceSelectors and print all resulting targets of a saas file."""
3867
3823
  console = Console()
@@ -3925,7 +3881,7 @@ def saas_targets(
3925
3881
  default="json",
3926
3882
  type=click.Choice(["json", "yaml"]),
3927
3883
  )
3928
- def query(output: str, query: str) -> None:
3884
+ def query(output, query):
3929
3885
  """Run a raw GraphQL query"""
3930
3886
  gqlapi = gql.get_api()
3931
3887
  result = gqlapi.query(query)
@@ -3939,7 +3895,7 @@ def query(output: str, query: str) -> None:
3939
3895
  @root.command()
3940
3896
  @click.argument("cluster")
3941
3897
  @click.argument("query")
3942
- def promquery(cluster: str, query: str) -> None:
3898
+ def promquery(cluster, query):
3943
3899
  """Run a PromQL query"""
3944
3900
  config_data = config.get_config()
3945
3901
  auth = {"path": config_data["promql-auth"]["secret_path"], "field": "token"}
@@ -3990,13 +3946,8 @@ def promquery(cluster: str, query: str) -> None:
3990
3946
  default=False,
3991
3947
  )
3992
3948
  def sre_checkpoint_metadata(
3993
- app_path: str,
3994
- parent_ticket: str,
3995
- jiraboard: str,
3996
- jiradef: str,
3997
- create_parent_ticket: bool,
3998
- dry_run: bool,
3999
- ) -> None:
3949
+ app_path, parent_ticket, jiraboard, jiradef, create_parent_ticket, dry_run
3950
+ ):
4000
3951
  """Check an app path for checkpoint-related metadata."""
4001
3952
  data = queries.get_app_metadata(app_path)
4002
3953
  settings = queries.get_app_interface_settings()
@@ -4035,13 +3986,8 @@ def sre_checkpoint_metadata(
4035
3986
  required=True,
4036
3987
  )
4037
3988
  def gpg_encrypt(
4038
- vault_path: str,
4039
- vault_secret_version: str,
4040
- file_path: str,
4041
- openshift_path: str,
4042
- output: str,
4043
- for_user: str,
4044
- ) -> None:
3989
+ vault_path, vault_secret_version, file_path, openshift_path, output, for_user
3990
+ ):
4045
3991
  """
4046
3992
  Encrypt the specified secret (local file, vault or openshift) with a
4047
3993
  given users gpg key. This is intended for easily sharing secrets with
@@ -4064,7 +4010,7 @@ def gpg_encrypt(
4064
4010
  @click.option("--channel", help="the channel that state is part of")
4065
4011
  @click.option("--sha", help="the commit sha we want state for")
4066
4012
  @environ(["APP_INTERFACE_STATE_BUCKET"])
4067
- def get_promotion_state(channel: str, sha: str) -> None:
4013
+ def get_promotion_state(channel: str, sha: str):
4068
4014
  from tools.saas_promotion_state.saas_promotion_state import (
4069
4015
  SaasPromotionState,
4070
4016
  )
@@ -4089,7 +4035,7 @@ def get_promotion_state(channel: str, sha: str) -> None:
4089
4035
  @click.option("--sha", help="the commit sha we want state for")
4090
4036
  @click.option("--publisher-id", help="the publisher id we want state for")
4091
4037
  @environ(["APP_INTERFACE_STATE_BUCKET"])
4092
- def mark_promotion_state_successful(channel: str, sha: str, publisher_id: str) -> None:
4038
+ def mark_promotion_state_successful(channel: str, sha: str, publisher_id: str):
4093
4039
  from tools.saas_promotion_state.saas_promotion_state import (
4094
4040
  SaasPromotionState,
4095
4041
  )
@@ -4113,9 +4059,7 @@ def mark_promotion_state_successful(channel: str, sha: str, publisher_id: str) -
4113
4059
  help="filesystem path to a local app-interface repo",
4114
4060
  default=os.environ.get("APP_INTERFACE_PATH", None),
4115
4061
  )
4116
- def test_change_type(
4117
- change_type_name: str, role_name: str, app_interface_path: str
4118
- ) -> None:
4062
+ def test_change_type(change_type_name: str, role_name: str, app_interface_path: str):
4119
4063
  from reconcile.change_owners import tester
4120
4064
 
4121
4065
  # tester.test_change_type(change_type_name, datafile_path)
@@ -4124,7 +4068,7 @@ def test_change_type(
4124
4068
 
4125
4069
  @root.group()
4126
4070
  @click.pass_context
4127
- def sso_client(ctx: click.Context) -> None:
4071
+ def sso_client(ctx):
4128
4072
  """SSO client commands"""
4129
4073
 
4130
4074
 
@@ -4160,7 +4104,7 @@ def sso_client(ctx: click.Context) -> None:
4160
4104
  )
4161
4105
  @click.pass_context
4162
4106
  def create(
4163
- ctx: click.Context,
4107
+ ctx,
4164
4108
  client_name: str,
4165
4109
  contact_email: str,
4166
4110
  keycloak_instance_vault_path: str,
@@ -4194,7 +4138,7 @@ def create(
4194
4138
  @sso_client.command()
4195
4139
  @click.argument("sso-client-vault-secret-path", required=True)
4196
4140
  @click.pass_context
4197
- def remove(ctx: click.Context, sso_client_vault_secret_path: str) -> None:
4141
+ def remove(ctx, sso_client_vault_secret_path: str):
4198
4142
  """Remove an existing SSO client"""
4199
4143
  vault_settings = get_app_interface_vault_settings()
4200
4144
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
@@ -4241,12 +4185,8 @@ def remove(ctx: click.Context, sso_client_vault_secret_path: str) -> None:
4241
4185
  )
4242
4186
  @click.pass_context
4243
4187
  def external_resources(
4244
- ctx: click.Context,
4245
- provision_provider: str,
4246
- provisioner: str,
4247
- provider: str,
4248
- identifier: str,
4249
- ) -> None:
4188
+ ctx, provision_provider: str, provisioner: str, provider: str, identifier: str
4189
+ ):
4250
4190
  """External resources commands"""
4251
4191
  ctx.obj["provision_provider"] = provision_provider
4252
4192
  ctx.obj["provisioner"] = provisioner
@@ -4258,7 +4198,7 @@ def external_resources(
4258
4198
 
4259
4199
  @external_resources.command()
4260
4200
  @click.pass_context
4261
- def get_input(ctx: click.Context) -> None:
4201
+ def get_input(ctx):
4262
4202
  """Gets the input data for an external resource asset. Input data is what is used
4263
4203
  in the Reconciliation Job to manage the resource."""
4264
4204
  erv2cli = Erv2Cli(
@@ -4273,7 +4213,7 @@ def get_input(ctx: click.Context) -> None:
4273
4213
 
4274
4214
  @external_resources.command()
4275
4215
  @click.pass_context
4276
- def request_reconciliation(ctx: click.Context) -> None:
4216
+ def request_reconciliation(ctx):
4277
4217
  """Marks a resource as it needs to get reconciled. The itegration will reconcile the resource at
4278
4218
  its next iteration."""
4279
4219
  erv2cli = Erv2Cli(
@@ -4300,7 +4240,7 @@ def request_reconciliation(ctx: click.Context) -> None:
4300
4240
  default=False,
4301
4241
  )
4302
4242
  @click.pass_context
4303
- def migrate(ctx: click.Context, dry_run: bool, skip_build: bool) -> None:
4243
+ def migrate(ctx, dry_run: bool, skip_build: bool) -> None:
4304
4244
  """Migrate an existing external resource managed by terraform-resources to ERv2.
4305
4245
 
4306
4246
 
@@ -4406,7 +4346,7 @@ def migrate(ctx: click.Context, dry_run: bool, skip_build: bool) -> None:
4406
4346
  @external_resources.command()
4407
4347
  @binary(["docker"])
4408
4348
  @click.pass_context
4409
- def debug_shell(ctx: click.Context) -> None:
4349
+ def debug_shell(ctx) -> None:
4410
4350
  """Enter an ERv2 debug shell to manually migrate resources."""
4411
4351
  # use a temporary directory in $HOME. The MacOS colima default configuration allows docker mounts from $HOME.
4412
4352
  with tempfile.TemporaryDirectory(dir=Path.home(), prefix="erv2-debug.") as _tempdir:
@@ -4445,7 +4385,7 @@ def debug_shell(ctx: click.Context) -> None:
4445
4385
  prompt=True,
4446
4386
  )
4447
4387
  @click.pass_context
4448
- def force_unlock(ctx: click.Context, lock_id: str) -> None:
4388
+ def force_unlock(ctx, lock_id: str) -> None:
4449
4389
  """Manually unlock the ERv2 terraform state."""
4450
4390
  # use a temporary directory in $HOME. The MacOS colima default configuration allows docker mounts from $HOME.
4451
4391
  with tempfile.TemporaryDirectory(
@@ -4486,14 +4426,14 @@ def force_unlock(ctx: click.Context, lock_id: str) -> None:
4486
4426
  @click.option("--include-pattern", help="Only include images that match this pattern")
4487
4427
  @click.pass_context
4488
4428
  def container_images(
4489
- ctx: click.Context,
4490
- cluster_name: str,
4491
- namespace_name: str,
4492
- thread_pool_size: int,
4493
- use_jump_host: bool,
4494
- exclude_pattern: str,
4495
- include_pattern: str,
4496
- ) -> None:
4429
+ ctx,
4430
+ cluster_name,
4431
+ namespace_name,
4432
+ thread_pool_size,
4433
+ use_jump_host,
4434
+ exclude_pattern,
4435
+ include_pattern,
4436
+ ):
4497
4437
  from tools.cli_commands.container_images_report import get_all_pods_images
4498
4438
 
4499
4439
  results = get_all_pods_images(
@@ -4540,7 +4480,7 @@ You can view the source of this Markdown to extract the JSON data.
4540
4480
  @get.command(help="Get all app tekton pipelines providers roles and users")
4541
4481
  @click.argument("app-name")
4542
4482
  @click.pass_context
4543
- def tekton_roles_and_users(ctx: click.Context, app_name: str) -> None:
4483
+ def tekton_roles_and_users(ctx, app_name):
4544
4484
  pp_namespaces = {
4545
4485
  p.namespace.path
4546
4486
  for p in get_tekton_pipeline_providers()
@@ -4567,7 +4507,6 @@ def tekton_roles_and_users(ctx: click.Context, app_name: str) -> None:
4567
4507
  if not seen:
4568
4508
  seen = True
4569
4509
 
4570
- users: str | list[str]
4571
4510
  if ctx.obj["options"]["output"] == "table":
4572
4511
  users = ", ".join([u.org_username for u in r.users])
4573
4512
  else:
@@ -4587,7 +4526,7 @@ def tekton_roles_and_users(ctx: click.Context, app_name: str) -> None:
4587
4526
  )
4588
4527
  @click.argument("aws-account")
4589
4528
  @click.pass_context
4590
- def log_group_usage(ctx: click.Context, aws_account: str) -> None:
4529
+ def log_group_usage(ctx, aws_account):
4591
4530
  accounts = queries.get_aws_accounts(name=aws_account)
4592
4531
  if not accounts:
4593
4532
  print("no aws account found with that name")
@@ -4597,7 +4536,7 @@ def log_group_usage(ctx: click.Context, aws_account: str) -> None:
4597
4536
  settings = queries.get_app_interface_settings()
4598
4537
  secret_reader = SecretReader(settings=settings)
4599
4538
  columns = ["log_group", "stored_bytes", "retention_days"]
4600
- results: list[dict[str, str | int]] = []
4539
+ results = []
4601
4540
 
4602
4541
  with AWSApi(1, [account], settings, secret_reader) as aws:
4603
4542
  session = aws.get_session(account["name"])