Merge lp:~evilnick/juju-core/docs-amulet into lp:juju-core/docs
- docs-amulet
- Merge into docs
Proposed by
Nick Veitch
Status: | Merged |
---|---|
Merge reported by: | Nick Veitch |
Merged at revision: | not available |
Proposed branch: | lp:~evilnick/juju-core/docs-amulet |
Merge into: | lp:juju-core/docs |
Diff against target: |
526 lines (+499/-1) 2 files modified
htmldocs/css/main.css (+14/-1) htmldocs/tools-amulet.html (+485/-0) |
To merge this branch: | bzr merge lp:~evilnick/juju-core/docs-amulet |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
charmers | Pending | ||
Review via email: mp+201061@code.launchpad.net |
Commit message
Description of the change
added initial amulet docs
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'htmldocs/css/main.css' |
2 | --- htmldocs/css/main.css 2013-12-03 01:36:47 +0000 |
3 | +++ htmldocs/css/main.css 2014-01-09 17:59:35 +0000 |
4 | @@ -279,10 +279,22 @@ |
5 | } |
6 | |
7 | .doc-content h3{ |
8 | - font-size: 18px; |
9 | + font-size: 20px; |
10 | color: #5E2750; |
11 | font-weight: 800; |
12 | } |
13 | + |
14 | +/** |
15 | + * Foldout stuff |
16 | + */ |
17 | + |
18 | +.doc-content code.method { |
19 | + font-size: 14px; |
20 | + color: #5E2750; |
21 | + font-weight: 800; |
22 | + background-color: #ffffff |
23 | +} |
24 | + |
25 | /** |
26 | * Walkthrough steps |
27 | */ |
28 | @@ -482,6 +494,7 @@ |
29 | overflow-x: auto; |
30 | } |
31 | |
32 | + |
33 | hr { |
34 | background: #fff; |
35 | height: 1px; |
36 | |
37 | === added file 'htmldocs/tools-amulet.html' |
38 | --- htmldocs/tools-amulet.html 1970-01-01 00:00:00 +0000 |
39 | +++ htmldocs/tools-amulet.html 2014-01-09 17:59:35 +0000 |
40 | @@ -0,0 +1,485 @@ |
41 | +<!DOCTYPE html> |
42 | +<html> |
43 | +<!--Head--> |
44 | + <head> |
45 | + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
46 | + <title>Juju Documentation</title> |
47 | + <script src="/wp-content/themes/ubuntu/library/js/all-yui.js"></script> |
48 | + <link href="https://fonts.googleapis.com/css?family=Ubuntu:400,300,300italic,400italic,700,700italic|Ubuntu+Mono" rel="stylesheet" type="text/css"> |
49 | + <link rel="stylesheet" type="text/css" media="screen" href="https://juju.ubuntu.com/wp-content/themes/juju-website/css/reset.css"> |
50 | + <link rel="shortcut icon" href="//assets.ubuntu.com/sites/ubuntu/latest/u/img/favicon.ico" /> |
51 | + <link rel="stylesheet" type="text/css" media="screen" href="//assets.ubuntu.com/sites/guidelines/css/latest/ubuntu-styles.css" /> |
52 | + <link rel="stylesheet" type="text/css" media="screen" href="//assets.ubuntu.com/sites/ubuntu/latest/u/css/global.css" /> |
53 | + <link rel="stylesheet" type="text/css" media="screen" href="https://juju.ubuntu.com/wp-content/themes/juju-website/css/960.css"> |
54 | + <link rel="stylesheet" type="text/css" media="screen" href="https://juju.ubuntu.com/wp-content/themes/juju-website/css/home-new.css"> |
55 | + <link rel="stylesheet" id="stacktack-css" href="https://juju.ubuntu.com/wp-content/plugins/stacktack/css/stacktack.min.css?ver=3.4.2" type="text/css" media="all"> |
56 | + <link href="./css/main.css" rel="stylesheet" type="text/css"> |
57 | + |
58 | + <!--[if lt IE 9]> |
59 | + <script type="text/javascript" src="//html5shim.googlecode.com/svn/trunk/html5.js"></script> |
60 | + <![endif]--> |
61 | + </head> |
62 | +<!--End-Head--> |
63 | + |
64 | + |
65 | + |
66 | + |
67 | + |
68 | + |
69 | + |
70 | + <body class="resources"> |
71 | + |
72 | +<!--Header--> |
73 | + |
74 | + <header class="banner global" role="banner"> |
75 | + <nav role="navigation" class="nav-primary nav-right"> |
76 | + <div class="logo"> |
77 | + <a class="logo-ubuntu" href="https://juju.ubuntu.com/"> |
78 | + <img width="118" height="27" src="//assets.ubuntu.com/sites/ubuntu/latest/u/img/logo.png" alt="Juju logo" /> |
79 | + <span>Juju</span> |
80 | + </a> |
81 | + </div> |
82 | + <ul> |
83 | + <li class="accessibility-aid"><a accesskey="s" href="#main-content">Jump to content</a></li> |
84 | + <li class="page_item page-item-8"><a href="https://juju.ubuntu.com/charms/">Charms</a></li> |
85 | + <li class="page_item page-item-10"><a href="https://juju.ubuntu.com/features/">Features</a></li> |
86 | + <li class="page_item page-item-12"><a href="https://juju.ubuntu.com/deployment/">Deploy</a></li> |
87 | + <li class="page_item page-item-14"><a href="https://juju.ubuntu.com/resources/">Resources</a></li> |
88 | + <li class="page_item page-item-16"><a href="https://juju.ubuntu.com/community/">Community</a></li> |
89 | + <li class="page_item page-item-18"><a href="https://juju.ubuntu.com/download/">Install Juju</a></li> |
90 | + </ul> |
91 | + <div id="box-search"> |
92 | + <form class="search-form" method="get" id="searchform" action="https://juju.ubuntu.com/"> |
93 | + <label class="off-left" for="s">Search:</label> |
94 | + <input class="form-text" type="text" value="" name="s" id="s" /> |
95 | + <button class="off-left form-submit" type="submit" id="searchsubmit">Search</button> |
96 | + </form> |
97 | + </div> |
98 | + </nav> |
99 | + </header> |
100 | +<!--End-Header--> |
101 | +<!--Preamble--> |
102 | +<div class="wrapper"> |
103 | + <div id="main-content" class="inner-wrapper" role="main"> |
104 | + <div class="row no-border"> |
105 | + <div class="header-navigation-secondary"></div> |
106 | + <h2 class="pagetitle">Juju documentation</h2> |
107 | + </div> |
108 | + <section id="content" class="container-12"> |
109 | + <div class="grid-12 doc-container"> |
110 | + <div id="navlinks" class="grid-3 doc-navigation">LINKS</div> |
111 | + <div class="grid-9 doc-content"> |
112 | +<!--End-Preamble--> |
113 | + <article> |
114 | + <section id="amulet"> |
115 | + <h1>Amulet, a testing harness</h1> |
116 | +<p>Amulet is a set of tools designed to simplify the testing process for charm authors. Amulet aims to be:</p> |
117 | +<ul> |
118 | +<li>a testing harness for writing and running tests.</li> |
119 | +<li>a way to validate charm relation data (not just what a charm expects/receives).</li> |
120 | +<li>a method to exercise and test charm relations outside of a deployment.</li> |
121 | + </ul> |
122 | +<p>While these tools are designed to help make test writing easier, much like charm helpers are designed to make hook writing easier, they are not required to write tests for charms. This library is offered as a completely optional set of tools for you to use.</p> |
123 | +</section> |
124 | +<section id="install"> |
125 | + |
126 | + <h1 >Installation</h1> |
127 | + <p>Amulet is available as both a package and via pip. For source packages, see <a href="https://github.com/marcoceppi/amulet/releases"> GitHub</a>.</p> |
128 | + <section class="code-example code-overflow"> |
129 | + <nav class="control"> |
130 | + <a href="." class="selected" data-action="ubuntu">Ubuntu</a> |
131 | + <a href="." class="" data-action="macosx">Mac OSX</a> |
132 | + <a href="." class="" data-action="windows">Windows</a> |
133 | + <a href="." class="" data-action="source">Source</a> |
134 | + </nav> |
135 | + <div class="terminal-wrap"> |
136 | + <div data-section="ubuntu"> |
137 | + <p>Amulet is available in the Juju Stable PPA for Ubuntu</p> |
138 | + |
139 | +<pre class="prettyprint">sudo add-apt-repository ppa:juju/stable |
140 | +sudo apt-get update |
141 | +sudo apt-get install amulet |
142 | +</pre> |
143 | + </div> |
144 | + <div data-section="macosx"> |
145 | + <p>Amulet is available via Pip:</p> |
146 | + |
147 | +<pre class="prettyprint lang-bash">sudo pip install amulet</pre> |
148 | + |
149 | + </div> |
150 | + <div data-section="windows"> |
151 | + <p>Amulet is available via Pip:</p> |
152 | + |
153 | +<pre class="prettyprint lang-bash">pip install amulet</pre> |
154 | + </div> |
155 | + <div data-section="source"> |
156 | + <p>Amulet is built with Python3, make sure it's installed prior to following these steps. While you can run Amulet from source, it's not recommended as it requires several changes to environment variables in order for Amulet to operate as it does in the packaged version.</p> |
157 | + |
158 | +<p>To install Amulet from source, first get the source:</p> |
159 | + |
160 | +<pre class="prettyprint lang-bash">git clone https://github.com/marcoceppi/amulet.git</pre> |
161 | +<p>Move in to the <code>amulet</code> directory and run |
162 | +<pre class="prettyprint lang-bash">sudo python3 setup.py install</pre> |
163 | +<p>You can also access the Python libraries; however, your <code>PYTHONPATH</code> will need to be amended in order for it to find the amulet directory.</p> |
164 | + |
165 | + </div> |
166 | + </div> |
167 | + </section> |
168 | + |
169 | + |
170 | + |
171 | + |
172 | +<section> |
173 | +<h2 id="usage">Usage</h2> |
174 | + |
175 | +<p>Amulet comes packaged with several tools. In order to provide the most flexibility, Amulet offers both direct Python library access and generic access via a programmable API for other languages (for example, <code>bash</code>).</p> |
176 | + |
177 | +<h3>Python</h3> |
178 | + |
179 | +<p>Amulet is made available to Python via the <code>amulet</code> module which you can import:</p> |
180 | + |
181 | +<pre class="prettyprint lang-python">import amulet</pre> |
182 | +<p>The amulet module seeds each module/command directly, so Deployment is made available in amulet/deployer.py and is accessible directly from amulet using:</p> |
183 | + |
184 | +<pre class="prettyprint lang-python">from amulet import Deployment</pre> |
185 | +<p>Though <code>deployer</code> is also available in the event you wish to execute any of the helper functions:</p> |
186 | +<pre class="prettyprint lang-python"> |
187 | +from amulet import deployer |
188 | +d = deployer.Deployment() |
189 | +</pre> |
190 | + |
191 | +<h3 id="api">Programmable API</h3> |
192 | + |
193 | +<p>A limited number of functions are made available through a generic forking API. The following examples assume you're using a BOURNE Shell, though this syntax could be used from within other languauges with the same expected results.</p> |
194 | + |
195 | +<p>Unlike the Python modules, only some of the functions of Amulet are available through this API, though efforts are being made to make the majority of the core functionality available.</p> |
196 | + |
197 | +<p>This API follows the subcommand workflow, much like Git or Bazaar. Amulet makes an amulet command available and each function is tied to a sub-command. To mimic the Python example you can create a a new Deployment by issuing the following command:</p> |
198 | + |
199 | +<pre class="prettyprint lang-bash">amulet deployment</pre> |
200 | + |
201 | +<p>Depending on the syntax and worflow for each function you can expect to provide either additional sub-commands, command-line flags, or a combination of the two.</p> |
202 | + |
203 | +<h2 id="functionality">Core functionality</h2> |
204 | +<p>This section is deigned to outline the core functions of Amulet. Again, please refer to the developer documentation for an exhaustive list of functions and methods. |
205 | + |
206 | +<h3>amulet.deployer</h3> |
207 | + |
208 | +<p>The Deployer module houses several classes for interacting and setting up an environment. These classes and methods are outlined below |
209 | + |
210 | +amulet.deployer.Deployment() |
211 | + |
212 | +<p>Deployment (amulet deployment, from amulet import Deployment) is an abstraction layer to the juju-deployer Juju plugin and a service lifecycle management tool. It's designed to allow an author to describe their deployment in simple terms:</p> |
213 | +<pre class="prettyprint lang-python"> |
214 | +import amulet |
215 | + |
216 | +d = amulet.Deployment() |
217 | +d.add('mysql') |
218 | +d.add('mediawiki') |
219 | +d.relate('mysql:db', 'mediawiki:db') |
220 | +d.expose('mediawiki') |
221 | +d.configure('mediawiki', title="My Wiki", skin="Nostolgia") |
222 | +d.setup() |
223 | +</pre> |
224 | +<p> |
225 | +That information is then translated to a Juju Deployer deployment file then, finally, juju-deployer executes the described setup. Amulet strives to ensure it implements the correct version and syntax of Juju Deployer, to avoid charm authors having to potentially intervene each time an update to <code>juju-deployer</code> is made. |
226 | +</p> |
227 | +<p>Once an environment has been set up, deployer can still drive the environment outside of of juju-deployer. So the same commands (add, relate, configure, expose) will instead interact directly with the environment by using either the Juju API or the juju commands.</p> |
228 | + |
229 | +<h4 id="object">Class:</h4> |
230 | +<p><code>Deployment(juju_env=None, series='precise', sentries=True, juju_deployer='juju-deployer', sentry_template=None)</code></p> |
231 | + |
232 | +<h4>Methods:</h4> |
233 | +<details><summary><code class="method">Deployment.add(service, charm=None, units=1)</code> |
234 | + |
235 | +<p>Add a new service to the deployment schema.</p></summary> |
236 | + |
237 | +<ul><li><code>service</code> Name of the service to deploy.</li> |
238 | +<li><code>charm</code> If provided, will be the charm used. Otherwise service is used as the charm.</li> |
239 | +<li><code>units</code> Number of units to deploy.</li></ul> |
240 | +<pre class="prettyprint lang-python">import amulet |
241 | + |
242 | +d = amulet.Deployment() |
243 | +d.add('wordpress') |
244 | +d.add('second-wp', charm='wordpress') |
245 | +d.add('personal-wp', charm='~marcoceppi/wordpress', units=2) |
246 | +</pre> |
247 | +</details> |
248 | +<details><summary><code class="method">Deployment.build_relations()</code> |
249 | + |
250 | +<p>Private method invoked during deployer_map. Creates relation mapping.</p></summary></details> |
251 | + |
252 | +<details><summary><code class="method">Deployment.build_sentries()</code> |
253 | + |
254 | +<p>Private method invoked during deployer_map. Creates sentries for services.</p> |
255 | + </summary></details> |
256 | +<details><summary><code class="method">Deployment.configure(service, **options)</code> |
257 | + |
258 | +<p>Change configuration options for a service.</p></summary> |
259 | + |
260 | +<ul><li><code>service</code> The service to configure.</li> |
261 | +<li><code>**options</code> Seed with key=val.</li></ul> |
262 | +<pre class="prettyprint lang-python"> |
263 | +import amulet |
264 | + |
265 | +d = amulet.Deployment() |
266 | +d.add('postgresql') |
267 | +d.configure('postgresql', autovacuum=True, cluster_name='cname') |
268 | +</pre> |
269 | +</details> |
270 | +<details><summary><code class="method">Deployment.deployer_map(services, relations)</code> |
271 | + |
272 | +<p>Create deployer file from provided services and relations.</p> |
273 | +</summary> |
274 | + |
275 | +<ul><li><code>services</code> Object of service and service data.</li> |
276 | +<li><code>relations</code> List of relations to map.</li> |
277 | +</ul> |
278 | +</details> |
279 | +<details><summary><code class="method">Deployment.expose(service)</code> |
280 | + |
281 | +<p>Indicate if a service should be exposed after deployment.</p></summary> |
282 | + |
283 | +<ul><li><code>service</code> - Name of service to expose</li></ul> |
284 | +<pre class="prettyprint lang-python"> |
285 | +import amulet |
286 | + |
287 | +d = amulet.Deployment() |
288 | +d.add('varnish') |
289 | +d.expose('varnish') |
290 | +</pre></details> |
291 | +<details><summary><code class="method">Deployment.load(deploy_cfg)</code> |
292 | + |
293 | +<p>Import an existing deployer object.</p> |
294 | +</summary> |
295 | +<ul><li><code>deploy_cfg</code> Already parsed deployer yaml/json file.</li></ul> |
296 | +</details> |
297 | +<details><summary><code class="method">Deployment.relate(*args)</code> |
298 | + |
299 | +<p>Relate two services together.</p></summary> |
300 | + |
301 | +<ul><li><code>*args</code> - <code>service:relation</code> to be related.</li></ul> |
302 | +<p>If more than two arguments are given, it's assumed they're to be added to the first argument as a relation.</p> |
303 | +<pre class="prettyprint lang-python">import amulet |
304 | + |
305 | +d = amulet.Deployment() |
306 | +d.add('postgresql') |
307 | +d.add('mysql') |
308 | +d.add('wordpress') |
309 | +d.add('mediawiki') |
310 | +d.add('discourse') |
311 | + |
312 | +d.relate('postgresql:db-admin', 'discourse:db') |
313 | +d.relate('mysql:db', 'wordpress:db', 'mediawiki:database') |
314 | +</pre> |
315 | +</details> |
316 | + |
317 | +<details><summary><code class="method">Deployment.setup(timeout=600)</code> |
318 | + |
319 | +<p>This will create the deployer mapping, create any sentries that are required, and execute juju-deployer with the generated mapping.</p></summary> |
320 | + |
321 | +<ul><li><code>timeout</code> in seconds, how long to wait for setup</li></ul> |
322 | + |
323 | +<pre class="prettyprint lang-python">import amulet |
324 | + |
325 | +d = amulet.Deployment() |
326 | +d.add('wordpress') |
327 | +d.add('mysql') |
328 | +d.configure('wordpress', debug=True) |
329 | +d.relate('wordpress:db', 'mysql:db') |
330 | +try: |
331 | + d.setup(timeout=900) |
332 | +except amulet.helpers.TimeoutError: |
333 | + # Setup didn't complete before timeout |
334 | + pass |
335 | + |
336 | +</pre></details> |
337 | +<h3>amulet.sentry</h3> |
338 | +<p>Sentries are an additional service built in to the Deployment tool which allow an author the ability to dig deeper in to a deployment environment. This is done by adding a set of tools to each service/unit deployed via a subordinate charm and a final "relation sentry" charm is deployed which all relations are proxied through. In doing so you can inspect on each service/unit deployed as well as receive detailed information about what data is being sent by which units/service during a relation.</p> |
339 | + |
340 | +<p>Sentries can be accessed from within your deployment using the sentry object. Using the above example from ## Deployer, each service and unit can be accessed using the following:</p> |
341 | +<pre class="prettyprint lang-python"> |
342 | +import amulet |
343 | + |
344 | +d = amulet.Deployment() |
345 | +d.add('mediawiki') |
346 | +d.add('mysql') |
347 | +d.setup() |
348 | + |
349 | +d.sentry.unit['mysql/0'] |
350 | +d.sentry.unit['mediawiki/0'] |
351 | +</pre> |
352 | +<p>Sentries provide several methods for which you can use to gather information about an environment. The following are a few examples.</p> |
353 | + |
354 | +<h2 id="examples">Examples</h2> |
355 | +<p>Here are a few examples of Amulet tests</p> |
356 | + |
357 | +<h3>WordPress</h3> |
358 | + |
359 | +<h4>tests/00-setup</h4> |
360 | +<pre class="prettyprint lang-python"> |
361 | +#!/bin/bash |
362 | + |
363 | +sudo apt-get install install amulet python-requests |
364 | +</pre> |
365 | +<h4>tests/01-simple</h4> |
366 | +<pre class="prettyprint lang-python"> |
367 | +import os |
368 | +import amulet |
369 | +import requests |
370 | + |
371 | +from .lib import helper |
372 | + |
373 | +d = amulet.Deployment() |
374 | +d.add('mysql') |
375 | +d.add('wordpress') |
376 | +d.relate('mysql:db', 'wordpress:db') |
377 | +d.expose('wordpress') |
378 | + |
379 | +try: |
380 | + # Create the deployment described above, give us 900 seconds to do it |
381 | + d.setup(timeout=900) |
382 | + # Setup will only make sure the services are deployed, related, and in a |
383 | + # "started" state. We can employ the sentries to actually make sure there |
384 | + # are no more hooks being executed on any of the nodes. |
385 | + d.sentry.wait() |
386 | +except amulet.helpers.TimeoutError: |
387 | + amulet.raise_status(amulet.SKIP, msg="Environment wasn't stood up in time") |
388 | +except: |
389 | + # Something else has gone wrong, raise the error so we can see it and this |
390 | + # will automatically "FAIL" the test. |
391 | + raise |
392 | + |
393 | +# Shorten the names a little to make working with unit data easier |
394 | +wp_unit = d.sentry.unit['wordpress/0'] |
395 | +mysql_unit = d.sentry.unit['mysql/0'] |
396 | + |
397 | +# WordPress requires user input to "finish" a setup. This code is contained in |
398 | +# the helper.py file found in the lib directory. If it's not able to complete |
399 | +# the WordPress setup we need to quit the test, not as failed per se, but as a |
400 | +# SKIPed test since we can't accurately setup the environment |
401 | +try: |
402 | + helper.finish_setup(wp_unit.info['public-address'], password='amulet-test') |
403 | +except: |
404 | + amulet.raise_status(amulet.SKIP, msg="Unable to finish WordPress setup") |
405 | + |
406 | +home_page = requests.get('http://%s/' % wp_unit.info['public-address']) |
407 | +home_page.raise_for_status() # Make sure it's not 5XX error |
408 | +</pre> |
409 | +<h4>tests/lib/helper.py</h4> |
410 | +<pre class="prettyprint lang-python"> |
411 | +import requests |
412 | + |
413 | +def finish_setup(unit, user='admin', password=None): |
414 | + h = {'User-Agent': 'Mozilla/5.0 Gecko/20100101 Firefox/12.0', |
415 | + 'Content-Type': 'application/x-www-form-urlencoded', |
416 | + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*', |
417 | + 'Accept-Encoding': 'gzip, deflate'} |
418 | + |
419 | + r = requests.post('http://%s/wp-admin/install.php?step=2' % unit, |
420 | + headers=h, data={'weblog_title': 'Amulet Test %s' % unit, |
421 | + 'user_name': user, 'admin_password': password, |
422 | + 'admin_email': 'test@example.tld', |
423 | + 'admin_password2': password, |
424 | + 'Submit': 'Install WordPress'}) |
425 | +</pre> |
426 | + |
427 | + |
428 | + |
429 | + </section> |
430 | + </article> |
431 | +<!--Postamble--> |
432 | + </div> |
433 | + </div> |
434 | + </section> |
435 | + </div> |
436 | +</div> |
437 | +<!--End-Postamble--> |
438 | +<!--Footer--> |
439 | + <footer class="global clearfix" role="contentinfo"> |
440 | + <nav role="navigation"> |
441 | + <div class="footer-a"> |
442 | + <div class="clearfix"> |
443 | + <ul> |
444 | + <li> |
445 | + <h2><a href="/">Juju</a></h2> |
446 | + <ul> |
447 | + <li><a href="/charms">Charms</a></li> |
448 | + <li><a href="/features">Features</a></li> |
449 | + <li><a href="/deployment">Deployment</a></li> |
450 | + </ul> |
451 | + </li> |
452 | + <li> |
453 | + <h2><a href="/resources">Resources</a></h2> |
454 | + <ul> |
455 | + <li><a href="/resources/juju-overview/">Overview</a></li> |
456 | + <li><a href="/docs/">Documentation</a></li> |
457 | + <li><a href="/resources/the-juju-gui/">The Juju web UI</a></li> |
458 | + <li><a href="/docs/authors-charm-store.html">The charm store</a></li> |
459 | + <li><a href="/docs/getting-started.html#test">Tutorial</a></li> |
460 | + <li><a href="/resources/videos/">Videos</a></li> |
461 | + <li><a href="/resources/easy-tasks-for-new-developers/">Easy tasks for new developers</a></li> |
462 | + </ul> |
463 | + </li> |
464 | + <li> |
465 | + <h2><a href="/community">Community</a></h2> |
466 | + <ul> |
467 | + <li><a href="/community/blog/">Juju Blog</a></li> |
468 | + <li><a href="/events/">Events</a></li> |
469 | + <li><a href="/community/weekly-charm-meeting/">Weekly charm meeting</a></li> |
470 | + <li><a href="/community/charmers/">Charmers</a></li> |
471 | + <li><a href="/docs/authors-charm-writing.html">Write a charm</a></li> |
472 | + <li><a href="/docs/contributing.html">Help with documentation</a></li> |
473 | + <li><a href="https://bugs.launchpad.net/juju-core/+filebug">File a bug</a></li> <li><a href="/labs/">Juju Labs</a></li> |
474 | + </ul> |
475 | + </li> |
476 | + <li> |
477 | + <h2><a href="https://jujucharms.com/sidebar/">Try Juju</a></h2> |
478 | + <ul> |
479 | + <li><a href="https://jujucharms.com/">Charm store</a></li> |
480 | + <li><a href="/download/">Download Juju</a></li> |
481 | + </ul> |
482 | + </li> |
483 | + </ul> |
484 | + </div> |
485 | + </div> |
486 | + </nav> |
487 | + <div class="legal clearfix"> |
488 | + <p>© 2013 Canonical Ltd. Ubuntu and Canonical are registered trademarks of <a href="http://canonical.com">Canonical Ltd</a>.</p> |
489 | + </div> |
490 | + </footer> |
491 | + |
492 | +<!--End-Footer--> |
493 | + |
494 | + |
495 | +<!--Scripts--> |
496 | + <script src="//google-code-prettify.googlecode.com/svn/loader/run_prettify.js?skin=sunburst"></script> |
497 | + <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> |
498 | + <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"></script> |
499 | + <script src="//d38yea5fb4e2oh.cloudfront.net/jquery.stacktack.min.js"></script> |
500 | + <script type="text/javascript" src="//code.jquery.com/jquery-1.9.1.js"></script> |
501 | + <script type="text/javascript" src="./js/main.js"></script> |
502 | + <script type="text/javascript" src="./js/jquery.details.js"></script> |
503 | + <script src="//assets.ubuntu.com/sites/ubuntu/latest/u/js/core.js"></script> |
504 | + <script src="//assets.ubuntu.com/sites/ubuntu/latest/u/js/global.js"></script> |
505 | + <!-- google analytics --> |
506 | + <script> |
507 | + var _gaq = _gaq || []; |
508 | + _gaq.push(['_setAccount', 'UA-1018242-41']); |
509 | + _gaq.push(['_trackPageview']); |
510 | + |
511 | + (function() { |
512 | + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; |
513 | + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; |
514 | + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); |
515 | + })(); |
516 | + </script> |
517 | +<!--End-Scripts--> |
518 | + |
519 | + |
520 | + |
521 | + |
522 | + |
523 | + |
524 | +</body></html> |
525 | + |
526 | \ No newline at end of file |