blob: 71c322df420c2503394db0551f18b7737b396f20 [file] [log] [blame]
#!/usr/bin/env python3
#
# 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 logging
import os
import pathlib
import shutil
import subprocess
import sys
import click
import yaml
import scripts
from liminal.build import liminal_apps_builder
from liminal.core import environment
from liminal.core.util import files_util
from liminal.kubernetes import volume_util
from liminal.logging.logging_setup import logging_initialization
from liminal.runners.airflow import dag
logging_initialization()
try:
import importlib.resources as pkg_resources
except ImportError:
# Try backported to PY<37 `importlib_resources`.
import importlib_resources as pkg_resources
@click.group()
def cli():
pass
def docker_is_running():
try:
return not subprocess.check_output("docker info >/dev/null 2>&1", shell=True)
except subprocess.CalledProcessError:
msg = "Docker is not running. Please start docker service on your machine\n"
sys.stderr.write(f"ERROR: {msg}")
raise RuntimeError(msg)
@cli.command("build", short_help="builds dockers from your business logic")
@click.option('--path', default=os.getcwd(), help='Build within this path.')
def build(path):
click.echo(f'Building liminal apps in {path}')
if docker_is_running():
liminal_apps_builder.build_liminal_apps(path)
def deploy_liminal_core_internal(clean):
# noinspection PyTypeChecker
with pkg_resources.path(dag, 'liminal_dags.py') as p:
dags_path = p
os.makedirs(environment.get_dags_dir(), exist_ok=True)
dag_target_file = os.path.join(environment.get_liminal_home(), 'liminal_dags.py')
shutil.copyfile(dags_path, dag_target_file)
# initialize the env. variable which indicates to the docke compose which
# liminal to install in airflow docker
liminal_version = environment.get_liminal_version()
logging.info(f'Deploying liminal version: {liminal_version}')
# if liminal is installed from local file - the developer needs to put it in the /scripts folder
# in which case it will end up inside the container during build
if liminal_version.find("file://") > -1:
local_file_name = os.path.basename(liminal_version)
full_path = os.path.join('/opt/airflow/dags', local_file_name)
logging.info(
f'Liminal was installed locally, changing the LIMINAL_VERSION parameter to {full_path}')
os.environ[environment.LIMINAL_VERSION_PARAM_NAME] = full_path
if clean:
docker_compose_command('down', ['--remove-orphans', '--rmi', 'local'])
docker_compose_command('build', ['--no-cache'])
docker_compose_command('run', ['webserver', 'resetdb', '-y'])
docker_compose_command('run', ['webserver', 'initdb'])
docker_compose_command('down', ['--remove-orphans'])
def docker_compose_command(command_name, args):
detached_mode = False
docker_compose_path, project_dir = get_docker_compose_paths()
concated_args = ' '.join(args)
run_command = [
'docker-compose '
f'-f "{docker_compose_path}" '
'-p liminal --project-directory '
f'{project_dir} {command_name} {concated_args}'
]
logging.info(run_command[0])
if ('tail' in str(args) or '-d' in str(args) or command_name in ['ps', 'down']):
detached_mode = True
if detached_mode:
output = subprocess.run(run_command, env=os.environ, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, universal_newlines=True)
return output.stdout, output.stderr
else:
subprocess.call(run_command, env=os.environ, shell=True)
return '', ''
@cli.command("deploy", short_help="deploys your liminal.yaml files to $LIMINAL_HOME folder")
@click.option('--path', default=os.getcwd(), help="folder containing liminal.yaml files")
@click.option('--clean', is_flag=True, default=False,
help="re-deploy liminal airflow components from scratch")
def deploy_liminal_apps(path, clean):
click.echo("deploying liminal yaml files")
liminal_home = environment.get_liminal_home()
os.makedirs(liminal_home, exist_ok=True)
os.makedirs(environment.get_dags_dir(), exist_ok=True)
deploy_liminal_core_internal(clean)
config_files = files_util.find_config_files(path)
for config_file in config_files:
click.echo(f"deploying liminal file: {config_file}")
yml_name = os.path.basename(config_file)
target_yml_name = os.path.join(environment.get_dags_dir(), yml_name)
shutil.copyfile(config_file, target_yml_name)
with open(config_file) as stream:
config = yaml.safe_load(stream)
volume_util.create_local_volumes(config, os.path.dirname(config_file))
def liminal_is_running():
stdout, stderr = docker_compose_command('ps', [])
return "liminal" in stdout
@cli.command("stop", short_help="stops the docker compose")
def stop():
if docker_is_running() and liminal_is_running():
# initialize liminal home by default
environment.get_liminal_home()
docker_compose_command('down', [])
@cli.command("logs", short_help="display the docker compose logs")
@click.option('--follow', '-f', is_flag=True, default=False, help="Follow log output.")
@click.option('--tail', '-t', default=0, type=int,
help="Number of lines to show from the end of the log")
def logs(follow, tail):
if docker_is_running() and liminal_is_running():
# initialize liminal home by default
environment.get_liminal_home()
if follow:
docker_compose_command('logs', ['--follow'])
if tail > 0:
stdout, stderr = docker_compose_command('logs', [f'--tail={tail}'])
logging.info(stdout)
@cli.command("start",
short_help="starts a local airflow in docker compose. should be run after deploy. " +
"Make sure docker is running on your machine")
@click.option('--detached-mode', '-d', is_flag=True, default=False, help="Start liminal in detached mode.")
def start(detached_mode):
liminal_version = environment.get_liminal_version()
logging.info(f'starting liminal version: {liminal_version}')
if docker_is_running():
# initialize liminal home by default
environment.get_liminal_home()
if detached_mode:
docker_compose_command('up', ['-d'])
else:
docker_compose_command('up', [])
def get_docker_compose_paths():
# noinspection PyTypeChecker
with pkg_resources.path(scripts, 'docker-compose.yml') as p:
docker_compose_path = p
project_dir = pathlib.Path(docker_compose_path).parent.parent.absolute()
return docker_compose_path, project_dir
if __name__ == '__main__':
cli()