def shell(ctx, app, organization=None, project=None, deck=None, container=None, **kwargs): """ Drop into an interactive shell. """ cluster_data, deck = get_deck_from_arguments(ctx, organization, project, deck) # get cluster cluster = get_cluster_or_exit(ctx, cluster_data.id) provider_data = cluster.storage.get() # shell k8s = KubeAPI(provider_data, deck) app = argument_app(k8s, app) # get the data of the selected pod data = k8s.get_pod(app) telepresence = Telepresence(provider_data) # the corresponding deployment by getting rid of the pod name suffix deployment = "-".join(data.metadata.name.split("-")[0:-2]) # 1. check if this pod is of a switched deployment (in case of an active Telepresence) if telepresence.is_swapped(deployment, namespace=data.metadata.namespace): # the container name generated in "app switch" for that pod container_name = settings.TELEPRESENCE_DOCKER_IMAGE_FORMAT.format( project=cluster_data.name.lower(), deck=deck["title"].lower(), name=deployment.lower()).replace(":", "") if Docker().check_running(container_name): # 2. Connect to that container # 2.a connect using Docker Docker().exec(container_name, "/bin/sh", interactive=True) else: console.error( "This is a Telepresence Pod with no corresponding Docker container " "running in order to connect (inconsistent state?)") else: if not container and len(data.spec.containers) > 1: container = console.container_list(data=data) if not container: return None # 2.b connect using kubernetes KubeCtl(provider_data).exec_pod(app, deck["environment"][0]["namespace"], "/bin/sh", interactive=True, container=container)
def list(ctx, organization, project, deck, **kwargs): """List all apps.""" cluster_data, deck = get_deck_from_arguments(ctx, organization, project, deck) # get cluster cluster = get_cluster_or_exit(ctx, cluster_data.id) provider_data = cluster.storage.get() # list k8s = KubeAPI(provider_data, deck) pod_table = [] def _ready_ind(c) -> Tuple[bool, str]: # get container count if c is None: container_count = 0 ready_count = 0 else: container_count = len(c) ready_count = sum([val.ready for val in c]) return container_count == ready_count, f"{ready_count}/{container_count}" for pod in k8s.get_pods().items: if pod.status.phase in ["Terminating", "Evicted", "Pending"]: continue all_ready, count = _ready_ind(pod.status.container_statuses) pod_table.append( OrderedDict({ "name": pod.metadata.name, "ready": count, "state": "Ok" if all_ready else "Not Ok", "age": age_from_timestamp( pod.metadata.creation_timestamp.timestamp()), })) console.table( data=pod_table, headers={ "name": "Name", "ready": "Ready", "state": "State", "age": "Age", }, )
def update(ctx, app, organization, project, deck, **kwargs): """ Trigger a forced update of the given app. This command creates a new app instance. """ cluster_data, deck = get_deck_from_arguments(ctx, organization, project, deck) # get cluster cluster = get_cluster_or_exit(ctx, cluster_data.id) provider_data = cluster.storage.get() # delete pod k8s = KubeAPI(provider_data, deck) apps = argument_apps(k8s, [app] if app else [], multiselect=True) [k8s.delete_pod(app) for app in apps] console.info( f"The app(s) {', '.join(apps)} are currently updating and do not exist anymore." )
def logs(ctx, app, container=None, organization=None, project=None, deck=None, follow=False, **kwargs): """ Display the logs for an app. If this app contains multiple containers, specify the ``container`` argument or choose it from the interactive selector. You can follow the log stream if you specify the ``-f`` flag. """ cluster_data, deck = get_deck_from_arguments(ctx, organization, project, deck) # get cluster cluster = get_cluster_or_exit(ctx, cluster_data.id) provider_data = cluster.storage.get() # log k8s = KubeAPI(provider_data, deck) app = argument_app(k8s, app) # get the data of the selected pod if not container: data = k8s.get_pod(app) if len(data.spec.containers) > 1: container = console.container_list(data=data) if not container: return None logs = k8s.get_logs(app, follow, container=container) # output click.echo(logs)
def env(ctx, app, init, organization, project, deck, **kwargs): """ Display the environment variables for the given app. This prints the environment variables for all containers. You can print the environment variables for all init containers with the ``-i`` flag. """ cluster_data, deck = get_deck_from_arguments(ctx, organization, project, deck) # get cluster cluster = get_cluster_or_exit(ctx, cluster_data.id) provider_data = cluster.storage.get() # env k8s = KubeAPI(provider_data, deck) app = argument_app(k8s, app) # get the data of the selected pod data = k8s.get_pod(app) if init: containers = data.spec.init_containers else: containers = data.spec.containers if containers: console.info(f"This app runs {len(containers)} container(s).") for idx, container in enumerate(containers): console.info(f"Container {idx + 1}: {container.image}") env_vars = [] def _value_from(s) -> Tuple[str, str]: # return an indicator if this values comes from a secret and the name if s.config_map_key_ref: return "ConfigMap", s.config_map_key_ref elif s.field_ref and s.field_ref.field_path: return "Field", s.field_ref.field_path elif s.resource_field_ref: return "ResourceField", s.resource_field_ref elif s.secret_key_ref and s.secret_key_ref.name: return "Secret", f"Secret: {s.secret_key_ref.name} Key: {s.secret_key_ref.key}" if container.env: for env in container.env: if env.value_from: type, source = _value_from(env.value_from) else: type, source = "Definition", "-" env_vars.append( OrderedDict({ "name": env.name, "value": str(env.value[:50]) + "..." if env.value and len(env.value) > 50 else env.value, "source_type": type, "path": source, })) if container.env_from: for env in container.env_from: if env.config_map_ref: _cm = k8s.get_configmap(env.config_map_ref.name) if _cm.data: for k, v in _cm.data.items(): env_vars.append( OrderedDict({ "name": k, "value": str(v[:50]) + "..." if v and len(v) > 50 else v, "source_type": "ConfigMap", "path": _cm.metadata.name, })) console.table( env_vars, headers={ "name": "Name", "value": "Value", "path": "Path", "source_type": "Source Type", }, ) else: console.info("No container running")
def switch(ctx, app, organization, project, deck, deployment, unikubefile: str = None, no_build: bool = False, **kwargs): """ Switch a running deployment with a local Docker container. """ cluster_data, deck = get_deck_from_arguments(ctx, organization, project, deck) # get cluster cluster = get_cluster_or_exit(ctx, cluster_data.id) # unikube file input try: unikube_file = unikube_file_selector.get(path_unikube_file=unikubefile) unikube_file_app = unikube_file.get_app(name=app) except Exception as e: console.debug(e) console.error("Invalid 'app' argument.", _exit=True) # 2: Get a deployment # 2.1.a Check the deployment identifier if not deployment and unikube_file_app: # 1.1.b check the unikubefile deployment = unikube_file_app.get_deployment() if not deployment: console.error( "Please specify the 'deployment' key of your app in your unikube.yaml.", _exit=True) else: console.error( "Please specify the deployment either using the '--deployment' option or in the unikube.yaml. " "Run 'unikube app switch' in a directory containing the unikube.yaml file.", _exit=True, ) # 2.2 Fetch available "deployment:", deployments # GraphQL try: graph_ql = GraphQL(authentication=ctx.auth) data = graph_ql.query( """ query($id: UUID) { deck(id: $id) { deployments(level: "local") { id title description ports isSwitchable } environment { id type valuesPath namespace } } } """, query_variables={ "id": deck["id"], }, ) except Exception as e: console.debug(e) console.exit_generic_error() target_deployment = None for _deployment in data["deck"]["deployments"]: if _deployment["title"] == deployment: target_deployment = _deployment # 2.3 Check and select deployment data if target_deployment is None: console.error( f"The deployment '{deployment}' you specified could not be found.", _exit=True, ) ports = target_deployment["ports"].split(",") deployment = target_deployment["title"] namespace = deck["environment"][0]["namespace"] console.info("Please wait while unikube prepares the switch.") with click_spinner.spinner(beep=False, disable=False, force=False, stream=sys.stdout): # check telepresence provider_data = cluster.storage.get() telepresence = Telepresence(provider_data) available_deployments = telepresence.list(namespace, flat=True) if deployment not in available_deployments: console.error( "The given deployment cannot be switched. " f"You may have to run 'unikube deck install {deck}' first.", _exit=True, ) is_swapped = telepresence.is_swapped(deployment, namespace) k8s = KubeAPI(provider_data, deck) # service account token, service cert service_account_tokens = k8s.get_serviceaccount_tokens(deployment) # 3: Build an new Docker image # 3.1 Grab the docker file context, dockerfile, target = unikube_file_app.get_docker_build() if not target: target = "" console.debug(f"{context}, {dockerfile}, {target}") # 3.2 Set an image name image_name = settings.TELEPRESENCE_DOCKER_IMAGE_FORMAT.format( project=cluster_data.name.replace(" ", "").lower(), deck=deck["title"], name=deployment) docker = Docker() if is_swapped: console.warning( "It seems this app is already switched in another process. ") if click.confirm("Do you want to kill it and switch here?"): telepresence.leave(deployment, namespace, silent=True) if docker.check_running(image_name): docker.kill(name=image_name) else: sys.exit(0) # 3.3 Build image if not docker.image_exists(image_name) or not no_build: if no_build: console.warning( f"Ignoring --no-build since the required image '{image_name}' does not exist" ) console.info( f"Building a Docker image for {dockerfile} with context {context}") with click_spinner.spinner(beep=False, disable=False, force=False, stream=sys.stdout): status, msg = docker.build(image_name, context, dockerfile, target) if not status: console.debug(msg) console.error("Failed to build Docker image.", _exit=True) console.info(f"Docker image successfully built: {image_name}") # 4. Start the Telepresence session # 4.1 Set the right intercept port port = unikube_file_app.get_port() if port is None: port = str(ports[0]) if len(ports) > 1: console.warning( f"No port specified although there are multiple ports available: {ports}. " f"Defaulting to port {port} which might not be correct.") if port not in ports: console.error( f"The specified port {port} is not in the rage of available options: {ports}", _exit=True) if not _is_local_port_free(port): console.error( f"The local port {port} is busy. Please stop the application running on " f"this port and try again.", _exit=True, ) # 4.2 See if there are volume mounts mounts = unikube_file_app.get_mounts() console.debug(f"Volumes requested: {mounts}") # mount service tokens if service_account_tokens: tmp_sa_token = tempfile.NamedTemporaryFile(delete=True) tmp_sa_cert = tempfile.NamedTemporaryFile(delete=True) tmp_sa_token.write(service_account_tokens[0].encode()) tmp_sa_cert.write(service_account_tokens[1].encode()) tmp_sa_token.flush() tmp_sa_cert.flush() mounts.append((tmp_sa_token.name, settings.SERVICE_TOKEN_FILENAME)) mounts.append((tmp_sa_cert.name, settings.SERVICE_CERT_FILENAME)) else: tmp_sa_token = None tmp_sa_cert = None # 4.3 See if there special env variables envs = unikube_file_app.get_environment() console.debug(f"Envs requested: {envs}") # 4.4 See if there is a run command to be executed command = unikube_file_app.get_command(port=port) console.debug(f"Run command: {command}") console.info( "Starting your container, this may take a while to become effective") telepresence.swap(deployment, image_name, command, namespace, envs, mounts, port) if docker.check_running(image_name): docker.kill(name=image_name) if tmp_sa_token: tmp_sa_token.close() tmp_sa_cert.close()
def info(ctx, app, organization, project, deck, **kwargs): """Display the status for the given app name.""" cluster_data, deck = get_deck_from_arguments(ctx, organization, project, deck) # get cluster cluster = get_cluster_or_exit(ctx, cluster_data.id) provider_data = cluster.storage.get() # shell k8s = KubeAPI(provider_data, deck) app = argument_app(k8s, app) # get the data of the selected pod data = k8s.get_pod(app) pod_status = data.status console.info( f"This app runs {len(pod_status.container_statuses)} container(s).") for idx, status in enumerate(pod_status.container_statuses): console.info(f"Container {idx + 1}: {status.image}") print("\nStartup command from workload manifest:") console.table([ ("Command", " ".join(data.spec.containers[idx].command) if data.spec.containers[idx].command else None), ("Args", " ".join(data.spec.containers[idx].args) if data.spec.containers[idx].args else None), ]) print("\nApp status:") console.table([ { "State": "Running", "Value": status.state.running.started_at if status.state.running else None }, { "State": "Terminated", "Value": status.state.terminated.finished_at if status.state.terminated else None, }, { "State": "Waiting", "Value": status.state.waiting.message if status.state.waiting else None }, ]) conditions = [] for condition in pod_status.conditions: conditions.append( OrderedDict({ "type": condition.type, "status": condition.status, "reason": condition.reason, "last_transition_time": condition.last_transition_time, "last_probe_time": condition.last_probe_time, "message": condition.message, })) if conditions: conditions = sorted( conditions, key=lambda x: x.get("last_transition_time").timestamp()) # print a line for padding on the console print() console.info("All conditions for this app:") console.table( conditions, headers={ "type": "Type", "status": "Status", "reason": "Reason", "last_transition_time": "Time", "last_probe_time": "Probe Time", "message": "Message", }, ) else: console.info("No condition to display")
def ps(ctx, **kwargs): """ Displays the current process state. """ # cluster cluster_list = ctx.cluster_manager.get_cluster_list(ready=True) cluster_id_list = [cluster.id for cluster in cluster_list] # GraphQL try: graph_ql = GraphQL(authentication=ctx.auth) data = graph_ql.query( """ query { allProjects { results { title id description } } } """, ) project_list = data["allProjects"]["results"] except Exception as e: console.debug(e) console.exit_generic_error() cluster_data = [] for project in project_list: if project["id"] in cluster_id_list: cluster_data.append(project) console.info("Project:") console.table( data={ "id": [cluster["id"] for cluster in cluster_data], "title": [cluster["title"] for cluster in cluster_data], "description": [cluster["description"] for cluster in cluster_data], }, headers=["cluster: id", "name", "description"], ) console.echo("") # switch intercept_count = 0 if cluster_data: cluster = get_cluster_or_exit(ctx, cluster_data[0]["id"]) provider_data = cluster.storage.get() telepresence = Telepresence(provider_data) intercept_count = telepresence.intercept_count() if intercept_count == 0 or not intercept_count: console.info("No app switched!") else: console.info(f"Apps switched: #{intercept_count}") console.echo("") # context local_storage_user = get_local_storage_user() user_data = local_storage_user.get() show_context(ctx=ctx, context=user_data.context)