Skip to content

Commit 3ef9939

Browse files
committed
Implement atomic deployments for Nulecule application. Fixes projectatomic#421
When there's an error during running a Nulecule application, rollback the changes made by stopping the application.
1 parent 81a4cdb commit 3ef9939

File tree

5 files changed

+30
-12
lines changed

5 files changed

+30
-12
lines changed

atomicapp/nulecule/base.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,22 +135,23 @@ def run(self, provider_key=None, dryrun=False):
135135
for component in self.components:
136136
component.run(provider_key, dryrun)
137137

138-
def stop(self, provider_key=None, dryrun=False):
138+
def stop(self, provider_key=None, dryrun=False, ignore_errors=False):
139139
"""
140140
Stop the Nulecule application.
141141
142142
Args:
143143
provider_key (str): Provider to use for running Nulecule
144144
application
145145
dryrun (bool): Do not make changes to host when True
146+
ignore_errors (bool): Ignore errors, if any, when True
146147
147148
Returns:
148149
None
149150
"""
150151
provider_key, provider = self.get_provider(provider_key, dryrun)
151152
# stop the Nulecule application
152153
for component in self.components:
153-
component.stop(provider_key, dryrun)
154+
component.stop(provider_key, dryrun, ignore_errors)
154155

155156
def uninstall(self):
156157
# uninstall the Nulecule application
@@ -264,7 +265,7 @@ def run(self, provider_key, dryrun=False):
264265
provider.init()
265266
provider.deploy()
266267

267-
def stop(self, provider_key=None, dryrun=False):
268+
def stop(self, provider_key=None, dryrun=False, ignore_errors=False):
268269
"""
269270
Stop the Nulecule component with the specified provider.
270271
"""
@@ -274,7 +275,7 @@ def stop(self, provider_key=None, dryrun=False):
274275
provider_key, provider = self.get_provider(provider_key, dryrun)
275276
provider.artifacts = self.rendered_artifacts.get(provider_key, [])
276277
provider.init()
277-
provider.undeploy()
278+
provider.undeploy(ignore_errors)
278279

279280
def load_config(self, config=None, ask=False, skip_asking=False):
280281
"""

atomicapp/nulecule/main.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def run(self, cli_provider, answers_output, ask,
192192

193193
self.nulecule.load_config(config=self.nulecule.config, ask=ask)
194194
self.nulecule.render(cli_provider, dryrun)
195-
self.nulecule.run(cli_provider, dryrun)
195+
196196
runtime_answers = self._get_runtime_answers(
197197
self.nulecule.config, cli_provider)
198198
self._write_answers(
@@ -202,12 +202,21 @@ def run(self, cli_provider, answers_output, ask,
202202
self._write_answers(answers_output, runtime_answers,
203203
self.answers_format)
204204

205-
def stop(self, cli_provider, **kwargs):
205+
try:
206+
self.nulecule.run(cli_provider, dryrun)
207+
except Exception as e:
208+
logger.error('Application run error: %s' % e)
209+
logger.debug('Nulecule run error: %s' % e, exc_info=True)
210+
logger.info('Rolling back changes')
211+
self.stop(cli_provider, ignore_errors=True, **kwargs)
212+
213+
def stop(self, cli_provider, ignore_errors=False, **kwargs):
206214
"""
207215
Stops a running Nulecule application.
208216
209217
Args:
210218
cli_provider (str): Provider running the Nulecule application
219+
ignore_errors (bool): Ignore errors, if any, when True
211220
kwargs (dict): Extra keyword arguments
212221
"""
213222
# For stop we use the generated answer file from the run
@@ -219,7 +228,7 @@ def stop(self, cli_provider, **kwargs):
219228
self.app_path, config=self.answers, dryrun=dryrun)
220229
self.nulecule.load_config(config=self.answers)
221230
self.nulecule.render(cli_provider, dryrun=dryrun)
222-
self.nulecule.stop(cli_provider, dryrun)
231+
self.nulecule.stop(cli_provider, dryrun, ignore_errors)
223232

224233
def uninstall(self):
225234
# For future use

atomicapp/plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def checkConfigFile(self):
8888
"[general] section of the answers.conf file."
8989
% (self.config_file, PROVIDER_CONFIG_KEY))
9090

91-
def undeploy(self):
91+
def undeploy(self, ignore_errors=False):
9292
logger.warning(
9393
"Call to undeploy for provider %s failed - this action is not implemented",
9494
self.key)

atomicapp/providers/docker.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def deploy(self):
103103
else:
104104
subprocess.check_call(cmd)
105105

106-
def undeploy(self):
106+
def undeploy(self, ignore_errors=False):
107107
logger.info("Undeploying to provider: Docker")
108108
artifact_names = list()
109109

@@ -132,4 +132,8 @@ def undeploy(self):
132132
if self.dryrun:
133133
logger.info("DRY-RUN: STOPPING CONTAINER %s", " ".join(cmd))
134134
else:
135-
subprocess.check_call(cmd)
135+
try:
136+
subprocess.check_call(cmd)
137+
except Exception as e:
138+
if not ignore_errors:
139+
raise e

atomicapp/providers/kubernetes.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def deploy(self):
183183
cmd.append("--kubeconfig=%s" % self.config_file)
184184
self._call(cmd)
185185

186-
def undeploy(self):
186+
def undeploy(self, ignore_errors=False):
187187
"""Undeploys the app by given resource manifests.
188188
Undeploy operation first scale down the replicas to 0 and then deletes
189189
the resource from cluster.
@@ -203,4 +203,8 @@ def undeploy(self):
203203
cmd = [self.kubectl, "delete", "-f", path, "--namespace=%s" % self.namespace]
204204
if self.config_file:
205205
cmd.append("--kubeconfig=%s" % self.config_file)
206-
self._call(cmd)
206+
try:
207+
self._call(cmd)
208+
except Exception as e:
209+
if not ignore_errors:
210+
raise e

0 commit comments

Comments
 (0)