Module codeflare_sdk.utils.openshift_oauth

Expand source code
from urllib3.util import parse_url
import yaml

from ..cluster.auth import config_check, api_config_handler

from kubernetes import client
from kubernetes import dynamic


def _route_api_getter():
    return dynamic.DynamicClient(
        api_config_handler() or client.ApiClient()
    ).resources.get(api_version="route.openshift.io/v1", kind="Route")


def create_openshift_oauth_objects(cluster_name, namespace):
    config_check()
    oauth_port = 8443
    oauth_sa_name = f"{cluster_name}-oauth-proxy"
    tls_secret_name = _gen_tls_secret_name(cluster_name)
    service_name = f"{cluster_name}-oauth"
    port_name = "oauth-proxy"

    _create_or_replace_oauth_sa(namespace, oauth_sa_name, cluster_name)
    _create_or_replace_oauth_service_obj(
        cluster_name, namespace, oauth_port, tls_secret_name, service_name, port_name
    )
    _create_or_replace_oauth_route_object(
        cluster_name,
        namespace,
        service_name,
        port_name,
    )
    _create_or_replace_oauth_rb(cluster_name, namespace, oauth_sa_name)


def _create_or_replace_oauth_sa(namespace, oauth_sa_name, cluster_name):
    oauth_sa = client.V1ServiceAccount(
        api_version="v1",
        kind="ServiceAccount",
        metadata=client.V1ObjectMeta(
            name=oauth_sa_name,
            namespace=namespace,
            annotations={
                "serviceaccounts.openshift.io/oauth-redirectreference.first": '{"kind":"OAuthRedirectReference","apiVersion":"v1","reference":{"kind":"Route","name":"'
                + "ray-dashboard-"
                + cluster_name
                + '"}}'
            },
        ),
    )
    try:
        client.CoreV1Api(api_config_handler()).create_namespaced_service_account(
            namespace=namespace, body=oauth_sa
        )
    except client.ApiException as e:
        if e.reason == "Conflict":
            client.CoreV1Api(api_config_handler()).replace_namespaced_service_account(
                namespace=namespace,
                body=oauth_sa,
                name=oauth_sa_name,
            )
        else:
            raise e


def _create_or_replace_oauth_rb(cluster_name, namespace, oauth_sa_name):
    oauth_crb = client.V1ClusterRoleBinding(
        api_version="rbac.authorization.k8s.io/v1",
        kind="ClusterRoleBinding",
        metadata=client.V1ObjectMeta(name=f"{cluster_name}-rb"),
        role_ref=client.V1RoleRef(
            api_group="rbac.authorization.k8s.io",
            kind="ClusterRole",
            name="system:auth-delegator",
        ),
        subjects=[
            client.V1Subject(
                kind="ServiceAccount", name=oauth_sa_name, namespace=namespace
            )
        ],
    )
    try:
        client.RbacAuthorizationV1Api(api_config_handler()).create_cluster_role_binding(
            body=oauth_crb
        )
    except client.ApiException as e:
        if e.reason == "Conflict":
            client.RbacAuthorizationV1Api(
                api_config_handler()
            ).replace_cluster_role_binding(body=oauth_crb, name=f"{cluster_name}-rb")
        else:
            raise e


def _gen_tls_secret_name(cluster_name):
    return f"{cluster_name}-proxy-tls-secret"


def delete_openshift_oauth_objects(cluster_name, namespace):
    # NOTE: it might be worth adding error handling here, but shouldn't be necessary because cluster.down(...) checks
    # for an existing cluster before calling this => the objects should never be deleted twice
    oauth_sa_name = f"{cluster_name}-oauth-proxy"
    service_name = f"{cluster_name}-oauth"
    v1_routes = _route_api_getter()
    client.CoreV1Api(api_config_handler()).delete_namespaced_service_account(
        name=oauth_sa_name, namespace=namespace
    )
    client.CoreV1Api(api_config_handler()).delete_namespaced_service(
        name=service_name, namespace=namespace
    )
    v1_routes.delete(name=f"ray-dashboard-{cluster_name}", namespace=namespace)
    client.RbacAuthorizationV1Api(api_config_handler()).delete_cluster_role_binding(
        name=f"{cluster_name}-rb"
    )


