qontract-reconcile 0.10.2.dev296__py3-none-any.whl → 0.10.2.dev298__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.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev296
3
+ Version: 0.10.2.dev298
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Project-URL: homepage, https://github.com/app-sre/qontract-reconcile
6
6
  Project-URL: repository, https://github.com/app-sre/qontract-reconcile
@@ -66,7 +66,7 @@ reconcile/openshift_prometheus_rules.py,sha256=FVVx1D7KCUnNZh7NwVNbD6t4lXKRSO7ph
66
66
  reconcile/openshift_resourcequotas.py,sha256=0CSuCre3T2ON42Ku1UDhTRugfmUNBx8PILpxIQaAzJU,2882
67
67
  reconcile/openshift_resources.py,sha256=YnhDxCvsp0muxEmULiqWhoar9EzxohTrnbY-U7oS5Hc,1603
68
68
  reconcile/openshift_resources_base.py,sha256=2oOURMtVDsPDG--lPN7c8ar0FPziCm695J2lV3VnVjk,43036
69
- reconcile/openshift_rhcs_certs.py,sha256=RUIEetvirJ-38VV3_Zen6Chi2vPgPaEdRMNXGR1JhSM,10325
69
+ reconcile/openshift_rhcs_certs.py,sha256=tuEz6Wzw5jrHM7fAOSS5d5pDV5SDY0uhjWrlYtCCSYk,10547
70
70
  reconcile/openshift_rolebindings.py,sha256=Mani4fSG6v55cPlAaQ1bmSBza_mFkNtMhdJFjTMGX0o,7250
71
71
  reconcile/openshift_routes.py,sha256=xnA34f32xDdkfV2MXIC1QURFJioQUsXT8AZBiY7iSP0,1298
72
72
  reconcile/openshift_saas_deploy.py,sha256=0_C9OoLGfzoAJ4M2UyCVC9HeHa5w-jP7l0_RxJMRO4k,13131
@@ -82,12 +82,12 @@ reconcile/openshift_tekton_resources.py,sha256=z9OidaI7Ju2O0O0PfIcdoyH62VA4TxLTD
82
82
  reconcile/openshift_upgrade_watcher.py,sha256=dGTQQdCOl95Bz0wOqg6JaMdBSSDgnAveH_hprUafhW0,6624
83
83
  reconcile/openshift_users.py,sha256=h4dH3gTCFFQID76PFuYeMWNzFQ9DgTUtsOcvxfj-3cs,5385
84
84
  reconcile/openshift_vault_secrets.py,sha256=Ax-_EBWWU1VRHYyKaUkGJkIjHGwWM3bZgjXL5CkPW8k,1883
85
- reconcile/quay_base.py,sha256=GQkUpuEzC1V_QrHEu_PoELVGnlNRvgFnTqVKp-xkgC4,2070
85
+ reconcile/quay_base.py,sha256=hfHv8ET6iw0GqPyncYJMRH7YFwJc5E1C9z7zET5MCjo,2327
86
86
  reconcile/quay_membership.py,sha256=No2sgEyTVj-hr5VPLy_xdrYAPvt-xo-CPpOt0X3x_6o,6623
87
87
  reconcile/quay_mirror.py,sha256=pA1_OujRduwQ6dYljoWXU_VJgAwlv7DzThk26ymKmGs,14327
88
- reconcile/quay_mirror_org.py,sha256=2xLD-PZggP33LhZYxun5I3deF8hwGH9zueMtAByphzE,10842
88
+ reconcile/quay_mirror_org.py,sha256=ltPbHuWUI8Wnl8gV4aeYmvoYFA1uXLWqlXqEPpw7Hi0,11065
89
89
  reconcile/quay_permissions.py,sha256=BF539lRxjpgwm88WzazklzgaCF_ipRALwbO2AdpqUqE,4388
90
- reconcile/quay_repos.py,sha256=woB2afCBgz0UPekHcYtV8zwQCZHZZBL8VDf82ATWZxE,7524
90
+ reconcile/quay_repos.py,sha256=fBleLzMtfDmTidpzbrTt8kGCy-Bk3J06EO4hhyghGnQ,7570
91
91
  reconcile/queries.py,sha256=FLUZBtFC2S-e6yjtC1Oq968CJP6t3nc36NPj0wYa0bE,53472
92
92
  reconcile/query_validator.py,sha256=csOSkKxcf6ZlpchJu4ck2jLYKUN6y1l-UmSQUFHgssY,1618
