Skip to content

Commit

Permalink
Implement atomic deployments for Nulecule application. Fixes projecta…
Browse files Browse the repository at this point in the history
…tomic#421

When there's an error during running a Nulecule application,
rollback the changes made by stopping the application.
  • Loading branch information
rtnpro committed Jan 19, 2016
1 parent 36f7ad4 commit 98c7928
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 9 deletions.
8 changes: 5 additions & 3 deletions atomicapp/nulecule/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,22 +159,23 @@ def run(self, provider_key=None, dryrun=False):
for component in self.components:
component.run(provider_key, dryrun)

def stop(self, provider_key=None, dryrun=False):
def stop(self, provider_key=None, dryrun=False, ignore_errors=False):
"""
Stop the Nulecule application.
Args:
provider_key (str): Provider to use for running Nulecule
application
dryrun (bool): Do not make changes to host when True
ignore_errors (bool): Ignore errors, if any, when True
Returns:
None
"""
provider_key, provider = self.get_provider(provider_key, dryrun)
# stop the Nulecule application
for component in self.components:
component.stop(provider_key, dryrun)
component.stop(provider_key, dryrun, ignore_errors)

# TODO: NOT YET IMPLEMENTED
def uninstall(self):
Expand Down Expand Up @@ -292,7 +293,7 @@ def run(self, provider_key, dryrun=False):
provider.init()
provider.deploy()

def stop(self, provider_key=None, dryrun=False):
def stop(self, provider_key=None, dryrun=False, ignore_errors=False):
"""
Stop the Nulecule component with the specified provider.
"""
Expand All @@ -302,6 +303,7 @@ def stop(self, provider_key=None, dryrun=False):
provider_key, provider = self.get_provider(provider_key, dryrun)
provider.artifacts = self.rendered_artifacts.get(provider_key, [])
provider.init()
provider.ignore_errors = ignore_errors
provider.undeploy()

def load_config(self, config=None, ask=False, skip_asking=False):
Expand Down
15 changes: 12 additions & 3 deletions atomicapp/nulecule/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def run(self, cli_provider, answers_output, ask,

self.nulecule.load_config(config=self.nulecule.config, ask=ask)
self.nulecule.render(cli_provider, dryrun)
self.nulecule.run(cli_provider, dryrun)

runtime_answers = self._get_runtime_answers(
self.nulecule.config, cli_provider)
self._write_answers(
Expand All @@ -243,12 +243,21 @@ def run(self, cli_provider, answers_output, ask,
self._write_answers(answers_output, runtime_answers,
self.answers_format)

def stop(self, cli_provider, **kwargs):
try:
self.nulecule.run(cli_provider, dryrun)
except Exception as e:
logger.error('Application run error: %s' % e)
logger.debug('Nulecule run error: %s' % e, exc_info=True)
logger.info('Rolling back changes')
self.stop(cli_provider, ignore_errors=True, **kwargs)

def stop(self, cli_provider, ignore_errors=False, **kwargs):
"""
Stops a running Nulecule application.
Args:
cli_provider (str): Provider running the Nulecule application
ignore_errors (bool): Ignore errors, if any, when True
kwargs (dict): Extra keyword arguments
"""
# For stop we use the generated answer file from the run
Expand All @@ -260,7 +269,7 @@ def stop(self, cli_provider, **kwargs):
self.app_path, config=self.answers, dryrun=dryrun)
self.nulecule.load_config(config=self.answers)
self.nulecule.render(cli_provider, dryrun=dryrun)
self.nulecule.stop(cli_provider, dryrun)
self.nulecule.stop(cli_provider, dryrun, ignore_errors)

def uninstall(self):
# For future use
Expand Down
1 change: 1 addition & 0 deletions atomicapp/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(self, config, path, dryrun):
self.config = config
self.path = path
self.dryrun = dryrun
self.ignore_errors = False
if Utils.getRoot() == HOST_DIR:
self.container = True

Expand Down
6 changes: 5 additions & 1 deletion atomicapp/providers/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,8 @@ def undeploy(self):
if self.dryrun:
logger.info("DRY-RUN: STOPPING CONTAINER %s", " ".join(cmd))
else:
subprocess.check_call(cmd)
try:
subprocess.check_call(cmd)
except Exception as e:
if not self.ignore_errors:
raise e
8 changes: 6 additions & 2 deletions atomicapp/providers/kubernetes.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,12 @@ def _call(self, cmd):
if self.dryrun:
logger.info("DRY-RUN: %s", " ".join(cmd))
else:
ec, stdout, stderr = Utils.run_cmd(cmd, checkexitcode=True)
return stdout
try:
ec, stdout, stderr = Utils.run_cmd(cmd, checkexitcode=True)
return stdout
except Exception as e:
if not self.ignore_errors:
raise e

def process_k8s_artifacts(self):
"""Processes Kubernetes manifests files and checks if manifest under
Expand Down

0 comments on commit 98c7928

Please sign in to comment.