nuvolaris/endpoint.py (254 lines of code) (raw):

# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # import kopf, logging, json, time import os import nuvolaris.kube as kube import nuvolaris.kustomize as kus import nuvolaris.config as cfg import nuvolaris.openwhisk as openwhisk import nuvolaris.apihost_util as apihost_util import nuvolaris.util as util import nuvolaris.operator_util as operator_util from nuvolaris.ingress_data import IngressData from nuvolaris.route_data import RouteData from nuvolaris.user_metadata import UserMetadata def ingress_secret_name(namespace, ingress="apihost"): return f"{namespace}-{ingress}-crt" def api_ingress_name(namespace, ingress="apihost"): return namespace == "nuvolaris" and ingress or f"{namespace}-{ingress}-api-ingress" def api_route_name(namespace,route="apihost"): return namespace == "nuvolaris" and route or f"{namespace}-{route}-api-route" def api_secret_name(namespace): return f"{namespace}-crt" def api_middleware_ingress_name(namespace,ingress): return f"{namespace}-{ingress}-api-ingress-add-prefix" def deploy_info_route(apihost,namespace): info = RouteData(apihost) info.with_route_name(api_route_name(namespace,"apihost-info")) info.with_service_name("controller-ip") info.with_service_kind("Service") info.with_service_port("8080") info.with_context_path("/api/info") info.with_rewrite_target("/") logging.info(f"*** configuring route for apihost-info") path_to_template_yaml = info.render_template(namespace) res = kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) return res def deploy_api_routes(apihost,namespace,should_create_www=False): logging.info(f"**** configuring openshift route based endpoint for apihost {apihost}") api = RouteData(apihost) api.with_route_name(api_route_name(namespace,"apihost")) api.with_service_name("controller-ip") api.with_service_kind("Service") api.with_service_port("8080") api.with_context_path("/api/v1") api.with_rewrite_target("/api/v1") my = RouteData(apihost) my.with_route_name(api_route_name(namespace,"apihost-my")) my.with_service_name("controller-ip") my.with_service_kind("Service") my.with_service_port("8080") my.with_context_path("/api/my") my.with_rewrite_target(f"/api/v1/web/namespace/{namespace}") logging.info(f"*** configuring route for apihost") path_to_template_yaml = api.render_template(namespace) res = kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) logging.info(f"*** configuring route for apihost-my") path_to_template_yaml = my.render_template(namespace) res += kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) if should_create_www: www_my = RouteData(apihost_util.append_prefix_to_url(apihost,"www")) www_my.with_route_name(api_route_name(namespace,"apihost-www-my")) www_my.with_service_name("controller-ip") www_my.with_service_kind("Service") www_my.with_service_port("8080") www_my.with_context_path("/api/my") www_my.with_rewrite_target(f"/api/v1/web/namespace/{namespace}") logging.info(f"*** configuring route for apihost-www-my") path_to_template_yaml = www_my.render_template(namespace) res += kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) return res def deploy_info_ingress(apihost, namespace): res = "" info = IngressData(apihost) info.with_ingress_name(api_ingress_name(namespace,"apihost-info")) info.with_secret_name(api_secret_name(namespace)) info.with_context_path("/api/info") info.with_context_regexp("(/|$)(.*)") info.with_rewrite_target("/$2") info.with_service_name("controller") info.with_service_port("3233") info.with_middleware_ingress_name(api_middleware_ingress_name(namespace,"apihost-info")) if info.requires_traefik_middleware(): logging.info("*** configuring traefik middleware for apihost-info ingress") path_to_template_yaml = info.render_traefik_middleware_template(namespace) res += kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) logging.info(f"*** configuring static ingress for apihost-info") path_to_template_yaml = info.render_template(namespace) res += kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) return res def deploy_api_ingresses(apihost, namespace,should_create_www=False): logging.info(f"**** configuring ingresses based endpoint for apihost {apihost}") res = "" api = IngressData(apihost) api.with_ingress_name(api_ingress_name(namespace,"apihost")) api.with_secret_name(api_secret_name(namespace)) api.with_context_path("/api/v1") api.with_context_regexp("(/|$)(.*)") api.with_rewrite_target("/api/v1/$2") api.with_service_name("controller") api.with_service_port("3233") api.with_middleware_ingress_name(api_middleware_ingress_name(namespace,"apihost")) my = IngressData(apihost) my.with_ingress_name(api_ingress_name(namespace,"apihost-my")) my.with_secret_name(api_secret_name(namespace)) my.with_context_path("/api/my") my.with_context_regexp("(/|$)(.*)") my.with_rewrite_target(f"/api/v1/web/{namespace}/$2") my.with_service_name("controller") my.with_service_port("3233") my.with_middleware_ingress_name(api_middleware_ingress_name(namespace,"apihost-my")) if api.requires_traefik_middleware(): logging.info("*** configuring traefik middleware for apihost ingress") path_to_template_yaml = api.render_traefik_middleware_template(namespace) res += kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) if my.requires_traefik_middleware(): logging.info("*** configuring traefik middleware for apihost-my ingress") path_to_template_yaml = my.render_traefik_middleware_template(namespace) res += kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) logging.info(f"*** configuring static ingress for apihost") path_to_template_yaml = api.render_template(namespace) res += kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) logging.info(f"*** configuring static ingress for apihost-my") path_to_template_yaml = my.render_template(namespace) res += kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) if should_create_www: www_my = IngressData(apihost_util.append_prefix_to_url(apihost,"www")) www_my.with_ingress_name(api_ingress_name(namespace,"apihost-www-my")) www_my.with_secret_name(api_secret_name(namespace)+"-www") www_my.with_context_path("/api/my") www_my.with_context_regexp("(/|$)(.*)") www_my.with_rewrite_target(f"/api/v1/web/{namespace}/$2") www_my.with_service_name("controller") www_my.with_service_port("3233") www_my.with_middleware_ingress_name(api_middleware_ingress_name(namespace,"apihost-www-my")) if www_my.requires_traefik_middleware(): logging.info("*** configuring traefik middleware for apihost-www-my ingress") path_to_template_yaml = www_my.render_traefik_middleware_template(namespace) res += kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) logging.info(f"*** configuring static ingress for apihost-www-my") path_to_template_yaml = www_my.render_template(namespace) res += kube.kubectl("apply", "-f",path_to_template_yaml) os.remove(path_to_template_yaml) return res def create(owner=None): runtime = cfg.get('nuvolaris.kube') apihost = apihost_util.get_apihost(runtime) hostname = apihost_util.extract_hostname(apihost) should_create_www = "www" not in hostname and runtime not in ["kind"] logging.info(f"*** Saving configuration for OpenWishk apihost={apihost}") openwhisk.annotate(f"apihost={apihost}") cfg.put("config.apihost", apihost) if runtime == 'openshift': res = deploy_info_route(apihost,"nuvolaris") return deploy_api_routes(apihost,"nuvolaris",should_create_www) else: res = deploy_info_ingress(apihost,"nuvolaris") return deploy_api_ingresses(apihost,"nuvolaris",should_create_www) def delete(owner=None): """ undeploys ingresses for nuvolaris apihost """ logging.info(f"*** removing ingresses for nuvolaris apihost") namespace = "nuvolaris" runtime = cfg.get('nuvolaris.kube') ingress_class = util.get_ingress_class(runtime) apihost = apihost_util.get_apihost(runtime) hostname = apihost_util.extract_hostname(apihost) should_delete_www = "www" not in hostname and runtime not in ["kind"] try: res = "" if(runtime=='openshift'): res = kube.kubectl("delete", "route",api_route_name(namespace,"apihost")) res += kube.kubectl("delete", "route",api_route_name(namespace,"apihost-my")) res += kube.kubectl("delete", "route",api_route_name(namespace,"apihost-info")) if should_delete_www: res += kube.kubectl("delete", "route",api_route_name(namespace,"apihost-www-my")) return res if(ingress_class == 'traefik'): res = kube.kubectl("delete", "middleware.traefik.containo.us",api_middleware_ingress_name(namespace,"apihost")) res += kube.kubectl("delete", "middleware.traefik.containo.us",api_middleware_ingress_name(namespace,"apihost-my")) res += kube.kubectl("delete", "middleware.traefik.containo.us",api_middleware_ingress_name(namespace,"apihost-info")) if should_delete_www: res += kube.kubectl("delete", "middleware.traefik.containo.us",api_middleware_ingress_name(namespace,"apihost-www-my")) res += kube.kubectl("delete", "ingress",api_ingress_name(namespace,"apihost")) res += kube.kubectl("delete", "ingress",api_ingress_name(namespace,"apihost-my")) res += kube.kubectl("delete", "ingress",api_ingress_name(namespace,"apihost-info")) if should_delete_www: res += kube.kubectl("delete", "ingress",api_ingress_name(namespace,"apihost-www-my")) return res except Exception as e: logging.warn(e) return False def patch(status, action, owner=None): """ Called by the operator patcher to create/update/delete endpoint for apihost """ try: logging.info(f"*** handling request to {action} endpoint") if action == 'create': msg = create(owner) operator_util.patch_operator_status(status,'endpoint','on') elif action == 'delete': msg = delete(owner) operator_util.patch_operator_status(status,'endpoint','off') else: msg = create(owner) operator_util.patch_operator_status(status,'endpoint','updated') logging.info(msg) logging.info(f"*** handled request to {action} endpoint") except Exception as e: logging.error('*** failed to update endpoint: %s' % e) operator_util.patch_operator_status(status,'endpoint','error') def create_ow_api_endpoint(ucfg, user_metadata: UserMetadata, owner=None): """ deploy ingresses to access a generic user api and ow api in a CORS friendly way currently this is not supported for openshift """ runtime = cfg.get('nuvolaris.kube') namespace = ucfg.get("namespace") apihost = ucfg.get("apihost") or "auto" hostname = apihost_util.get_user_static_hostname(runtime, namespace, apihost) logging.debug(f"using hostname {hostname} to configure access to user openwhisk api") try: apihost_url = apihost_util.get_user_static_url(runtime, hostname) my_url = apihost_util.get_user_api_url(runtime, hostname,"api/my") user_metadata.add_metadata("USER_REST_API_URL",my_url) user_metadata.add_metadata("USER_V1_API_URL",apihost_url) if runtime == 'openshift': return deploy_api_routes(apihost_url, namespace) else: return deploy_api_ingresses(apihost_url, namespace) except Exception as e: logging.warn(e) return False def delete_ow_api_endpoint(ucfg): """ undeploy the ingresses """ namespace = ucfg.get("namespace") runtime = cfg.get('nuvolaris.kube') logging.info(f"*** removing api endpoint for {namespace}") runtime = cfg.get('nuvolaris.kube') ingress_class = util.get_ingress_class(runtime) try: res = "" if(runtime=='openshift'): res = kube.kubectl("delete", "route",api_route_name(namespace,"apihost")) res += kube.kubectl("delete", "route",api_route_name(namespace,"apihost-my")) return res if(ingress_class == 'traefik'): res += kube.kubectl("delete", "middleware.traefik.containo.us",api_middleware_ingress_name(namespace,"apihost")) res += kube.kubectl("delete", "middleware.traefik.containo.us",api_middleware_ingress_name(namespace,"apihost-my")) res += kube.kubectl("delete", "ingress",api_ingress_name(namespace,"apihost")) res += kube.kubectl("delete", "ingress",api_ingress_name(namespace,"apihost-my")) return res except Exception as e: logging.warn(e) return False