93
93
  reconcile/requests_sender.py,sha256=914iluuF4UVgG3VyxxtnHOu4yf6YKS2fIy6PViSsFTQ,3875
@@ -386,7 +386,7 @@ reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py,sha256=Ferae
386
386
  reconcile/gql_definitions/quay_membership/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
387
387
  reconcile/gql_definitions/quay_membership/quay_membership.py,sha256=MKBkrE-1YYelaAAxOdpqUwCo45kOVC8q29vXArqK_zM,3075
388
388
  reconcile/gql_definitions/rhcs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
389
- reconcile/gql_definitions/rhcs/certs.py,sha256=8ba9GZVY70ppekuxrMjE4wm6WqcMW2IFawjhWvxHrmI,4677
389
+ reconcile/gql_definitions/rhcs/certs.py,sha256=UXTPcX6A7wJzGOgNMymlJi1KTaBDkelwexCTc0KpJU8,6792
390
390
  reconcile/gql_definitions/rhidp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
391
391
  reconcile/gql_definitions/rhidp/organizations.py,sha256=dW9y3ewFu3E-DFrZAi_SEewHYR0MWYeOB52vwnVcq5E,2580
392
392
  reconcile/gql_definitions/service_dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -637,11 +637,11 @@ reconcile/utils/openssl.py,sha256=QVvhzhpChq_4Daf_5wE1qeZJr4thg3DDjJPn4bOPD4E,36
637
637
  reconcile/utils/output.py,sha256=wvsyf8NsFTaFHNkc8to75ta8f474Y4TMO4cHyAHEePk,2209
638
638
  reconcile/utils/pagerduty_api.py,sha256=6Ae-KjcmA6Bf328UhTdQ2VwYjh4uFIW1NdZW6PUgT-c,7607
639
639
  reconcile/utils/parse_dhms_duration.py,sha256=TONpLnec5gHeF7k815YNJpQyDjXhkxZIcv9s8ffbTSY,1840
640
- reconcile/utils/password_validator.py,sha256=XwuWg-8CPlcuG7dl_oQ1G1h2gSVSnfMym_VkuprpWVg,2183
640
+ reconcile/utils/password_validator.py,sha256=knR6jJGc-v44v-hhQFvpYrEubuFfCCc3Qly6n_GJm5I,2191
641
641
  reconcile/utils/prometheus.py,sha256=Ad0rwLbxRuuYjHwkwJloHEdK0bvy42h-p-HIT1DhDhs,3832
642
642
  reconcile/utils/promotion_state.py,sha256=McSgGj3oog83ThJCrMR2v8q6Xb_Pxij-HEe_RbDu8cg,3946
643
643
  reconcile/utils/promtool.py,sha256=YnqwMAzsQVGuBZ1j9zy3UcVPFQVJgBMLzQkxhK_KFkU,3079
644
- reconcile/utils/quay_api.py,sha256=Z6e3oPSp8sCIYxUSDTc6r_qZmXN8Ci_gK90DSCF7BDw,7814
644
+ reconcile/utils/quay_api.py,sha256=ZWjfjzFnIsbKRDcdAnP9tWQezclf53I7VWZJ0gbF2kE,8260
645
645
  reconcile/utils/quay_mirror.py,sha256=dpWCNv5lITwIk6Q9RkmqaQKHNk_JPy27UQEribJ7E-U,1324
646
646
  reconcile/utils/raw_github_api.py,sha256=2WKtE8ABYYB9UGOAh9N_kLkksBWL3320Z2_scteZddI,2805
647
647
  reconcile/utils/repo_owners.py,sha256=P0QX6F0oB8wYA08yiyzhYUiBtU57iIK_PsxbzKENbKM,6571
@@ -796,7 +796,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
796
796
  tools/saas_promotion_state/saas_promotion_state.py,sha256=uQv2QJAmUXP1g2GPIH30WTlvL9soY6m9lefpZEVDM5w,3965
797
797
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
798
798
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
799
- qontract_reconcile-0.10.2.dev296.dist-info/METADATA,sha256=7dJrZxkVMSto5PnvpCwOcPohimXhWm9frSgZUDnUXdw,24916
800
- qontract_reconcile-0.10.2.dev296.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
801
- qontract_reconcile-0.10.2.dev296.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
802
- qontract_reconcile-0.10.2.dev296.dist-info/RECORD,,
799
+ qontract_reconcile-0.10.2.dev298.dist-info/METADATA,sha256=gruuhkwrqSh_2pDHyY0JBlIRMju96xZrOUm2bP1qoVA,24916
800
+ qontract_reconcile-0.10.2.dev298.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
801
+ qontract_reconcile-0.10.2.dev298.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
802
+ qontract_reconcile-0.10.2.dev298.dist-info/RECORD,,
@@ -61,6 +61,24 @@ query RhcsCerts {
61
61
  annotations
62
62
  }
