Skip to content

Commit

Permalink
Merge pull request #1 from KI-labs/master
Browse files Browse the repository at this point in the history
K-80: tf state file handling for multiple deployments (KI-labs#95)
  • Loading branch information
aalhour authored Oct 22, 2020
2 parents 951c1cd + 8ac1e00 commit 321f596
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 103 deletions.
67 changes: 30 additions & 37 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.3
1.1.4
32 changes: 21 additions & 11 deletions cli/kaos_cli/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from kaos_cli.facades.backend_facade import BackendFacade, is_cloud_provider
from typing import Optional

from kaos_cli.utils.helpers import build_dir
from kaos_cli.utils.custom_classes import CustomHelpOrder, NotRequiredIf
from kaos_cli.utils.decorators import build_env_check, pass_obj
from kaos_cli.utils.validators import validate_unused_port, validate_inputs, EnvironmentState
Expand All @@ -16,8 +17,13 @@
# BUILD group
# =============
@click.group(name='build', cls=CustomHelpOrder,
short_help=' {} and its {} '.format(
click.style('build', bold=True), click.style('sub-commands', bold=True)))
short_help='{} and its {}: {}, {}, {} and {}'.format(
click.style('Infrastructure deployments', bold=True),
click.style('sub-commands', bold=False),
click.style('deploy', bold=True),
click.style('list', bold=True),
click.style('set', bold=True),
click.style('active', bold=True)))
def build():
"""
Build command allows you to deploy infrastructre and list the available deployments
Expand All @@ -26,8 +32,9 @@ def build():


@build.command(name='deploy',
short_help='{}'.format(
click.style('Build the kaos backend', bold=True, fg='black')))
short_help='{} the {} backend'.format(
click.style('Build', bold=True),
click.style('kaos', bold=True)))
@click.option('-c', '--cloud', type=click.Choice([DOCKER, MINIKUBE, AWS, GCP]),
help='selected provider', required=True)
@click.option('-e', '--env', type=click.Choice(['prod', 'stage', 'dev']),
Expand Down Expand Up @@ -58,9 +65,12 @@ def deploy(backend: BackendFacade, cloud: str, env: str, force: bool, verbose: b
click.echo('{} - Performing {} build of the backend'.format(
click.style("Warning", bold=True, fg='yellow'),
click.style("force", bold=True)))
env_state.remove_terraform_files()

# set env variable appropriately
env_state.set_build_env()
cloud = env_state.cloud
env = env_state.env

if not yes:
# confirm creation of backend
Expand Down Expand Up @@ -106,7 +116,6 @@ def deploy(backend: BackendFacade, cloud: str, env: str, force: bool, verbose: b
sys.exit(1)

try:

is_built_successfully, env_state = backend.build(env_state.cloud,
env_state.env,
local_backend=local_backend,
Expand All @@ -129,7 +138,7 @@ def deploy(backend: BackendFacade, cloud: str, env: str, force: bool, verbose: b
click.echo("{} - Successfully built {} [{}] environment".format(
click.style("Info", bold=True, fg='green'),
click.style('kaos', bold=True),
click.style(env_state.env, bold=True, fg='blue')))
click.style(env, bold=True, fg='blue')))
else:
click.echo("{} - Successfully built {} environment".format(
click.style("Info", bold=True, fg='green'),
Expand All @@ -139,8 +148,8 @@ def deploy(backend: BackendFacade, cloud: str, env: str, force: bool, verbose: b
click.echo("{} - Deployment Unsuccessful while creating {} [{} {}] environment".format(
click.style("Error", bold=True, fg='red'),
click.style('kaos', bold=True),
click.style(env_state.cloud, bold=True, fg='red'),
click.style(env_state.env, bold=True, fg='red'))),
click.style(cloud, bold=True, fg='red'),
click.style(env, bold=True, fg='red'))),
sys.exit(1)

except Exception as e:
Expand Down Expand Up @@ -250,8 +259,9 @@ def get_active_context(backend: BackendFacade):


@click.command(name='destroy',
short_help='{}'.format(
click.style('Destroy the kaos backend', bold=True, fg='black')))
short_help='{} the {} backend'.format(
click.style('Destroys', bold=True),
click.style('kaos', bold=True)))
@click.option('-c', '--cloud', type=click.Choice([DOCKER, MINIKUBE, AWS, GCP]),
help='selected provider provider', required=True)
@click.option('-e', '--env', type=click.Choice(['prod', 'stage', 'dev']),
Expand All @@ -272,7 +282,7 @@ def destroy(backend: BackendFacade, cloud, env, verbose, yes):
env_state.set_build_env()

# Ensure that appropriate warnings are displayed
env_state.validate_if_tfstate_exits()
env_state.validate_if_tf_state_exits()

if not yes:
# confirm creation of backend
Expand Down
77 changes: 35 additions & 42 deletions cli/kaos_cli/facades/backend_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,14 @@
from distutils.dir_util import copy_tree

import requests
from kaos_cli.constants import DOCKER, MINIKUBE, PROVIDER_DICT, AWS, BACKEND, INFRASTRUCTURE, GCP, LOCAL_CONFIG_DICT, \
from kaos_cli.constants import DOCKER, MINIKUBE, AWS, BACKEND, INFRASTRUCTURE, GCP, LOCAL_CONFIG_DICT, \
CONTEXTS, ACTIVE, BACKEND_CACHE, DEFAULT, USER, REMOTE, KAOS_STATE_DIR
from kaos_cli.exceptions.exceptions import HostnameError
from kaos_cli.services.state_service import StateService
from kaos_cli.services.terraform_service import TerraformService
from kaos_cli.utils.environment import check_environment
from kaos_cli.utils.helpers import build_dir
from kaos_cli.utils.validators import EnvironmentState
from kaos_cli.exceptions.handle_exceptions import handle_specific_exception, handle_exception


def is_cloud_provider(cloud):
return cloud not in (DOCKER, MINIKUBE)
from kaos_cli.utils.validators import EnvironmentState, is_cloud_provider


class BackendFacade:
Expand Down Expand Up @@ -134,17 +129,18 @@ def set_context_by_index(self, index):

def build(self, provider, env, local_backend=False, verbose=False):
env_state = EnvironmentState.initialize(provider, env)
if not env_state.if_build_dir_exists:

if not os.path.exists(env_state.build_dir):
build_dir(env_state.build_dir)

auth_token = uuid.uuid4()
extra_vars = self._get_vars(provider, env_state.build_dir, auth_token)
self.tf_service.cd_dir(env_state.build_dir)

self.tf_service.cd_dir(env_state.build_dir)
self.tf_service.set_verbose(verbose)
directory = self._tf_init(provider, env, local_backend, destroying=False)
self.tf_service.plan(directory, extra_vars)
self.tf_service.apply(directory, extra_vars)
self._tf_init(env_state, provider, env, local_backend, destroying=False)
self.tf_service.plan(env_state.build_dir, extra_vars)
self.tf_service.apply(env_state.build_dir, extra_vars)
self.tf_service.execute()

# check if the deployed successfully
Expand All @@ -153,20 +149,15 @@ def build(self, provider, env, local_backend=False, verbose=False):

if env_state.if_tfstate_exists:
url, kubeconfig = self._parse_config(env_state.build_dir)

current_context = provider if provider in [DOCKER, MINIKUBE] else f"{provider}_{env}"

self.state_service.set(DEFAULT, user=USER)

self._set_context_list(current_context)
self._set_active_context(current_context)
self.state_service.set(current_context)

self.state_service.set_section(current_context, BACKEND,
url=url, token=auth_token)
self.state_service.set_section(current_context, INFRASTRUCTURE,
kubeconfig=kubeconfig)

self.state_service.write()
return True, env_state

Expand All @@ -177,14 +168,17 @@ def destroy(self, env_state, verbose=False):
self.tf_service.cd_dir(env_state.build_dir)

self.tf_service.set_verbose(verbose)
directory = self._tf_init(env_state.cloud, env_state.env, local_backend=False, destroying=True)

self._tf_init(env_state, env_state.cloud, env_state.env, local_backend=False, destroying=True)

current_context = env_state.cloud if env_state.cloud in [DOCKER, MINIKUBE] \
else env_state.cloud + '_' + env_state.env

self._delete_resources(current_context)
self._unset_context_list(current_context)
self._remove_section(current_context)
self._deactivate_context()
self.tf_service.destroy(directory, extra_vars)
self.tf_service.destroy(env_state.build_dir, extra_vars)
self.tf_service.execute()
self._remove_build_files(env_state.build_dir)
self.state_service.write()
Expand All @@ -207,25 +201,21 @@ def _delete_resources(self, context):
if self.state_service.has_section(context, BACKEND):
requests.delete(f"{self.url}/internal/resources")

def _tf_init(self, provider, env, local_backend, destroying=False):
directory = PROVIDER_DICT.get(provider)
def _tf_init(self, env_state, provider, env, local_backend, destroying=False):
check_environment(provider)
if is_cloud_provider(provider):
provider_directory = f"{directory}/{env}"
directory = f"{directory}/__working_{env}"
if not destroying or not os.path.isdir(directory):
copy_tree(provider_directory, directory)
if not destroying or not os.path.isdir(env_state.build_dir):
copy_tree(env_state.provider_directory, env_state.build_dir)
if local_backend:
shutil.copy(LOCAL_CONFIG_DICT.get(provider), directory)
shutil.copy(LOCAL_CONFIG_DICT.get(provider), env_state.build_dir)

# simply always create the workspace
self.tf_service.init(directory)
self.tf_service.new_workspace(directory, env)
self.tf_service.select_workspace(directory, env)

self.tf_service.init(env_state.build_dir)
self.tf_service.new_workspace(env_state.build_dir, env)
self.tf_service.select_workspace(env_state.build_dir, env)
else:
self.tf_service.init(directory)
return directory
copy_tree(env_state.provider_directory, env_state.build_dir)
self.tf_service.init(env_state.build_dir)

def _set_context_list(self, current_context):
try:
Expand All @@ -236,17 +226,20 @@ def _set_context_list(self, current_context):
updated_contexts = []

if isinstance(contexts, list):
contexts.append(current_context)
if current_context not in contexts:
contexts.append(current_context)
updated_contexts = contexts
elif isinstance(contexts, str) or not contexts:
# There is only one context or no context in available contexts
if contexts:
# exactly one available context
updated_contexts.append(contexts)
updated_contexts.append(current_context)
else:
# no available context
updated_contexts.append(current_context)
# check if current context is the same as existing context
if current_context != contexts:
# There is only one context or no context in available contexts
if contexts:
# exactly one available context
updated_contexts.append(contexts)
updated_contexts.append(current_context)
else:
# no available context
updated_contexts.append(current_context)

self.state_service.set(CONTEXTS, environments=updated_contexts)

Expand Down Expand Up @@ -311,7 +304,7 @@ def _parse_config(dir_build):

@staticmethod
def _get_vars(provider, dir_build, auth_token=None):
extra_vars = f"--var config_dir={dir_build} --var token={auth_token}"
extra_vars = f"--var config_dir={dir_build} --var token={auth_token} "

if provider == AWS:
KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
Expand Down
Loading

0 comments on commit 321f596

Please sign in to comment.