Skip to content
This repository was archived by the owner on Jan 19, 2018. It is now read-only.
Open
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
8 changes: 5 additions & 3 deletions atomicapp/nulecule/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,22 +160,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)

def load_config(self, config=None, ask=False, skip_asking=False):
"""
Expand Down Expand Up @@ -290,7 +291,7 @@ def run(self, provider_key, dryrun=False):
provider.init()
provider.run()

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 @@ -300,6 +301,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.stop()

def load_config(self, config=None, ask=False, skip_asking=False):
Expand Down
16 changes: 13 additions & 3 deletions atomicapp/nulecule/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,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 @@ -241,12 +241,22 @@ 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in this case we still need to error out after the "stop" has been performed. Otherwise the application returns a good exit code and the user might not realize that it didn't fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dustymabe pushed fix!

raise NuleculeException('Rolled back changes.')

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 @@ -258,7 +268,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 clean(self, force=False):
# 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 stop(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
3 changes: 2 additions & 1 deletion atomicapp/providers/marathon.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ def stop(self):
msg = "Error deleting app: %s, Marathon API response %s - %s" % (
artifact["id"], status_code, return_data)
logger.error(msg)
raise ProviderFailedException(msg)
if not self.ignore_errors:
raise ProviderFailedException(msg)

def _process_artifacts(self):
""" Parse and validate Marathon artifacts
Expand Down
6 changes: 5 additions & 1 deletion atomicapp/providers/openshift.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,11 @@ def stop(self):
if self.dryrun:
logger.info("DRY-RUN: DELETE %s", url)
else:
self.oc.delete(url)
try:
self.oc.delete(url)
except Exception as e:
if not self.ignore_errors:
raise e
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the self.oc.scale() above (line 459) will fail if the rc doesn't exist in the target. We may need to consider putting the try/except deeper in the code. One extreme would be to put it at the Utils.make_rest_request() level, the other extreme would be to simply try/except around the scale call above. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this may be the only part @rtnpro you should have a look at, other code LGTM.


def _process_artifacts(self):
"""
Expand Down
7 changes: 5 additions & 2 deletions tests/units/nulecule/test_nulecule.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@ class TestNuleculeStop(unittest.TestCase):
def test_stop(self):
provider = 'docker'
dryrun = False
ignore_errors = False
mock_component_1 = mock.Mock()
mock_component_2 = mock.Mock()

n = Nulecule('some-id', '0.0.2', {}, [], 'some/path')
n.components = [mock_component_1, mock_component_2]
n.stop(provider)

mock_component_1.stop.assert_called_once_with(provider, dryrun)
mock_component_2.stop.assert_called_once_with(provider, dryrun)
mock_component_1.stop.assert_called_once_with(
provider, dryrun, ignore_errors)
mock_component_2.stop.assert_called_once_with(
provider, dryrun, ignore_errors)


class TestNuleculeLoadConfig(unittest.TestCase):
Expand Down