def _create_or_replace_oauth_service_obj(
    cluster_name: str,
    namespace: str,
    oauth_port: int,
    tls_secret_name: str,
    service_name: str,
    port_name: str,
) -> client.V1Service:
    oauth_service = client.V1Service(
        api_version="v1",
        kind="Service",
        metadata=client.V1ObjectMeta(
            annotations={
                "service.beta.openshift.io/serving-cert-secret-name": tls_secret_name
            },
            name=service_name,
            namespace=namespace,
        ),
        spec=client.V1ServiceSpec(
            ports=[
                client.V1ServicePort(
                    name=port_name,
                    protocol="TCP",
                    port=443,
                    target_port=oauth_port,
                )
            ],
            selector={
                "app.kubernetes.io/created-by": "kuberay-operator",
                "app.kubernetes.io/name": "kuberay",
                "ray.io/cluster": cluster_name,
                "ray.io/identifier": f"{cluster_name}-head",
                "ray.io/node-type": "head",
            },
        ),
    )
    try:
        client.CoreV1Api(api_config_handler()).create_namespaced_service(
            namespace=namespace, body=oauth_service
        )
    except client.ApiException as e:
        if e.reason == "Conflict":
            client.CoreV1Api(api_config_handler()).replace_namespaced_service(
                namespace=namespace, body=oauth_service, name=service_name
            )
        else:
            raise e


def _create_or_replace_oauth_route_object(
    cluster_name: str,
    namespace: str,
    service_name: str,
    port_name: str,
):
    route = f"""
        apiVersion: route.openshift.io/v1
        kind: Route
        metadata:
            name: ray-dashboard-{cluster_name}
            namespace: {namespace}
        spec:
            port:
                targetPort: {port_name}
            tls:
                termination: passthrough
            to:
                kind: Service
                name: {service_name}
    """
    route_data = yaml.safe_load(route)
    v1_routes = _route_api_getter()
    try:
        existing_route = v1_routes.get(
            name=f"ray-dashboard-{cluster_name}", namespace=namespace
        )
        route_data["metadata"]["resourceVersion"] = existing_route["metadata"][
            "resourceVersion"
        ]
        v1_routes.replace(body=route_data)
    except dynamic.client.ApiException:
        v1_routes.create(body=route_data)

Functions

def create_openshift_oauth_objects(cluster_name, namespace)
Expand source code
def create_openshift_oauth_objects(cluster_name, namespace):
    config_check()
    oauth_port = 8443
    oauth_sa_name = f"{cluster_name}-oauth-proxy"
    tls_secret_name = _gen_tls_secret_name(cluster_name)
    service_name = f"{cluster_name}-oauth"
    port_name = "oauth-proxy"

    _create_or_replace_oauth_sa(namespace, oauth_sa_name, cluster_name)
    _create_or_replace_oauth_service_obj(
        cluster_name, namespace, oauth_port, tls_secret_name, service_name, port_name
    )
    _create_or_replace_oauth_route_object(
        cluster_name,
        namespace,
        service_name,
        port_name,
    )
    _create_or_replace_oauth_rb(cluster_name, namespace, oauth_sa_name)
def delete_openshift_oauth_objects(cluster_name, namespace)
Expand source code
def delete_openshift_oauth_objects(cluster_name, namespace):
    # NOTE: it might be worth adding error handling here, but shouldn't be necessary because cluster.down(...) checks
    # for an existing cluster before calling this => the objects should never be deleted twice
    oauth_sa_name = f"{cluster_name}-oauth-proxy"
    service_name = f"{cluster_name}-oauth"
    v1_routes = _route_api_getter()
    client.CoreV1Api(api_config_handler()).delete_namespaced_service_account(
        name=oauth_sa_name, namespace=namespace
    )
    client.CoreV1Api(api_config_handler()).delete_namespaced_service(
        name=service_name, namespace=namespace
    )
    v1_routes.delete(name=f"ray-dashboard-{cluster_name}", namespace=namespace)
    client.RbacAuthorizationV1Api(api_config_handler()).delete_cluster_role_binding(
        name=f"{cluster_name}-rb"
    )