Merge lp:~jml/juju-jitsu/deploy-one into lp:juju-jitsu

Proposed by Jonathan Lange
Status: Needs review
Proposed branch: lp:~jml/juju-jitsu/deploy-one
Merge into: lp:juju-jitsu
Diff against target: 236 lines (+202/-1)
3 files modified
sub-commands/Makefile.am (+2/-0)
sub-commands/deploy-one (+200/-0)
sub-commands/deploy-to (+0/-1)
To merge this branch: bzr merge lp:~jml/juju-jitsu/deploy-one
Reviewer Review Type Date Requested Status
Clint Byrum (community) Needs Fixing
Review via email: mp+128061@code.launchpad.net

Commit message

Add deploy-one subcommand. Deploys one instance of a service.

Description of the change

Hello!

A thing I do a lot when developing charms is this:

  juju deploy $CHARM_NAME
  jitsu watch $SERVICE_NAME --x-state pending --num-units 1
  juju status

And then in another terminal:

  juju debug-log

Because I was raised by humans, I've acquired their trait of being easily
bored by doing the same things over and over.

This branch adds a command that does pretty much all of that:

  jitsu deploy-one --debug $CHARM_NAME

Combined with tools like 'alert' or <http://mumak.net/undistract-me>, this
takes much of the pain out of iterating on charms.

Known issues:

 - Figuring out the default service name from the charm name is
   somewhat kludgy.

 - Always exits with 0, even if the charm did not deploy

cheers,
jml

To post a comment you must log in.
Revision history for this message
Clint Byrum (clint-fewbar) wrote :

Actually there is one issue, please fix. Add deploy-one to sub-commands/Makefile.am

review: Needs Fixing
lp:~jml/juju-jitsu/deploy-one updated
84. By Jonathan Lange

Add deploy-one to sub-commands.

85. By Jonathan Lange

Dicking about with documentation.

Revision history for this message
Jonathan Lange (jml) wrote :

Have fixed sub-commands/Makefile.am. I tried to make the --help command work better, but I couldn't actually get _any_ --help to work when run for my branch.

cheers,
jml

Revision history for this message
Clint Byrum (clint-fewbar) wrote :

  File "sub-commands/deploy-one", line 40
    description="deploy a single unit of a service"))
                                                    ^
SyntaxError: invalid syntax

and when that is addressed

Traceback (most recent call last):
  File "sub-commands/deploy-one", line 201, in <module>
    main()
  File "sub-commands/deploy-one", line 179, in main
    parser = get_parser()
  File "sub-commands/deploy-one", line 38, in get_parser
    parser = ArgumentParser(
NameError: global name 'ArgumentParser' is not defined

And when *that* is addressed..

Traceback (most recent call last):
  File "sub-commands/deploy-one", line 201, in <module>
    main()
  File "sub-commands/deploy-one", line 179, in main
    parser = get_parser()
  File "sub-commands/deploy-one", line 40, in get_parser
    description="deploy a single unit of a service")
TypeError: __init__() got an unexpected keyword argument 'help'

review: Needs Fixing
lp:~jml/juju-jitsu/deploy-one updated
86. By Jonathan Lange

Fix a bunch of trivial syntax errors

Revision history for this message
Jonathan Lange (jml) wrote :

On 9 October 2012 18:53, Clint Byrum <email address hidden> wrote:
> Review: Needs Fixing
>
> File "sub-commands/deploy-one", line 40
> description="deploy a single unit of a service"))
> ^
> SyntaxError: invalid syntax
>
> and when that is addressed
>
> Traceback (most recent call last):
> File "sub-commands/deploy-one", line 201, in <module>
> main()
> File "sub-commands/deploy-one", line 179, in main
> parser = get_parser()
> File "sub-commands/deploy-one", line 38, in get_parser
> parser = ArgumentParser(
> NameError: global name 'ArgumentParser' is not defined
>
> And when *that* is addressed..
>
> Traceback (most recent call last):
> File "sub-commands/deploy-one", line 201, in <module>
> main()
> File "sub-commands/deploy-one", line 179, in main
> parser = get_parser()
> File "sub-commands/deploy-one", line 40, in get_parser
> description="deploy a single unit of a service")
> TypeError: __init__() got an unexpected keyword argument 'help'
>
> --
> https://code.launchpad.net/~jml/juju-jitsu/deploy-one/+merge/128061
> You are the owner of lp:~jml/juju-jitsu/deploy-one.

