From 53bcf71361bd9dd0abd35673caa9ff9acea5eeda Mon Sep 17 00:00:00 2001 From: Nick Barrett Date: Thu, 8 Oct 2020 10:48:49 +0100 Subject: [PATCH 1/4] Implement `requireKubetools` config variable and `configVersion`. This replaces `minKubetoolsVersion` (maintaining backwards compatibility) and enables tracking of config version. --- kubetools/config.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/kubetools/config.py b/kubetools/config.py index e7caa89f..d6f2d4c0 100644 --- a/kubetools/config.py +++ b/kubetools/config.py @@ -7,7 +7,7 @@ import yaml -from pkg_resources import parse_version +from pkg_resources import parse_version, Requirement from . import __version__ from .exceptions import KubeConfigError @@ -101,9 +101,19 @@ def load_kubetools_config( config = yaml.safe_load(config) config['_filename'] = filename - # Check Kubetools version? + config_version = config.get('configVersion', 0) + + if 'requireKubetools' in config: + _check_kubetools_version(config['requireKubetools']) + + # TODO: remove this in v13, compat w/v12 if 'minKubetoolsVersion' in config: - _check_min_version(config) + if config_version > 0: + raise KubeConfigError(( + '`minKubetoolsVersion` is not valid in v1 config files, ' + 'please use `requireKubetools`!' + )) + _check_kubetools_version(f'>={config["minKubetoolsVersion"]}') # Apply an env name? if env: @@ -133,19 +143,17 @@ def load_kubetools_config( return config -def _check_min_version(config): +def _check_kubetools_version(version_requirement): running_version = parse_version(__version__) - needed_version = parse_version( - # Version must be a string - str(config['minKubetoolsVersion']), + required_versions = Requirement.parse( + 'kubetools{0}'.format(version_requirement), ) - if needed_version > running_version: - raise KubeConfigError( - 'Minimum Kubetools version not met, need {0} but got {1}'.format( - needed_version, running_version, - ), - ) + if running_version not in required_versions: + raise KubeConfigError(( + 'Kubetools version requirement not met ' + '(requires {0}, running {1})' + ).format(version_requirement, __version__)) def _filter_config_data(key, items_or_object, env, namespace, dev): From 3497a65d866293d89a4882de3b2923d22385b77d Mon Sep 17 00:00:00 2001 From: Nick Barrett Date: Thu, 8 Oct 2020 11:04:06 +0100 Subject: [PATCH 2/4] Implement new style conditions for config v1. Based on the discussion @ https://github.com/EDITD/kubetools/issues/64. --- kubetools/config.py | 56 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/kubetools/config.py b/kubetools/config.py index d6f2d4c0..04fc7f9e 100644 --- a/kubetools/config.py +++ b/kubetools/config.py @@ -33,6 +33,7 @@ def load_kubetools_config( env=None, namespace=None, dev=False, # when true disables env/namespace filtering (dev *only*) + test=False, custom_config_file=False, ): ''' @@ -127,6 +128,8 @@ def load_kubetools_config( env=env, namespace=namespace, dev=dev, + test=test, + use_legacy_conditions=config_version <= 0, ) # De-nest/apply any contextContexts @@ -156,13 +159,26 @@ def _check_kubetools_version(version_requirement): ).format(version_requirement, __version__)) -def _filter_config_data(key, items_or_object, env, namespace, dev): +def _filter_config_data( + key, + items_or_object, + env, + namespace, + dev, + test, + use_legacy_conditions=False, +): + condition_checker = ( + _legacy_conditions_match if use_legacy_conditions else _conditions_match + ) + def is_match(item): - return _conditions_match( + return condition_checker( item.get('conditions'), env=env, namespace=namespace, dev=dev, + test=test, ) if isinstance(items_or_object, list): @@ -185,7 +201,8 @@ def is_match(item): )) -def _conditions_match(conditions, env, namespace, dev): +# TODO: remove this in v13, compat w/v12 +def _legacy_conditions_match(conditions, env, namespace, dev, test): # No conditions? We're good! if conditions is None: return True @@ -213,6 +230,39 @@ def _conditions_match(conditions, env, namespace, dev): return True +def _conditions_match(conditions, env, namespace, dev, test): + if conditions is None: + return True + + if dev: + return conditions.get('dev', True) is True + + if test: + return conditions.get('test', True) is True + + deploy_conditions = conditions.get('deploy', True) + if deploy_conditions is True: + return True + + return any(_condition_matches(condition) for condition in deploy_conditions) + + +def _condition_matches(condition, env, namespace): + if ( + 'env' in condition and env != condition['env'] + or 'not_env' in condition and env == condition['not_env'] + ): + return False + + if ( + 'namespace' in condition and namespace != condition['namespace'] + or 'not_namespace' in condition and namespace == condition['not_namespace'] + ): + return False + + return True + + def _expand_containers(key, items_or_object, contexts, dev): def do_expand(item): return _expand_container( From 6ba897aafd2c07c921a24a4fc3f73aeeeabbf945 Mon Sep 17 00:00:00 2001 From: Nick Barrett Date: Fri, 9 Oct 2020 14:37:29 +0100 Subject: [PATCH 3/4] Rename `env` -> `context` everywhere. This maintains backwards compatibility by passing the old `KUBE_ENV` environment variable as before, in additino to the new `KUBE_CONTEXT`. The move makes "context" the consistent method by which a target cluster is chosen - this is entirely a deploy tooling specific thing as K8s clusters don't have names. This means that the use of context and the `KUBE_CLUSTER` envvar are at the discretion of the individual or team using the tool as a way to differentiate between multiple clusters. --- kubetools/cli/deploy.py | 8 +- kubetools/cli/show.py | 10 +-- kubetools/config.py | 37 +++++---- kubetools/deploy/build.py | 4 +- kubetools/deploy/commands/cleanup.py | 6 +- kubetools/deploy/commands/deploy.py | 47 +++++------ kubetools/deploy/commands/restart.py | 8 +- kubetools/deploy/image.py | 3 +- kubetools/deploy/util.py | 4 +- kubetools/dev/__init__.py | 2 +- kubetools/kubernetes/api.py | 118 +++++++++++++-------------- 11 files changed, 126 insertions(+), 121 deletions(-) diff --git a/kubetools/cli/deploy.py b/kubetools/cli/deploy.py index d033b4af..bad321b1 100644 --- a/kubetools/cli/deploy.py +++ b/kubetools/cli/deploy.py @@ -145,7 +145,7 @@ def deploy( app_dirs = (os.getcwd(),) build = Build( - env=ctx.meta['kube_context'], + context=ctx.meta['kube_context'], namespace=namespace, ) @@ -221,7 +221,7 @@ def remove(ctx, yes, force, do_cleanup, namespace, app_or_project_names): ''' build = Build( - env=ctx.meta['kube_context'], + context=ctx.meta['kube_context'], namespace=namespace, ) @@ -272,7 +272,7 @@ def cleanup(ctx, yes, namespace): ''' build = Build( - env=ctx.meta['kube_context'], + context=ctx.meta['kube_context'], namespace=namespace, ) @@ -324,7 +324,7 @@ def restart(ctx, yes, force, namespace, app_or_project_names): ''' build = Build( - env=ctx.meta['kube_context'], + context=ctx.meta['kube_context'], namespace=namespace, ) diff --git a/kubetools/cli/show.py b/kubetools/cli/show.py index b317c059..b2b2a4f2 100644 --- a/kubetools/cli/show.py +++ b/kubetools/cli/show.py @@ -109,12 +109,12 @@ def show(ctx, namespace, app): exists = False - env = ctx.meta['kube_context'] + context = ctx.meta['kube_context'] if app: click.echo(f'--> Filtering by app={app}') - services = list_services(env, namespace) + services = list_services(context, namespace) if services: exists = True @@ -128,7 +128,7 @@ def show(ctx, namespace, app): }) click.echo() - deployments = list_deployments(env, namespace) + deployments = list_deployments(context, namespace) if deployments: exists = True @@ -145,7 +145,7 @@ def show(ctx, namespace, app): click.echo() if app: - replica_sets = list_replica_sets(env, namespace) + replica_sets = list_replica_sets(context, namespace) replica_sets = [ r for r in replica_sets if r.metadata.labels.get(NAME_LABEL_KEY) == app @@ -158,7 +158,7 @@ def show(ctx, namespace, app): }) click.echo() else: - jobs = list_jobs(env, namespace) + jobs = list_jobs(context, namespace) if jobs: exists = True diff --git a/kubetools/config.py b/kubetools/config.py index 04fc7f9e..51fa2582 100644 --- a/kubetools/config.py +++ b/kubetools/config.py @@ -30,7 +30,7 @@ def load_kubetools_config( directory=None, app_name=None, # Filters for config items - env=None, + context=None, namespace=None, dev=False, # when true disables env/namespace filtering (dev *only*) test=False, @@ -42,13 +42,13 @@ def load_kubetools_config( Filtering: Most config items (deployments, dependencies, upgrades) can have conditions attached to them (eg dev: true). If an item has conditions, *either* dev or - both env/namespace must match. + both context/namespace must match. Args: directory (str): directory to load ther config from (defaults to cwd) app_name (str): name of the app we're trying to load - env (str): which envrionment to filter the config items by - namespace (str): which namespace to filter the config items by + context (str): which K8s context to filter the config items by + namespace (str): which K8s namespace to filter the config items by dev (bool): filter config items by dev mode ''' @@ -116,16 +116,16 @@ def load_kubetools_config( )) _check_kubetools_version(f'>={config["minKubetoolsVersion"]}') - # Apply an env name? - if env: - config['env'] = env + if context: + config['context'] = context + config['env'] = context # Filter out config items according to our conditions for key in TOP_LEVEL_CONDITION_KEYS: if key in config: config[key] = _filter_config_data( key, config[key], - env=env, + context=context, namespace=namespace, dev=dev, test=test, @@ -162,7 +162,7 @@ def _check_kubetools_version(version_requirement): def _filter_config_data( key, items_or_object, - env, + context, namespace, dev, test, @@ -175,7 +175,7 @@ def _filter_config_data( def is_match(item): return condition_checker( item.get('conditions'), - env=env, + context=context, namespace=namespace, dev=dev, test=test, @@ -202,7 +202,7 @@ def is_match(item): # TODO: remove this in v13, compat w/v12 -def _legacy_conditions_match(conditions, env, namespace, dev, test): +def _legacy_conditions_match(conditions, context, namespace, dev, test): # No conditions? We're good! if conditions is None: return True @@ -216,7 +216,7 @@ def _legacy_conditions_match(conditions, env, namespace, dev, test): return False # If we have envs but our env isn't present, fail! - if 'envs' in conditions and env not in conditions['envs']: + if 'envs' in conditions and context not in conditions['envs']: return False # We have namespaces but our namespace isn't present, fail! @@ -230,7 +230,7 @@ def _legacy_conditions_match(conditions, env, namespace, dev, test): return True -def _conditions_match(conditions, env, namespace, dev, test): +def _conditions_match(conditions, context, namespace, dev, test): if conditions is None: return True @@ -244,13 +244,16 @@ def _conditions_match(conditions, env, namespace, dev, test): if deploy_conditions is True: return True - return any(_condition_matches(condition) for condition in deploy_conditions) + return any( + _condition_matches(condition, context, namespace) + for condition in deploy_conditions + ) -def _condition_matches(condition, env, namespace): +def _condition_matches(condition, context, namespace): if ( - 'env' in condition and env != condition['env'] - or 'not_env' in condition and env == condition['not_env'] + 'context' in condition and context != condition['context'] + or 'not_context' in condition and context == condition['not_context'] ): return False diff --git a/kubetools/deploy/build.py b/kubetools/deploy/build.py index 0c94ab26..c6418581 100644 --- a/kubetools/deploy/build.py +++ b/kubetools/deploy/build.py @@ -14,8 +14,8 @@ class Build(object): in_stage = False - def __init__(self, env, namespace): - self.env = env + def __init__(self, context, namespace): + self.context = context self.namespace = namespace def log_info(self, text, extra_detail=None, formatter=lambda s: s): diff --git a/kubetools/deploy/commands/cleanup.py b/kubetools/deploy/commands/cleanup.py index 14ac73ba..34cd109e 100644 --- a/kubetools/deploy/commands/cleanup.py +++ b/kubetools/deploy/commands/cleanup.py @@ -17,7 +17,7 @@ # If the cleanup removes all remaining objects, the namespace will be deleted too. def get_cleanup_objects(build): - replica_sets = list_replica_sets(build.env, build.namespace) + replica_sets = list_replica_sets(build.context, build.namespace) replica_set_names = set(get_object_name(replica_set) for replica_set in replica_sets) replica_sets_to_delete = [] replica_set_names_to_delete = set() @@ -34,7 +34,7 @@ def get_cleanup_objects(build): if replica_set.metadata.deletion_timestamp: replica_set_names_already_deleted.add(get_object_name(replica_set)) - pods = list_pods(build.env, build.namespace) + pods = list_pods(build.context, build.namespace) pod_names = set(get_object_name(pod) for pod in pods) pods_to_delete = [] pod_names_to_delete = set() @@ -54,7 +54,7 @@ def get_cleanup_objects(build): if pod.metadata.deletion_timestamp: pod_names_already_deleted.add(get_object_name(pod)) - namespaces = list_namespaces(build.env) + namespaces = list_namespaces(build.context) current_namespace = None for namespace in namespaces: if namespace.metadata.name == build.namespace: diff --git a/kubetools/deploy/commands/deploy.py b/kubetools/deploy/commands/deploy.py index 37663714..2187bd23 100644 --- a/kubetools/deploy/commands/deploy.py +++ b/kubetools/deploy/commands/deploy.py @@ -92,14 +92,15 @@ def get_deploy_objects( all_jobs = [] envvars = { - 'KUBE_ENV': build.env, + 'KUBE_ENV': build.context, # legacy support + 'KUBE_CONTEXT': build.context, 'KUBE_NAMESPACE': build.namespace, } if extra_envvars: envvars.update(extra_envvars) annotations = { - 'kubetools/env': build.env, + 'kubetools/env': build.context, 'kubetools/namespace': build.namespace, } if extra_annotations: @@ -119,7 +120,7 @@ def get_deploy_objects( kubetools_config = load_kubetools_config( app_dir, - env=build.env, + context=build.context, namespace=build.namespace, app_name=app_dir, custom_config_file=custom_config_file, @@ -145,7 +146,7 @@ def get_deploy_objects( existing_deployments = { get_object_name(deployment): deployment - for deployment in list_deployments(build.env, build.namespace) + for deployment in list_deployments(build.context, build.namespace) } # If we haven't been provided an explicit number of replicas, default to using @@ -166,15 +167,15 @@ def log_deploy_changes( ): existing_namespace_names = set( get_object_name(namespace) - for namespace in list_namespaces(build.env) + for namespace in list_namespaces(build.context) ) existing_service_names = set( get_object_name(service) - for service in list_services(build.env, build.namespace) + for service in list_services(build.context, build.namespace) ) existing_deployment_names = set( get_object_name(deployment) - for deployment in list_deployments(build.env, build.namespace) + for deployment in list_deployments(build.context, build.namespace) ) deploy_service_names = set( @@ -223,37 +224,37 @@ def execute_deploy(build, namespace, services, deployments, jobs): # Now execute the deploy process if namespace: with build.stage('Create and/or update namespace'): - if namespace_exists(build.env, namespace): + if namespace_exists(build.context, namespace): build.log_info(f'Update namespace: {get_object_name(namespace)}') - update_namespace(build.env, namespace) + update_namespace(build.context, namespace) else: build.log_info(f'Create namespace: {get_object_name(namespace)}') - create_namespace(build.env, namespace) + create_namespace(build.context, namespace) if depend_services: with build.stage('Create and/or update dependency services'): for service in depend_services: - if deployment_exists(build.env, build.namespace, service): + if deployment_exists(build.context, build.namespace, service): build.log_info(f'Update service: {get_object_name(service)}') - update_service(build.env, build.namespace, service) + update_service(build.context, build.namespace, service) else: build.log_info(f'Create service: {get_object_name(service)}') - create_service(build.env, build.namespace, service) + create_service(build.context, build.namespace, service) if depend_deployments: with build.stage('Create and/or update dependency deployments'): for deployment in depend_deployments: - if deployment_exists(build.env, build.namespace, deployment): + if deployment_exists(build.context, build.namespace, deployment): build.log_info(f'Update deployment: {get_object_name(deployment)}') - update_deployment(build.env, build.namespace, deployment) + update_deployment(build.context, build.namespace, deployment) else: build.log_info(f'Create deployment: {get_object_name(deployment)}') - create_deployment(build.env, build.namespace, deployment) + create_deployment(build.context, build.namespace, deployment) noexist_main_services = [] exist_main_services = [] for service in main_services: - if not service_exists(build.env, build.namespace, service): + if not service_exists(build.context, build.namespace, service): noexist_main_services.append(service) else: exist_main_services.append(service) @@ -262,12 +263,12 @@ def execute_deploy(build, namespace, services, deployments, jobs): with build.stage('Create any app services that do not exist'): for service in noexist_main_services: build.log_info(f'Create service: {get_object_name(service)}') - create_service(build.env, build.namespace, service) + create_service(build.context, build.namespace, service) noexist_main_deployments = [] exist_main_deployments = [] for deployment in main_deployments: - if not deployment_exists(build.env, build.namespace, deployment): + if not deployment_exists(build.context, build.namespace, deployment): noexist_main_deployments.append(deployment) else: exist_main_deployments.append(deployment) @@ -276,22 +277,22 @@ def execute_deploy(build, namespace, services, deployments, jobs): with build.stage('Create any app deployments that do not exist'): for deployment in noexist_main_deployments: build.log_info(f'Create deployment: {get_object_name(deployment)}') - create_deployment(build.env, build.namespace, deployment) + create_deployment(build.context, build.namespace, deployment) if jobs: with build.stage('Execute upgrades'): for job in jobs: build.log_info(f'Create job: {get_object_name(job)}') - create_job(build.env, build.namespace, job) + create_job(build.context, build.namespace, job) if exist_main_deployments: with build.stage('Update existing app deployments'): for deployment in exist_main_deployments: build.log_info(f'Update deployment: {get_object_name(deployment)}') - update_deployment(build.env, build.namespace, deployment) + update_deployment(build.context, build.namespace, deployment) if exist_main_services: with build.stage('Update existing app services'): for service in exist_main_services: build.log_info(f'Update service: {get_object_name(service)}') - update_service(build.env, build.namespace, service) + update_service(build.context, build.namespace, service) diff --git a/kubetools/deploy/commands/restart.py b/kubetools/deploy/commands/restart.py index 8efe5f97..7ec1a46f 100644 --- a/kubetools/deploy/commands/restart.py +++ b/kubetools/deploy/commands/restart.py @@ -26,7 +26,7 @@ def get_restart_objects(build, app_names=None, force=False): for deployment in deployments } - replica_sets = list_replica_sets(build.env, build.namespace) + replica_sets = list_replica_sets(build.context, build.namespace) replica_set_names_to_deployment = {} for replica_set in replica_sets: @@ -50,7 +50,7 @@ def get_restart_objects(build, app_names=None, force=False): name_to_deployment[owner_name] ) - pods = list_pods(build.env, build.namespace) + pods = list_pods(build.context, build.namespace) deployment_name_to_pods = defaultdict(list) for pod in pods: @@ -81,5 +81,5 @@ def execute_restart(build, deployments_and_pods): with build.stage(f'Restart pods for {get_object_name(deployment)}'): for pod in pods: build.log_info(f'Delete pod: {get_object_name(pod)}') - delete_pod(build.env, build.namespace, pod) - wait_for_deployment(build.env, build.namespace, deployment) + delete_pod(build.context, build.namespace, pod) + wait_for_deployment(build.context, build.namespace, deployment) diff --git a/kubetools/deploy/image.py b/kubetools/deploy/image.py index 75df5b43..151d2e09 100644 --- a/kubetools/deploy/image.py +++ b/kubetools/deploy/image.py @@ -177,7 +177,8 @@ def _ensure_docker_images( # Run it, passing in the commit hashes as ENVars env = { - 'KUBE_ENV': build.env, + 'KUBE_ENV': build.context, # legacy support + 'KUBE_CONTEXT': build.context, 'BUILD_COMMIT': commit_hash, } if previous_commit: diff --git a/kubetools/deploy/util.py b/kubetools/deploy/util.py index 4bbb51fc..53a8fbd0 100644 --- a/kubetools/deploy/util.py +++ b/kubetools/deploy/util.py @@ -46,14 +46,14 @@ def log_actions(build, action, object_type, names, name_formatter): def delete_objects(build, objects, delete_function): for obj in objects: build.log_info(f'Delete: {get_object_name(obj)}') - delete_function(build.env, build.namespace, obj) + delete_function(build.context, build.namespace, obj) def get_app_objects( build, app_or_project_names, list_objects_function, force=False, ): - objects = list_objects_function(build.env, build.namespace) + objects = list_objects_function(build.context, build.namespace) def filter_object(obj): if not is_kubetools_object(obj): diff --git a/kubetools/dev/__init__.py b/kubetools/dev/__init__.py index acb23a1f..b2412fdc 100644 --- a/kubetools/dev/__init__.py +++ b/kubetools/dev/__init__.py @@ -32,4 +32,4 @@ def dev(ctx, env, debug=False): env = settings.DEV_DEFAULT_ENV # Get the config and attach it to the context - ctx.obj = load_kubetools_config(env=env, dev=True) + ctx.obj = load_kubetools_config(context=env, dev=True) diff --git a/kubetools/kubernetes/api.py b/kubetools/kubernetes/api.py index 0d0c20e0..9fd56e1d 100644 --- a/kubetools/kubernetes/api.py +++ b/kubetools/kubernetes/api.py @@ -27,22 +27,22 @@ def is_kubetools_object(obj): return True -def _get_api_client(env): - return config.new_client_from_config(context=env) +def _get_api_client(context): + return config.new_client_from_config(context=context) -def _get_k8s_core_api(env): - api_client = _get_api_client(env) +def _get_k8s_core_api(context): + api_client = _get_api_client(context) return client.CoreV1Api(api_client=api_client) -def _get_k8s_apps_api(env): - api_client = _get_api_client(env) +def _get_k8s_apps_api(context): + api_client = _get_api_client(context) return client.AppsV1Api(api_client=api_client) -def _get_k8s_batch_api(env): - api_client = _get_api_client(env) +def _get_k8s_batch_api(context): + api_client = _get_api_client(context) return client.BatchV1Api(api_client=api_client) @@ -87,18 +87,18 @@ def _wait_for_no_object(*args): return _wait_for(lambda: _object_exists(*args) is False) -def namespace_exists(env, namespace_obj): - k8s_core_api = _get_k8s_core_api(env) +def namespace_exists(context, namespace_obj): + k8s_core_api = _get_k8s_core_api(context) return _object_exists(k8s_core_api, 'read_namespace', None, namespace_obj) -def list_namespaces(env): - k8s_core_api = _get_k8s_core_api(env) +def list_namespaces(context): + k8s_core_api = _get_k8s_core_api(context) return k8s_core_api.list_namespace().items -def create_namespace(env, namespace_obj): - k8s_core_api = _get_k8s_core_api(env) +def create_namespace(context, namespace_obj): + k8s_core_api = _get_k8s_core_api(context) k8s_namespace = k8s_core_api.create_namespace( body=namespace_obj, ) @@ -107,8 +107,8 @@ def create_namespace(env, namespace_obj): return k8s_namespace -def update_namespace(env, namespace_obj): - k8s_core_api = _get_k8s_core_api(env) +def update_namespace(context, namespace_obj): + k8s_core_api = _get_k8s_core_api(context) k8s_namespace = k8s_core_api.patch_namespace( name=get_object_name(namespace_obj), body=namespace_obj, @@ -117,8 +117,8 @@ def update_namespace(env, namespace_obj): return k8s_namespace -def delete_namespace(env, namespace, namespace_obj): - k8s_core_api = _get_k8s_core_api(env) +def delete_namespace(context, namespace, namespace_obj): + k8s_core_api = _get_k8s_core_api(context) k8s_core_api.delete_namespace( name=get_object_name(namespace_obj), ) @@ -126,13 +126,13 @@ def delete_namespace(env, namespace, namespace_obj): _wait_for_no_object(k8s_core_api, 'read_namespace', None, namespace_obj) -def list_pods(env, namespace): - k8s_core_api = _get_k8s_core_api(env) +def list_pods(context, namespace): + k8s_core_api = _get_k8s_core_api(context) return k8s_core_api.list_namespaced_pod(namespace=namespace).items -def delete_pod(env, namespace, pod): - k8s_core_api = _get_k8s_core_api(env) +def delete_pod(context, namespace, pod): + k8s_core_api = _get_k8s_core_api(context) k8s_core_api.delete_namespaced_pod( name=get_object_name(pod), namespace=namespace, @@ -141,13 +141,13 @@ def delete_pod(env, namespace, pod): _wait_for_no_object(k8s_core_api, 'read_namespaced_pod', namespace, pod) -def list_replica_sets(env, namespace): - k8s_apps_api = _get_k8s_apps_api(env) +def list_replica_sets(context, namespace): + k8s_apps_api = _get_k8s_apps_api(context) return k8s_apps_api.list_namespaced_replica_set(namespace=namespace).items -def delete_replica_set(env, namespace, replica_set): - k8s_apps_api = _get_k8s_apps_api(env) +def delete_replica_set(context, namespace, replica_set): + k8s_apps_api = _get_k8s_apps_api(context) k8s_apps_api.delete_namespaced_replica_set( name=get_object_name(replica_set), namespace=namespace, @@ -156,13 +156,13 @@ def delete_replica_set(env, namespace, replica_set): _wait_for_no_object(k8s_apps_api, 'read_namespaced_replica_set', namespace, replica_set) -def list_services(env, namespace): - k8s_core_api = _get_k8s_core_api(env) +def list_services(context, namespace): + k8s_core_api = _get_k8s_core_api(context) return k8s_core_api.list_namespaced_service(namespace=namespace).items -def delete_service(env, namespace, service): - k8s_core_api = _get_k8s_core_api(env) +def delete_service(context, namespace, service): + k8s_core_api = _get_k8s_core_api(context) k8s_core_api.delete_namespaced_service( name=get_object_name(service), namespace=namespace, @@ -171,13 +171,13 @@ def delete_service(env, namespace, service): _wait_for_no_object(k8s_core_api, 'read_namespaced_service', namespace, service) -def service_exists(env, namespace, service): - k8s_core_api = _get_k8s_core_api(env) +def service_exists(context, namespace, service): + k8s_core_api = _get_k8s_core_api(context) return _object_exists(k8s_core_api, 'read_namespaced_service', namespace, service) -def create_service(env, namespace, service): - k8s_core_api = _get_k8s_core_api(env) +def create_service(context, namespace, service): + k8s_core_api = _get_k8s_core_api(context) k8s_service = k8s_core_api.create_namespaced_service( body=service, namespace=namespace, @@ -187,8 +187,8 @@ def create_service(env, namespace, service): return k8s_service -def update_service(env, namespace, service): - k8s_core_api = _get_k8s_core_api(env) +def update_service(context, namespace, service): + k8s_core_api = _get_k8s_core_api(context) k8s_service = k8s_core_api.patch_namespaced_service( name=get_object_name(service), body=service, @@ -198,13 +198,13 @@ def update_service(env, namespace, service): return k8s_service -def list_deployments(env, namespace): - k8s_apps_api = _get_k8s_apps_api(env) +def list_deployments(context, namespace): + k8s_apps_api = _get_k8s_apps_api(context) return k8s_apps_api.list_namespaced_deployment(namespace=namespace).items -def delete_deployment(env, namespace, deployment): - k8s_apps_api = _get_k8s_apps_api(env) +def delete_deployment(context, namespace, deployment): + k8s_apps_api = _get_k8s_apps_api(context) k8s_apps_api.delete_namespaced_deployment( name=get_object_name(deployment), namespace=namespace, @@ -213,36 +213,36 @@ def delete_deployment(env, namespace, deployment): _wait_for_no_object(k8s_apps_api, 'read_namespaced_deployment', namespace, deployment) -def deployment_exists(env, namespace, deployment): - k8s_apps_api = _get_k8s_apps_api(env) +def deployment_exists(context, namespace, deployment): + k8s_apps_api = _get_k8s_apps_api(context) return _object_exists(k8s_apps_api, 'read_namespaced_deployment', namespace, deployment) -def create_deployment(env, namespace, deployment): - k8s_apps_api = _get_k8s_apps_api(env) +def create_deployment(context, namespace, deployment): + k8s_apps_api = _get_k8s_apps_api(context) k8s_deployment = k8s_apps_api.create_namespaced_deployment( body=deployment, namespace=namespace, ) - wait_for_deployment(env, namespace, k8s_deployment) + wait_for_deployment(context, namespace, k8s_deployment) return k8s_deployment -def update_deployment(env, namespace, deployment): - k8s_apps_api = _get_k8s_apps_api(env) +def update_deployment(context, namespace, deployment): + k8s_apps_api = _get_k8s_apps_api(context) k8s_deployment = k8s_apps_api.patch_namespaced_deployment( name=get_object_name(deployment), body=deployment, namespace=namespace, ) - wait_for_deployment(env, namespace, k8s_deployment) + wait_for_deployment(context, namespace, k8s_deployment) return k8s_deployment -def wait_for_deployment(env, namespace, deployment): - k8s_apps_api = _get_k8s_apps_api(env) +def wait_for_deployment(context, namespace, deployment): + k8s_apps_api = _get_k8s_apps_api(context) def check_deployment(): d = k8s_apps_api.read_namespaced_deployment( @@ -256,13 +256,13 @@ def check_deployment(): _wait_for(check_deployment, get_object_name(deployment)) -def list_jobs(env, namespace): - k8s_batch_api = _get_k8s_batch_api(env) +def list_jobs(context, namespace): + k8s_batch_api = _get_k8s_batch_api(context) return k8s_batch_api.list_namespaced_job(namespace=namespace).items -def delete_job(env, namespace, job): - k8s_batch_api = _get_k8s_batch_api(env) +def delete_job(context, namespace, job): + k8s_batch_api = _get_k8s_batch_api(context) k8s_batch_api.delete_namespaced_job( name=get_object_name(job), namespace=namespace, @@ -271,19 +271,19 @@ def delete_job(env, namespace, job): _wait_for_no_object(k8s_batch_api, 'read_namespaced_job', namespace, job) -def create_job(env, namespace, job): - k8s_batch_api = _get_k8s_batch_api(env) +def create_job(context, namespace, job): + k8s_batch_api = _get_k8s_batch_api(context) k8s_job = k8s_batch_api.create_namespaced_job( body=job, namespace=namespace, ) - wait_for_job(env, namespace, k8s_job) + wait_for_job(context, namespace, k8s_job) return k8s_job -def wait_for_job(env, namespace, job): - k8s_batch_api = _get_k8s_batch_api(env) +def wait_for_job(context, namespace, job): + k8s_batch_api = _get_k8s_batch_api(context) def check_job(): j = k8s_batch_api.read_namespaced_job( From 22c5169efac532cce12f5586c4af69e6ebc82984 Mon Sep 17 00:00:00 2001 From: Nick Barrett Date: Fri, 9 Oct 2020 14:37:40 +0100 Subject: [PATCH 4/4] Add tests for the condition checking logic. --- tests/test_config_conditions.py | 105 ++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 tests/test_config_conditions.py diff --git a/tests/test_config_conditions.py b/tests/test_config_conditions.py new file mode 100644 index 00000000..9392389d --- /dev/null +++ b/tests/test_config_conditions.py @@ -0,0 +1,105 @@ +from unittest import TestCase + +from kubetools.config import _conditions_match, _legacy_conditions_match + + +def _test_conditions( + matcher, + conditions, + desired_value, + context=None, + namespace=None, + dev=False, + test=False, +): + result = matcher(conditions, context, namespace, dev, test) + assert result == desired_value + + +def _test_condition_matcher(*args, **kwargs): + _test_conditions(_conditions_match, *args, **kwargs) + + +def _test_legacy_condition_matcher(*args, **kwargs): + _test_conditions(_legacy_conditions_match, *args, **kwargs) + + +class TestConditionMatching(TestCase): + def test_no_conditions(self): + _test_condition_matcher(None, True) + + def test_no_dev_conditions(self): + conditions = {'dev': False} + _test_condition_matcher(conditions, False, dev=True) + + def test_no_test_conditions(self): + conditions = {'test': False} + _test_condition_matcher(conditions, False, test=True) + + def test_deploy_true_conditions(self): + conditions = {'deploy': True} + _test_condition_matcher(conditions, True) + + def test_deploy_namespace_context_conditions(self): + conditions = {'deploy': [ + {'namespace': 'another-namespace', 'context': 'another-context'}, + {'namespace': 'a-namespace', 'context': 'a-context'}, + ]} + _test_condition_matcher( + conditions, + True, + namespace='a-namespace', + context='a-context', + ) + + def test_deploy_context_conditions(self): + conditions = {'deploy': [ + {'context': 'a-context'}, + ]} + _test_condition_matcher( + conditions, + True, + namespace='a-namespace', + context='a-context', + ) + + def test_deploy_namespace_conditions(self): + conditions = {'deploy': [ + {'namespace': 'a-namespace'}, + ]} + _test_condition_matcher( + conditions, + True, + namespace='a-namespace', + context='a-context', + ) + + def test_deploy_not_context_conditions(self): + conditions = {'deploy': [ + {'not_context': 'a-context'}, + ]} + _test_condition_matcher( + conditions, + False, + namespace='a-namespace', + context='a-context', + ) + + def test_deploy_not_namespace_conditions(self): + conditions = {'deploy': [ + {'not_namespace': 'a-namespace'}, + ]} + _test_condition_matcher( + conditions, + False, + namespace='a-namespace', + context='a-context', + ) + + +class TestLegacyConditionMatching(TestCase): + def test_no_conditions(self): + _test_legacy_condition_matcher(None, True) + + def test_dev_conditions(self): + _test_legacy_condition_matcher({'dev': True}, True, dev=True)