|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +from typing import Dict, Optional |
| 4 | + |
| 5 | +import base64 |
| 6 | +import requests |
| 7 | +from ocp_resources.gateway_gateway_networking_k8s_io import Gateway |
| 8 | +from ocp_resources.ingress_config_openshift_io import Ingress as IngressConfig |
| 9 | +from pyhelper_utils.shell import run_command |
| 10 | +from requests import Response |
| 11 | + |
| 12 | + |
| 13 | +def host_from_ingress_domain(client) -> str: |
| 14 | + """ |
| 15 | + Return 'maas.<ingress-domain>' using the wrapper (no 'oc get' calls). |
| 16 | + """ |
| 17 | + ing = IngressConfig(name="cluster", client=client) |
| 18 | + assert ing.exists, "ingresses.config.openshift.io/cluster not found" |
| 19 | + |
| 20 | + spec = ing.instance.spec |
| 21 | + domain = spec.get("domain") if isinstance(spec, dict) else getattr(spec, "domain", None) |
| 22 | + assert domain, "spec.domain is missing on Ingress 'cluster'" |
| 23 | + |
| 24 | + return f"maas.{domain}" |
| 25 | + |
| 26 | + |
| 27 | +def scheme_from_gateway(gw: Gateway) -> str: |
| 28 | + """ |
| 29 | + Decide 'http' or 'https' from Gateway listeners. |
| 30 | + Rule: if any listener has protocol HTTPS or tls set, return 'https'; else 'http'. |
| 31 | + listeners is a list[dict] per the Gateway wrapper. |
| 32 | + """ |
| 33 | + listeners = gw.instance.spec.get("listeners", []) |
| 34 | + for listener in listeners: |
| 35 | + protocol = (listener.get("protocol") or "").upper() |
| 36 | + if protocol == "HTTPS" or listener.get("tls"): |
| 37 | + return "https" |
| 38 | + return "http" |
| 39 | + |
| 40 | + |
| 41 | +def choose_scheme_via_gateway(client) -> str: |
| 42 | + """Cluster-wide; prefer maas-default-gateway if present, else first.""" |
| 43 | + gateways = list(Gateway.get(client=client)) |
| 44 | + if not gateways: |
| 45 | + return "http" |
| 46 | + |
| 47 | + gw = next( |
| 48 | + (g for g in gateways if (g.instance.metadata.name or "") == "maas-default-gateway"), |
| 49 | + gateways[0], |
| 50 | + ) |
| 51 | + return scheme_from_gateway(gw=gw) |
| 52 | + |
| 53 | + |
| 54 | +def current_user_bearer_via_oc() -> str: |
| 55 | + """ |
| 56 | + Return the current oc login token from `oc whoami -t`. |
| 57 | + """ |
| 58 | + rc, out, err = run_command(command=["oc", "whoami", "-t"]) |
| 59 | + assert (rc is True) or (rc == 0), f"failed to get token via 'oc whoami -t': rc={rc} err={err}" |
| 60 | + token = (out or "").strip() |
| 61 | + assert token, "empty token from 'oc whoami -t'" |
| 62 | + return token |
| 63 | + |
| 64 | + |
| 65 | +def maas_auth_headers(token: str) -> Dict[str, str]: |
| 66 | + """Build Authorization header for MaaS/Billing calls.""" |
| 67 | + return {"Authorization": f"Bearer {token}"} |
| 68 | + |
| 69 | + |
| 70 | +def mint_token( |
| 71 | + base_url: str, |
| 72 | + oc_user_token: str, |
| 73 | + minutes: int = 10, |
| 74 | + http: Optional[requests.Session] = None, |
| 75 | +) -> tuple[Response, dict]: |
| 76 | + """Mint a MaaS token. |
| 77 | +
|
| 78 | + Args: |
| 79 | + base_url: MaaS API base, e.g. "https://maas.apps.../maas-api". |
| 80 | + oc_user_token: Bearer used to mint the token (usually `oc whoami -t`). |
| 81 | + minutes: Token TTL in minutes. |
| 82 | +
|
| 83 | + Returns: |
| 84 | + (raw requests.Response, parsed_json_or_empty_dict) |
| 85 | + """ |
| 86 | + assert http is not None, "HTTP session is required (pass the fixture)" |
| 87 | + resp = http.post( |
| 88 | + f"{base_url}/v1/tokens", |
| 89 | + headers=maas_auth_headers(token=oc_user_token), |
| 90 | + json={"ttl": f"{minutes}m"}, |
| 91 | + timeout=60, |
| 92 | + ) |
| 93 | + try: |
| 94 | + body = resp.json() |
| 95 | + except Exception: |
| 96 | + body = {} |
| 97 | + return resp, body |
| 98 | + |
| 99 | + |
| 100 | +def b64url_decode(s: str) -> bytes: |
| 101 | + pad = "=" * (-len(s) % 4) |
| 102 | + # keyword-arg to satisfy FCN001 rule: |
| 103 | + return base64.urlsafe_b64decode(s=(s + pad).encode("utf-8")) |
0 commit comments