Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kubetools run #75

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions kubetools/cli/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
get_restart_objects,
log_restart_changes,
)
from kubetools.deploy.commands.run import (
execute_run,
get_run_objects,
log_run_changes,
)
from kubetools.kubernetes.api import get_object_name


Expand Down Expand Up @@ -201,6 +206,123 @@ def deploy(
)


@cli_bootstrap.command(help_priority=0)
@click.option(
'--default-registry',
help='Default registry for apps that do not specify.',
)
@click.option(
'-y', '--yes',
is_flag=True,
default=False,
help='Flag to auto-yes remove confirmation step.',
)
@click.option(
'envvars', '-e', '--envvar',
multiple=True,
callback=_validate_key_value_argument,
help='Extra environment variables to apply to Kubernetes objects, format: key=value.',
)
@click.option(
'-f', '--file',
nargs=1,
help='Specify a non-default Kubetools yml file to deploy from.',
type=click.Path(exists=True),
)
@click.option(
'--ignore-git-changes',
is_flag=True,
default=False,
help='Flag to ignore un-committed changes in git.',
)
@click.option(
'wait_for_job', '--wait',
is_flag=True,
default=False,
help='Whether to wait for the job to complete.',
)
@click.option(
'delete_completed_job', '--delete',
is_flag=True,
default=False,
help='Delete jobs after they complete (requires `--wait`).',
)
@click.argument('namespace')
@click.argument(
'app_dir',
type=click.Path(exists=True, file_okay=False),
)
@click.argument('container_context')
@click.argument(
'command',
nargs=-1,
)
@click.pass_context
def run(
ctx,
default_registry,
yes,
envvars,
file,
ignore_git_changes,
wait_for_job,
delete_completed_job,
namespace,
app_dir,
container_context,
command,
):
'''
Run a command for a given app in Kubernetes.
'''

if not wait_for_job and delete_completed_job:
raise click.BadParameter('Cannot have `--delete-job` without `--wait`!')

build = Build(
env=ctx.meta['kube_context'],
namespace=namespace,
)

if file:
custom_config_file = click.format_filename(file)
else:
custom_config_file = None

namespace, job = get_run_objects(
build, app_dir, container_context, command,
default_registry=default_registry,
extra_envvars=envvars,
ignore_git_changes=ignore_git_changes,
custom_config_file=custom_config_file,
)

if not any((namespace, job)):
click.echo('Nothing to do!')
return

log_run_changes(
build, namespace, job,
message='Executing changes:' if yes else 'Proposed changes:',
name_formatter=lambda name: click.style(name, bold=True),
)

if not yes:
click.confirm(click.style((
'Are you sure you wish to CREATE the above resource? '
'This cannot be undone.'
)), abort=True)
click.echo()

execute_run(
build,
namespace,
job,
wait_for_job=wait_for_job,
delete_completed_job=delete_completed_job,
)