All three fixed. Sorry about the cock-up, I think it was due to a
poorly-reverted attempt to integrate better with --help.

jml

Unmerged revisions

86. By Jonathan Lange

Fix a bunch of trivial syntax errors

85. By Jonathan Lange

Dicking about with documentation.

84. By Jonathan Lange

Add deploy-one to sub-commands.

83. By Jonathan Lange

More docs & sanity checking.

82. By Jonathan Lange

Dump status at the end.

81. By Jonathan Lange

Tidy up a little. Don't show debug log by default

80. By Jonathan Lange

First cut of deploy-one.

79. By Jonathan Lange

Flakes

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'sub-commands/Makefile.am'
2--- sub-commands/Makefile.am 2012-08-09 16:40:53 +0000
3+++ sub-commands/Makefile.am 2012-10-19 11:07:24 +0000
4@@ -3,6 +3,7 @@
5 EXTRA_DIST = aiki \
6 capfile \
7 deploy \
8+ deploy-one \
9 deploy-to \
10 get-service-info \
11 get-unit-info \
12@@ -20,6 +21,7 @@
13
14 subcommandsexec_SCRIPTS = capfile \
15 deploy \
16+ deploy-one \
17 deploy-to \
18 get-service-info \
19 get-unit-info \
20
21=== added file 'sub-commands/deploy-one'
22--- sub-commands/deploy-one 1970-01-01 00:00:00 +0000
23+++ sub-commands/deploy-one 2012-10-19 11:07:24 +0000
24@@ -0,0 +1,200 @@
25+#!/usr/bin/python
26+#
27+# deploy-one - deploy one instance of a service
28+# author - Jonathan M. Lange <jml@mumak.net>
29+
30+# Copyright 2012 Canonical Ltd. All Rights Reserved
31+#
32+# This program is free software: you can redistribute it and/or modify
33+# it under the terms of the GNU Affero General Public License as published by
34+# the Free Software Foundation, either version 3 of the License, or
35+# (at your option) any later version.
36+#
37+# This program is distributed in the hope that it will be useful,
38+# but WITHOUT ANY WARRANTY; without even the implied warranty of
39+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40+# GNU Affero General Public License for more details.
41+#
42+# You should have received a copy of the GNU Affero General Public License
43+# along with this program. If not, see <http://www.gnu.org/licenses/>.
44+
45+"""deploy-one - deploy one instance of a service
46+
47+Primarily intended for use while developing charms. This command will open a
48+tunnel, deploy a service, (optionally) show the debug log, and then terminate
49+when the service has either deployed or failed to deploy.
50+"""
51+
52+import argparse
53+from contextlib import contextmanager
54+import os
55+import subprocess
56+import sys
57+
58+from juju.control.utils import expand_path
59+
60+
61+def get_parser():
62+ parser = argparse.ArgumentParser(
63+ description="deploy a single unit of a service")
64+
65+ parser.add_argument(
66+ "--environment", "-e",
67+ default=os.environ.get("JUJU_ENV"),
68+ help="Environment to deploy the charm in.")
69+
70+ parser.add_argument(
71+ "--repository",
72+ help="Directory for charm lookup and retrieval",
73+ default=os.environ.get("JUJU_REPOSITORY"),
74+ type=expand_path)
75+
76+ parser.add_argument(
77+ "--config",
78+ help="YAML file containing service options")
79+
80+ parser.add_argument(
81+ "--debug", action="store_true",
82+ help="Whether or not to show the debug log")
83+
84+ parser.add_argument(
85+ "charm", nargs=None,
86+ help="Charm name")
87+
88+ parser.add_argument(
89+ "service_name", nargs="?",
90+ help="Service name of deployed charm")
91+
92+ return parser
93+
94+
95+class NullProcess(object):
96+
97+ def terminate(self):
98+ pass
99+
100+
101+def _sanity_check_subprocess(p):
102+ # Check that 'p' has printed a line to stdout and is still running.
103+ # Otherwise, raise an exception with its stdout.
104+ line = p.stderr.readline()
105+ if not line:
106+ raise RuntimeError(p.stdout.read())
107+ if p.poll() is not None:
108+ raise RuntimeError("Process terminated:\n%s" % (p.stderr.read(),))
109+
110+
111+def open_tunnel(environment):
112+ """Run 'juju open-tunnel'.
113+
114+ Use this to accelerate interaction between the client and the Juju
115+ environment.
116+ """
117+ cmd = ['juju', 'open-tunnel']
118+ if environment:
119+ cmd.extend(['--environment', environment])
120+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
121+ _sanity_check_subprocess(p)
122+ return p
123+
124+
125+def debug_log(environment, debug=True):
126+ """Run 'juju debug-log', forwarding output to our stdout."""
127+ if not debug:
128+ return NullProcess()
129+ cmd = ['juju', 'debug-log']
130+ if environment:
131+ cmd.extend(['--environment', environment])
132+ # stdout & stderr options here aren't necessary, but jml likes to be
133+ # explicit.
134+ return subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
135+
136+
137+def deploy(environment, repository, config, charm, service_name):
138+ # juju deploy --environment options.environment \
139+ # --repository options.repository \
140+ # --config options.config
141+ # charm [service_name]
142+ cmd = ['juju', 'deploy']
143+ if environment:
144+ cmd.extend(['--environment', environment])
145+ if repository:
146+ cmd.extend(['--repository', repository])
147+ if config:
148+ cmd.extend(['--config', config])
149+ cmd.extend([charm, service_name])
150+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
151+ out, err = p.communicate()
152+ if p.returncode:
153+ raise RuntimeError(err)
154+
155+
156+def watch_until_not_pending(environment, service_name):
157+ cmd = ['jitsu', 'watch']
158+ if environment:
159+ cmd.extend(['-e', environment])
160+ # Order matters here. service_name **must** be first.
161+ cmd.extend([
162+ service_name,
163+ '--x-state', 'pending',
164+ '--num-units', '1',
165+ ])
166+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
167+ p.communicate()
168+
169+
170+def dump_status(environment, scope=None, stream=None):
171+ """Dump status for an environment to 'stream'."""
172+ if stream is None:
173+ stream = sys.stdout
174+ cmd = ['juju', 'status']
175+ if environment:
176+ cmd.extend(['--environment', environment])
177+ if scope:
178+ cmd.append(scope)
179+ p = subprocess.Popen(cmd, stdout=stream, stderr=subprocess.STDOUT)
180+ p.communicate()
181+
182+
183+def get_service_name(charm, service_name=None):
184+ # XXX: Do I seriously have to do this? --- jml, 2012-10-04
185+ if service_name:
186+ return service_name
187+ if ':' in charm:
188+ return charm.split(':', 1)[1]
189+ return charm
190+
191+
192+@contextmanager
193+def terminating(process):
194+ try:
195+ yield
196+ finally:
197+ process.terminate()
198+
199+
200+def main():
201+ """Deploy a single unit of a service."""
202+ parser = get_parser()
203+ options = parser.parse_args()
204+
205+ environment = options.environment
206+ service_name = get_service_name(options.charm, options.service_name)
207+
208+ # Open a tunnel, deploy, start the debug log and then terminate once
209+ # deployment has finished (either successfully or not).
210+ with terminating(open_tunnel(environment)):
211+ deploy(
212+ environment, options.repository, options.config, options.charm,
213+ service_name)
214+ with terminating(debug_log(environment, options.debug)):
215+ watch_until_not_pending(environment, service_name)
216+
217+ dump_status(environment)
218+ # XXX: Always exit with 0, even if it failed. Should really return
219+ # non-zero when deploy failed.
220+ sys.exit(0)
221+
222+
223+if __name__ == '__main__':
224+ main()
225
226=== modified file 'sub-commands/deploy-to'
227--- sub-commands/deploy-to 2012-06-28 05:18:42 +0000
228+++ sub-commands/deploy-to 2012-10-19 11:07:24 +0000
229@@ -31,7 +31,6 @@
230 from juju.control.utils import (
231 expand_path, sync_environment_state)
232
233-from juju.charm.errors import ServiceConfigValueError
234 from juju.charm.publisher import CharmPublisher
235 from juju.charm.repository import resolve
236 from juju.environment.config import EnvironmentsConfig

Subscribers

People subscribed via source and target branches