63
63
  }
64
+ sharedResources {
65
+ openshiftResources {
66
+ provider
67
+ ... on NamespaceOpenshiftResourceRhcsCert_v1 {
68
+ secret_name
69
+ service_account_name
70
+ service_account_password {
71
+ ... on VaultSecret_v1 {
72
+ path
73
+ field
74
+ version
75
+ }
76
+ }
77
+ auto_renew_threshold_days
78
+ annotations
79
+ }
80
+ }
81
+ }
64
82
  cluster {
65
83
  name
66
84
  serverUrl
@@ -112,6 +130,32 @@ class NamespaceOpenshiftResourceRhcsCertV1(NamespaceOpenshiftResourceV1):
112
130
  annotations: Optional[Json] = Field(..., alias="annotations")
113
131
 
114
132
 
133
+ class SharedResourcesV1_NamespaceOpenshiftResourceV1(ConfiguredBaseModel):
134
+ provider: str = Field(..., alias="provider")
135
+
136
+
137
+ class SharedResourcesV1_NamespaceOpenshiftResourceV1_NamespaceOpenshiftResourceRhcsCertV1_VaultSecretV1(ConfiguredBaseModel):
138
+ ...
139
+
140
+
141
+ class SharedResourcesV1_NamespaceOpenshiftResourceV1_NamespaceOpenshiftResourceRhcsCertV1_VaultSecretV1_VaultSecretV1(SharedResourcesV1_NamespaceOpenshiftResourceV1_NamespaceOpenshiftResourceRhcsCertV1_VaultSecretV1):
142
+ path: str = Field(..., alias="path")
143
+ field: str = Field(..., alias="field")
144
+ version: Optional[int] = Field(..., alias="version")
145
+
146
+
147
+ class SharedResourcesV1_NamespaceOpenshiftResourceV1_NamespaceOpenshiftResourceRhcsCertV1(SharedResourcesV1_NamespaceOpenshiftResourceV1):
148
+ secret_name: str = Field(..., alias="secret_name")
149
+ service_account_name: str = Field(..., alias="service_account_name")
150
+ service_account_password: Union[SharedResourcesV1_NamespaceOpenshiftResourceV1_NamespaceOpenshiftResourceRhcsCertV1_VaultSecretV1_VaultSecretV1, SharedResourcesV1_NamespaceOpenshiftResourceV1_NamespaceOpenshiftResourceRhcsCertV1_VaultSecretV1] = Field(..., alias="service_account_password")
151
+ auto_renew_threshold_days: Optional[int] = Field(..., alias="auto_renew_threshold_days")
152
+ annotations: Optional[Json] = Field(..., alias="annotations")
153
+
154
+
155
+ class SharedResourcesV1(ConfiguredBaseModel):
156
+ openshift_resources: list[Union[SharedResourcesV1_NamespaceOpenshiftResourceV1_NamespaceOpenshiftResourceRhcsCertV1, SharedResourcesV1_NamespaceOpenshiftResourceV1]] = Field(..., alias="openshiftResources")
157
+
158
+
115
159
  class DisableClusterAutomationsV1(ConfiguredBaseModel):
116
160
  integrations: Optional[list[str]] = Field(..., alias="integrations")
117
161
 
@@ -132,6 +176,7 @@ class NamespaceV1(ConfiguredBaseModel):
132
176
  delete: Optional[bool] = Field(..., alias="delete")
133
177
  cluster_admin: Optional[bool] = Field(..., alias="clusterAdmin")
134
178
  openshift_resources: Optional[list[Union[NamespaceOpenshiftResourceRhcsCertV1, NamespaceOpenshiftResourceV1]]] = Field(..., alias="openshiftResources")
179
+ shared_resources: Optional[list[SharedResourcesV1]] = Field(..., alias="sharedResources")
135
180
  cluster: ClusterV1 = Field(..., alias="cluster")
136
181
 
137
182
 
@@ -2,7 +2,7 @@ import logging
2
2
  import sys
3
3
  import time
4
4
  from collections.abc import Callable, Iterable, Mapping
5
- from typing import Any
5
+ from typing import Any, cast
6
6
 
7
7
  import reconcile.openshift_base as ob
8
8
  import reconcile.openshift_resources_base as orb
@@ -67,20 +67,25 @@ class OpenshiftRhcsCertExpiration(GaugeMetric):
67
67
  return "qontract_reconcile_rhcs_cert_expiration_timestamp"
68
68
 
69
69
 
70
+ def _is_rhcs_cert(obj: Any) -> bool:
71
+ return getattr(obj, "provider", None) == "rhcs-cert"
72
+
73
+
70
74
  def get_namespaces_with_rhcs_certs(
71
- query_func: Callable, cluster_name: Iterable[str] | None = None
75
+ query_func: Callable,
76
+ cluster_name: Iterable[str] | None = None,
72
77
  ) -> list[NamespaceV1]:
73
- return [
74
- ns
75
- for ns in rhcs_certs_query(query_func=query_func).namespaces or []
76
- if integration_is_enabled(QONTRACT_INTEGRATION, ns.cluster)
77
- and not bool(ns.delete)
78
- and (not cluster_name or ns.cluster.name in cluster_name)
79
- and any(
80
- isinstance(r, NamespaceOpenshiftResourceRhcsCertV1)
81
- for r in ns.openshift_resources or []
82
- )
83
- ]
78
+ result: list[NamespaceV1] = []
79
+ for ns in rhcs_certs_query(query_func=query_func).namespaces or []:
80
+ ob.aggregate_shared_resources_typed(cast("Any", ns)) # mypy: ignore[arg-type]
81
+ if (
82
+ integration_is_enabled(QONTRACT_INTEGRATION, ns.cluster)
83
+ and not bool(ns.delete)
84
+ and (not cluster_name or ns.cluster.name in cluster_name)
85
+ and any(_is_rhcs_cert(r) for r in ns.openshift_resources or [])
86
+ ):
87
+ result.append(ns)
88
+ return result
84
89
 
85
90
 
86
91
  def construct_rhcs_cert_oc_secret(
@@ -224,17 +229,16 @@ def fetch_desired_state(
224
229
  ) -> None:
225
230
  vault = VaultClient()
226
231
  cert_provider = get_rhcs_provider_settings(query_func=query_func)
227
-
228
232
  for ns in namespaces:
229
233
  for cert_resource in ns.openshift_resources or []:
230
- if isinstance(cert_resource, NamespaceOpenshiftResourceRhcsCertV1):
234
+ if _is_rhcs_cert(cert_resource):
231
235
  ri.add_desired_resource(
232
236
  cluster=ns.cluster.name,
233
237
  namespace=ns.name,
234
238
  resource=fetch_openshift_resource_for_cert_resource(
235
239
  dry_run,
236
240
  ns,
237
- cert_resource,
241
+ cast("NamespaceOpenshiftResourceRhcsCertV1", cert_resource),
238
242
  vault,
239
243
  cert_provider,
240
244
  ),
reconcile/quay_base.py CHANGED
@@ -1,12 +1,24 @@
1
1
  from collections import namedtuple
2
- from typing import Any
2
+ from typing import Any, TypedDict
3
3
 
4
4
  from reconcile import queries
5
5
  from reconcile.utils.quay_api import QuayApi
6
6
  from reconcile.utils.secret_reader import SecretReader
7
7
 
8
8
  OrgKey = namedtuple("OrgKey", ["instance", "org_name"])
9
- QuayApiStore = dict[OrgKey, dict[str, Any]]
9
+
10
+
11
+ class OrgInfo(TypedDict):
12
+ url: str
13
+ api: QuayApi
14
+ push_token: dict[str, str] | None
15
+ teams: list[str]
16
+ managedRepos: bool
17
+ mirror: OrgKey | None
18
+ mirror_filters: dict[str, Any]
19
+
20
+
21
+ QuayApiStore = dict[OrgKey, OrgInfo]
10
22
 
11
23
 
12
24
  def get_quay_api_store() -> QuayApiStore:
@@ -49,14 +61,16 @@ def get_quay_api_store() -> QuayApiStore:
49
61
  else:
50
62
  push_token = None
51
63
 
52
- store[org_key] = {
64
+ org_info: OrgInfo = {
53
65
  "url": base_url,
54
66
  "api": QuayApi(token, org_name, base_url=base_url),
55
67
  "push_token": push_token,
56
- "teams": org_data.get("managedTeams"),
57
- "managedRepos": org_data.get("managedRepos"),
68
+ "teams": org_data.get("managedTeams") or [],
69
+ "managedRepos": bool(org_data.get("managedRepos")),
58
70
  "mirror": mirror,
59
71
  "mirror_filters": mirror_filters,
60
72
  }
61
73
 
74
+ store[org_key] = org_info
75
+
62
76
  return store
@@ -103,11 +103,16 @@ class QuayMirrorOrg:
103
103
 
104
104
  quay_api = org_info["api"]
105
105
  upstream_org_key = org_info["mirror"]
106
+ assert upstream_org_key is not None
106
107
  upstream_org = self.quay_api_store[upstream_org_key]
107
108
  upstream_quay_api = upstream_org["api"]
108
109
 
109
- username = upstream_org["push_token"]["user"]
110
- token = upstream_org["push_token"]["token"]
110
+ push_token = upstream_org["push_token"]
111
+
112
+ assert push_token is not None
113
+
114
+ username = push_token["user"]
115
+ token = push_token["token"]
111
116
 
112
117
  org_repos = [item["name"] for item in quay_api.list_images()]
113
118
  for repo in upstream_quay_api.list_images():
@@ -143,8 +148,11 @@ class QuayMirrorOrg:
143
148
  org_name = org_key.org_name
144
149
 
145
150
  server_url = org["url"]
146
- username = org["push_token"]["user"]
147
- password = org["push_token"]["token"]
151
+ push_token = org["push_token"]
152
+ assert push_token is not None
153
+
154
+ username = push_token["user"]
155
+ password = push_token["token"]
148
156
 
149
157
  for item in data:
150
158
  image = Image(
@@ -281,6 +289,8 @@ class QuayMirrorOrg:
281
289
  """
282
290
 
283
291
  push_token = self.quay_api_store[org_key]["push_token"]
292
+ assert push_token is not None
293
+
284
294
  username = push_token["user"]
285
295
  password = push_token["token"]
286
296
  return f"{username}:{password}"
reconcile/quay_repos.py CHANGED
@@ -230,6 +230,7 @@ def run(dry_run: bool) -> None:
230
230
  if org_info.get("mirror"):
231
231
  # ensure there are no circular mirror dependencies
232
232
  mirror_org_key = org_info["mirror"]
233
+ assert mirror_org_key is not None
233
234
  mirror_org = quay_api_store[mirror_org_key]
234
235
  if mirror_org.get("mirror"):
235
236
  logging.error(
@@ -29,7 +29,7 @@ class PasswordValidator:
29
29
  self._policy_flags = policy_flags
30
30
  self._mininum_length = minimum_length
31
31
 
32
- def validate(self, password: str):
32
+ def validate(self, password: str) -> None:
33
33
  errors: list[str] = []
34
34
 
35
35
  if len(password) < self._mininum_length:
@@ -1,3 +1,5 @@
1
+ from typing import Any
2
+
1
3
  import requests
2
4
 
3
5
 
@@ -8,17 +10,23 @@ class QuayTeamNotFoundError(Exception):
8
10
  class QuayApi:
9
11
  LIMIT_FOLLOWS = 15
10
12
 
11
- def __init__(self, token, organization, base_url="quay.io", timeout=60):
13
+ def __init__(
14
+ self,
15
+ token: str,
16
+ organization: str,
17
+ base_url: str = "quay.io",
18
+ timeout: int = 60,
19
+ ) -> None:
12
20
  self.token = token
13
21
  self.organization = organization
14
22
  self.auth_header = {"Authorization": "Bearer %s" % (token,)}
15
- self.team_members = {}
23
+ self.team_members: dict[str, Any] = {}
16
24
  self.api_url = f"https://{base_url}/api/v1"
17
25
 
18
26
  self._timeout = timeout
19
27
  """Timeout to use for HTTP calls to Quay (seconds)."""
20
28
 
21
- def list_team_members(self, team, **kwargs):
29
+ def list_team_members(self, team: str, **kwargs: Any) -> list[dict]:
22
30
  """
23
31
  List Quay team members.
24
32
 
@@ -52,12 +60,12 @@ class QuayApi:
52
60
 
53
61
  return members_list
54
62
 
55
- def user_exists(self, user):
63
+ def user_exists(self, user: str) -> bool:
56
64
  url = f"{self.api_url}/users/{user}"
57
65
  r = requests.get(url, headers=self.auth_header, timeout=self._timeout)
58
66
  return r.ok
59
67
 
60
- def remove_user_from_team(self, user, team):
68
+ def remove_user_from_team(self, user: str, team: str) -> bool:
61
69
  """Deletes an user from a team.
62
70
 
63
71
  :raises HTTPError if there are any problems with the request
@@ -80,7 +88,7 @@ class QuayApi:
80
88
 
81
89
  return True
82
90
 
83
- def add_user_to_team(self, user, team):
91
+ def add_user_to_team(self, user: str, team: str) -> bool:
84
92
  """Adds an user to a team.
85
93
 
86
94
  :raises HTTPError if there are any errors with the request
@@ -93,7 +101,9 @@ class QuayApi:
93
101
  r.raise_for_status()
94
102
  return True
95
103
 
96
- def create_or_update_team(self, team: str, role="member", description=None) -> None:
104
+ def create_or_update_team(
105
+ self, team: str, role: str = "member", description: str | None = None
106
+ ) -> None:
97
107
  """
98
108
  Create or update an Organization team.
99
109
 
@@ -117,7 +127,9 @@ class QuayApi:
117
127
  )
118
128
  r.raise_for_status()
119
129
 
120
- def list_images(self, images=None, page=None, count=0):
130
+ def list_images(
131
+ self, images: list | None = None, page: str | None = None, count: int = 0
132
+ ) -> list[dict[str, Any]]:
121
133
  """
122
134
  https://docs.quay.io/api/swagger/#!/repository/listRepos
123
135
 
@@ -156,7 +168,7 @@ class QuayApi:
156
168
  return self.list_images(images, next_page, count + 1)
157
169
  return images
158
170
 
159
- def repo_create(self, repo_name, description, public):
171
+ def repo_create(self, repo_name: str, description: str, public: str) -> None:
160
172
  """Creates a repository called repo_name with the given description
161
173
  and public flag.
162
174
 
@@ -180,14 +192,14 @@ class QuayApi:
180
192
  )
181
193
  r.raise_for_status()
182
194
 
183
- def repo_delete(self, repo_name):
195
+ def repo_delete(self, repo_name: str) -> None:
184
196
  url = f"{self.api_url}/repository/{self.organization}/{repo_name}"
185
197
 
186
198
  # perform request
187
199
  r = requests.delete(url, headers=self.auth_header, timeout=self._timeout)
188
200
  r.raise_for_status()
189
201
 
190
- def repo_update_description(self, repo_name, description):
202
+ def repo_update_description(self, repo_name: str, description: str) -> None:
191
203
  url = f"{self.api_url}/repository/{self.organization}/{repo_name}"
192
204
 
193
205
  params = {"description": description}
@@ -198,13 +210,13 @@ class QuayApi:
198
210
  )
199
211
  r.raise_for_status()
200
212
 
201
- def repo_make_public(self, repo_name):
213
+ def repo_make_public(self, repo_name: str) -> None:
202
214
  self._repo_change_visibility(repo_name, "public")
203
215
 
204
- def repo_make_private(self, repo_name):
216
+ def repo_make_private(self, repo_name: str) -> None:
205
217
  self._repo_change_visibility(repo_name, "private")
206
218
 
207
- def _repo_change_visibility(self, repo_name, visibility):
219
+ def _repo_change_visibility(self, repo_name: str, visibility: str) -> None:
208
220
  url = f"{self.api_url}/repository/{self.organization}/{repo_name}/changevisibility"
209
221
 
210
222
  params = {"visibility": visibility}
@@ -215,7 +227,7 @@ class QuayApi:
215
227
  )
216
228
  r.raise_for_status()
217
229
 
218
- def get_repo_team_permissions(self, repo_name, team):
230
+ def get_repo_team_permissions(self, repo_name: str, team: str) -> str | None:
219
231
  url = (
220
232
  f"{self.api_url}/repository/{self.organization}/"
221
233
  + f"{repo_name}/permissions/team/{team}"
@@ -231,7 +243,7 @@ class QuayApi:
231
243
 
232
244
  return r.json().get("role") or None
233
245
 
234
- def set_repo_team_permissions(self, repo_name, team, role):
246
+ def set_repo_team_permissions(self, repo_name: str, team: str, role: str) -> None:
235
247
  url = (
236
248
  f"{self.api_url}/repository/{self.organization}/"
237
249
  + f"{repo_name}/permissions/team/{team}"