@cli_bootstrap.command(help_priority=1)
@click.option(
'-y', '--yes',
Expand Down
56 changes: 6 additions & 50 deletions kubetools/deploy/commands/deploy.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
from os import path

from kubetools.config import load_kubetools_config
from kubetools.constants import (
GIT_BRANCH_ANNOTATION_KEY,
GIT_COMMIT_ANNOTATION_KEY,
GIT_TAG_ANNOTATION_KEY,
ROLE_LABEL_KEY,
)
from kubetools.constants import ROLE_LABEL_KEY
from kubetools.deploy.image import ensure_docker_images
from kubetools.deploy.util import log_actions, run_shell_command
from kubetools.deploy.util import log_actions
from kubetools.exceptions import KubeBuildError
from kubetools.kubernetes.api import (
create_deployment,
Expand All @@ -32,47 +27,7 @@
generate_namespace_config,
)


def _is_git_committed(app_dir):
git_status = run_shell_command(
'git', 'status', '--porcelain',
cwd=app_dir,
).strip().decode()

if git_status:
return False
return True


def _get_git_info(app_dir):
git_annotations = {}

commit_hash = run_shell_command(
'git', 'rev-parse', '--short=7', 'HEAD',
cwd=app_dir,
).strip().decode()
git_annotations[GIT_COMMIT_ANNOTATION_KEY] = commit_hash

branch_name = run_shell_command(
'git', 'rev-parse', '--abbrev-ref', 'HEAD',
cwd=app_dir,
).strip().decode()

if branch_name != 'HEAD':
git_annotations[GIT_BRANCH_ANNOTATION_KEY] = branch_name

try:
git_tag = run_shell_command(
'git', 'tag', '--points-at', commit_hash,
cwd=app_dir,
).strip().decode()
except KubeBuildError:
pass
else:
if git_tag:
git_annotations[GIT_TAG_ANNOTATION_KEY] = git_tag

return commit_hash, git_annotations
from .util import get_git_info, is_git_committed


# Deploy/upgrade
Expand Down Expand Up @@ -110,10 +65,10 @@ def get_deploy_objects(

for app_dir in app_dirs:
if path.exists(path.join(app_dir, '.git')):
if not _is_git_committed(app_dir) and not ignore_git_changes:
if not is_git_committed(app_dir) and not ignore_git_changes:
raise KubeBuildError(f'{app_dir} contains uncommitted changes, refusing to deploy!')

commit_hash, git_annotations = _get_git_info(app_dir)
commit_hash, git_annotations = get_git_info(app_dir)
annotations.update(git_annotations)
else:
raise KubeBuildError(f'{app_dir} is not a valid git repository!')
Expand Down Expand Up @@ -200,6 +155,7 @@ def log_deploy_changes(
log_actions(build, 'CREATE', 'deployment', new_deployments, name_formatter)
log_actions(build, 'UPDATE', 'service', update_services, name_formatter)
log_actions(build, 'UPDATE', 'deployment', update_deployments, name_formatter)
log_actions(build, 'CREATE', 'job', jobs, name_formatter)


def execute_deploy(build, namespace, services, deployments, jobs, delete_completed_jobs=True):
Expand Down
117 changes: 117 additions & 0 deletions kubetools/deploy/commands/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from os import path

from kubetools.config import load_kubetools_config
from kubetools.deploy.image import ensure_docker_images
from kubetools.deploy.util import log_actions
from kubetools.exceptions import KubeBuildError
from kubetools.kubernetes.api import (
create_job,
create_namespace,
delete_job,
get_object_name,
list_namespaces,
namespace_exists,
update_namespace,
)
from kubetools.kubernetes.config import generate_namespace_config
from kubetools.kubernetes.config.job import make_job_config

from .util import get_git_info, is_git_committed


# Run
# Create a Kubernetes job from an app + container context

def get_run_objects(
build,
app_dir,
container_context,
command,
default_registry=None,
extra_envvars=None,
extra_annotations=None,
ignore_git_changes=False,
custom_config_file=False,
):
envvars = {
'KUBE_ENV': build.env,
'KUBE_NAMESPACE': build.namespace,
}
if extra_envvars:
envvars.update(extra_envvars)

annotations = {
'kubetools/env': build.env,
'kubetools/namespace': build.namespace,
}
if extra_annotations:
annotations.update(extra_annotations)

namespace = generate_namespace_config(build.namespace, base_annotations=annotations)

if path.exists(path.join(app_dir, '.git')):
if not is_git_committed(app_dir) and not ignore_git_changes:
raise KubeBuildError(f'{app_dir} contains uncommitted changes, refusing to deploy!')

commit_hash, git_annotations = get_git_info(app_dir)
annotations.update(git_annotations)
else:
raise KubeBuildError(f'{app_dir} is not a valid git repository!')

kubetools_config = load_kubetools_config(
app_dir,
env=build.env,
namespace=build.namespace,
app_name=app_dir,
custom_config_file=custom_config_file,
)

context_to_image = ensure_docker_images(
kubetools_config, build, app_dir,
commit_hash=commit_hash,
default_registry=default_registry,
)

job = make_job_config({
'image': context_to_image[container_context],
'command': command,
})

return namespace, job


def log_run_changes(
build, namespace, job,
message='Executing changes:',
name_formatter=lambda name: name,
):
existing_namespace_names = set(
get_object_name(namespace)
for namespace in list_namespaces(build.env)
)

deploy_namespace_name = set((build.namespace,))

new_namespace = deploy_namespace_name - existing_namespace_names

with build.stage(message):
log_actions(build, 'CREATE', 'namespace', new_namespace, name_formatter)
log_actions(build, 'CREATE', 'job', [job], name_formatter)


def execute_run(build, namespace, job, wait_for_job=False, delete_completed_job=False):
if namespace:
with build.stage('Create and/or update namespace'):
if namespace_exists(build.env, namespace):
build.log_info(f'Update namespace: {get_object_name(namespace)}')
update_namespace(build.env, namespace)
else:
build.log_info(f'Create namespace: {get_object_name(namespace)}')
create_namespace(build.env, namespace)

with build.stage('Execute job'):
build.log_info(f'Create job: {get_object_name(job)}')
create_job(build.env, build.namespace, job, wait=wait_for_job)
if delete_completed_job:
build.log_info(f'Delete job: {get_object_name(job)}')
delete_job(build.env, build.namespace, job)
49 changes: 49 additions & 0 deletions kubetools/deploy/commands/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from kubetools.constants import (
GIT_BRANCH_ANNOTATION_KEY,
GIT_COMMIT_ANNOTATION_KEY,
GIT_TAG_ANNOTATION_KEY,
)
from kubetools.deploy.util import run_shell_command
from kubetools.exceptions import KubeBuildError


def is_git_committed(app_dir):
git_status = run_shell_command(
'git', 'status', '--porcelain',
cwd=app_dir,
).strip().decode()

if git_status:
return False
return True


def get_git_info(app_dir):
git_annotations = {}

commit_hash = run_shell_command(
'git', 'rev-parse', '--short=7', 'HEAD',
cwd=app_dir,
).strip().decode()
git_annotations[GIT_COMMIT_ANNOTATION_KEY] = commit_hash

branch_name = run_shell_command(
'git', 'rev-parse', '--abbrev-ref', 'HEAD',
cwd=app_dir,
).strip().decode()

if branch_name != 'HEAD':
git_annotations[GIT_BRANCH_ANNOTATION_KEY] = branch_name

try:
git_tag = run_shell_command(
'git', 'tag', '--points-at', commit_hash,
cwd=app_dir,
).strip().decode()
except KubeBuildError:
pass
else:
if git_tag:
git_annotations[GIT_TAG_ANNOTATION_KEY] = git_tag

return commit_hash, git_annotations
Loading