Merge lp:~evilnick/juju-core/docs-new-walkthrough into lp:juju-core/docs

Proposed by Nick Veitch
Status: Merged
Merged at revision: 36
Proposed branch: lp:~evilnick/juju-core/docs-new-walkthrough
Merge into: lp:juju-core/docs
Diff against target: 665 lines (+267/-340)
3 files modified
htmldocs/authors-charm-writing.html (+264/-338)
htmldocs/charms-deploying.html (+2/-2)
skel/footer.tpl (+1/-0)
To merge this branch: bzr merge lp:~evilnick/juju-core/docs-new-walkthrough
Reviewer Review Type Date Requested Status
Marco Ceppi Pending
Review via email: mp+176833@code.launchpad.net

Commit message

added new charm writing walktrhough

Description of the change

Added a new walkthrough to replace the old charm-writing page. This is completely new material and should actually work, unlike the old one.

Please check that this works and makes sense.

I am aware there are still issues with the syntax colouring of the source files, but I'd really like to get a working example up as soon as possible

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/authors-charm-writing.html'
2--- htmldocs/authors-charm-writing.html 2013-07-15 13:01:43 +0000
3+++ htmldocs/authors-charm-writing.html 2013-07-25 01:29:31 +0000
4@@ -83,8 +83,9 @@
5 </ul>
6 <h1>Charm Authors</h1>
7 <ul></ul>
8+ <li class=""><a href="authors-charm-intro.html">Want to write a charm?</a></li>
9 <li class=""><a href="authors-charm-anatomy.html">Anatomy of a charm</a></li>
10- <li class=""><a href="authors-charm-writing.html">Writing a charm</a></li>
11+ <li class=""><a href="authors-charm-writing.html">Writing a Charm</a></li>
12 <li class=" sub"><a href="authors-subordinate-services.html">Subordinate services</a></li>
13 <li class=" sub"><a href="authors-implicit-relations.html">Implicit Relations</a></li>
14 <li class=" sub"><a href="authors-testing.html">Charm Testing</a></li>
15@@ -104,343 +105,267 @@
16 </div>
17 <div class="grid-9 doc-content">
18 <article>
19- <section id ="charm-writing">
20- <h1>Writing a charm</h1>
21- <p>This tutorial demonstrates the basic workflow for writing, running and debugging a juju charm. Charms are a way to package and share your service deployment and orchestration knowledge and share them with the world.</p>
22-
23- <h2>Creating the charm</h2>
24- <p>In this example we are going to write a charm to deploy the drupal CMS system. For the sake of simplicity, we are going to use the mysql charm that comes bundled with juju. Assuming the current directory is the juju trunk, let's enter the directory</p>
25-
26- <h3>Creating a README File</h3>
27- <p>First create a README file with your text editor. This file must be plain text,the Charm Store will parse Markdown format and display it on the Charm Store's web interface, so it will be the public facing page for your charm. We recommend you leave basic usage instructions on how to use your charm in the README.</p>
28-
29- <h3>Making the metadata</h3>
30- <p>Now let's make the metadata and hooks directory:</p>
31- <pre><code>
32- mkdir -p drupal/hooks
33- vim drupal/metadata.yaml
34- </code></pre>
35- <p>Edit the metadata.yaml file to resemble:</p>
36- <pre><code>
37- name: drupal
38- summary: "Drupal CMS"
39- maintainer: "Drupal PowerUser &lt;drupaluser@somedomain.foo&gt;"</p>
40- description: Installs the drupal CMS system, relates to the mysql charm provided in examples directory. Can be scaled to multiple web servers
41- &nbsp;requires:
42- &nbsp;&nbsp;&nbsp;db
43- &nbsp;interface:
44- &nbsp;&nbsp;&nbsp;mysql
45- </code></pre>
46-
47- <p>The metadata.yaml file provides metadata around the charm. The file declares a charm with the name drupal. Since this is the first time to edit this charm, its revision number is one. A short and long description of the charm are provided. The final field is <cite>requires</cite>, this mentions the interface type required by this charm. Since this drupal charm uses the services of a mysql database, we need to require it in the metadata. Since this charm does not provide a service to any other charm, there is no <cite>provides</cite> field. You might be wondering where did the interface name &quot;mysql&quot; come from, you can locate the interface information from the mysql charm's metadata.yaml. Here it is for convenience:</p>
48- <pre><code>
49- name: mysql
50- summary: "MySQL relational database provider"
51- maintainer: "Joe Charmer &lt;youremail@whatever.com&gt;"
52- description: |
53- &nbsp;&nbsp;&nbsp;&nbsp;Installs and configures the MySQL package (mysqldb), then runs it.
54-
55- &nbsp;&nbsp;&nbsp;Upon a consuming service establishing a relation, creates a new
56- &nbsp;&nbsp;&nbsp;database for that service, if the database does not yet
57- &nbsp;&nbsp;&nbsp;exist. Publishes the following relation settings for consuming
58- &nbsp;&nbsp;&nbsp;services:
59-
60- &nbsp;&nbsp;database: database name
61- &nbsp;&nbsp;&nbsp;&nbsp;user: user name to access database
62- &nbsp;&nbsp;&nbsp;&nbsp;password: password to access the database
63- &nbsp;&nbsp;&nbsp;&nbsp;host: local hostname
64- provides:
65- &nbsp;&nbsp;&nbsp;db:
66- &nbsp;&nbsp;&nbsp;&nbsp;interface: mysql</p>
67- </code></pre>
68-
69- <p>That very last line mentions that the interface that mysql provides to us is &quot;mysql&quot;. Also the description mentions that four parameters are sent to the connecting charm (database, user, password, host) in order to enable it to connect to the database. We will make use of those variables once we start writing hooks. Such interface information is either provided in a bundled README file, or in the description. You can also read the charm code to discover such information as well.</p>
70- <p>In the next steps we will write the necessary hook scripts.</p>
71-
72- <h2>Have a plan</h2>
73- <p>When attempting to write a charm, it is beneficial to have a mental plan of what it takes to deploy the software. In our case, you should deploy drupal manually, understand where its configuration information is written, how the first node is deployed, and how further nodes are configured. With respect to this charm, this is the plan:</p>
74-
75- <ul>
76- <li>Install hook installs all needed components (apache, php, drush)</li>
77- <li>Once the database connection information is ready, call drush on first node to perform the initial setup (creates DB tables, completes setup)</li>
78- <li>For scaling onto other nodes, the DB tables have already been set-up. Thus we only need to append the database connection information into drupal's settings.php file. We will use a template file for that</li>
79- </ul>
80-
81-
82- <p class="note"><strong>Note:</strong>The hooks in a charm are executable files that can be written using any scripting or programming language. In our case, we'll use bash</p>
83-
84- <p>For production charms it is always recommended that you install software components from the Ubuntu archive (using apt-get) in order to get security updates. However in this example I am installing drush (Drupal shell) using apt-get, then using that to download and install the latest version of drupal. If you were deploying your own code, you could just as easily install a revision control tool (bzr, git, hg...etc) and use that to checkout a code branch to deploy from. This demonstrates the flexibility offered by juju which doesn't really force you into one way of doing things.</p>
85-
86-
87- <h2>Write hooks</h2>
88- <p>Let's change into the hooks directory:</p>
89- <pre>
90- cd drupal/hooks
91- vim install
92- </pre>
93-
94- <p>Since you should have already installed drupal, you have an idea what it takes to get it installed. My install script looks like:</p>
95- <pre><code>#!/bin/bash
96- set -eux # -x for verbose logging to juju debug-log
97- juju-log "Installing drush,apache2,php via apt-get"
98- apt-get -y install drush apache2 php5-gd libapache2-mod-php5 php5-cgi mysql-client-core-5.5
99- a2enmod php5
100- /etc/init.d/apache2 restart
101- juju-log "Using drush to download latest Drupal"
102- # Typo on next line, it should be www not ww
103- cd /var/ww &amp;&amp; drush dl drupal --drupal-project-rename=juju
104- </code></pre>
105-
106- <p>I have introduced an artificial typo on the last line &quot;ww not www&quot;, this is to simulate any error which you are bound to face sooner or later. Let's create other hooks:</p>
107- <pre>
108- vim start
109- </pre>
110-
111- <p>The start hook is empty, however it needs to be a valid executable, thus we'll add the first bash shebang line, here it is:</p>
112- <pre>
113- <span class="c">#!/bin/bash</span>
114- </pre>
115-
116- <p>Here's the &quot;stop&quot; script:</p>
117- <pre>
118- #!/bin/bash
119- juju-log "Stopping apache"
120- /etc/init.d/apache2 stop
121- </pre>
122-
123- <p>The final script, which does most of the work is &quot;db-relation-changed&quot;. This script gets the database connection information set by the mysql charm then sets up drupal for the first time, and opens port 80 for web access. Let's start with a simple version that only installs drupal on the first node. Here it is:</p>
124- <pre>#!/bin/bash
125- set -eux # -x for verbose logging to juju debug-log
126- hooksdir=$PWD
127- user=`relation-get user`
128- password=`relation-get password`
129- host=`relation-get host`
130- database=`relation-get database`
131- # All values are set together, so checking on a single value is enough
132- # If $user is not set, DB is still setting itself up, we exit awaiting next run
133- [ -z "$user" ] &amp;&amp; exit 0
134- juju-log "Setting up Drupal for the first time"
135- cd /var/www/juju &amp;&amp; drush site-install -y standard \
136- --db-url=mysql://$user:$password@$host/$database \
137- --site-name=juju --clean-url=0
138- cd /var/www/juju &amp;&amp; chown www-data sites/default/settings.php
139- open-port 80/tcp
140- </code>
141- </pre>
142-
143- <p>The script is quite simple, it reads the four variables needed to connect to mysql, ensures they are not null, then passes them to the drupal installer. Make sure all the hook scripts have executable permissions, and change directory above the examples directory:</p>
144- <pre>chmod +x *
145- $ cd ../../../..
146- </pre>
147-
148- <p>Checking on the drupal charm file-structure, this is what we have:</p>
149- <pre>
150- find examples/precise/drupal
151- examples/precise/drupal
152- examples/precise/drupal/metadata.yaml
153- examples/precise/drupal/hooks
154- examples/precise/drupal/hooks/db-relation-changed
155- examples/precise/drupal/hooks/stop
156- examples/precise/drupal/hooks/install
157- examples/precise/drupal/hooks/start
158- </pre>
159-
160- <h2>Test run</h2>
161- <p>Let us deploy the drupal charm. Remember that the install hook has a problem and will not exit cleanly. Deploying:</p>
162-
163- <pre>juju bootstrap</pre>
164-
165- <p>Wait a minute for the environment to bootstrap. Keep issuing the status command till you know the environment is ready:</p>
166-
167- <pre>
168- juju status
169- 2011-06-07 14:04:06,816 INFO Connecting to environment.
170- machines:
171- &nbsp;0:
172- &nbsp;&nbsp;&nbsp;agent-state: running
173- &nbsp;&nbsp;&nbsp;dns-name: ec2-50-16-107-102.compute-1.amazonaws.com
174- &nbsp;&nbsp;&nbsp;instance-id: i-130c9168
175- &nbsp;&nbsp;&nbsp;instance-state: running
176- services:
177- 2011-06-07 14:04:11,125 INFO 'status' command finished successfully
178- </pre>
179-
180- <p>It can be beneficial when debugging a new charm to always have the distributed debug-log running in a separate window:</p>
181- <pre>juju debug-log</pre>
182- <p>Let's deploy the mysql and drupal charms:</p>
183- <pre>juju deploy --repository=examples local:precise/mysql
184- juju deploy --repository=examples local:precise/drupal</pre>
185-
186- <p>This deploy is telling juju to look in a local repository for our charm, specifically in the examples/precise/mysql and examples/precise/drupal folders. Local repositories specified with the --repository switch must point to a directory which contains sub-directories named after an Ubuntu series (e.g. precise) and the charms. The repository can be named anything you wish.</p>
187- <p>Thus, when creating charms locally, this syntax should be followed:</p>
188- <pre><code>
189- repositoryName/ubuntuReleaseName/charmName</code></pre>
190- <p>Once the machines are started (hint: check the debug-log), issue a status command:</p>
191- <pre>juju status
192-
193- <p>machines:
194- &nbsp;0:
195- &nbsp;&nbsp;&nbsp;agent-state: running
196- &nbsp;&nbsp;&nbsp;dns-name: ec2-50-16-107-102.compute-1.amazonaws.com
197- &nbsp;&nbsp;&nbsp;instance-id: i-130c9168
198- &nbsp;&nbsp;&nbsp;instance-state: running</p>
199- &nbsp;1:
200- &nbsp;&nbsp;&nbsp;agent-state: running
201- &nbsp;&nbsp;&nbsp;dns-name: ec2-50-19-24-186.compute-1.amazonaws.com
202- &nbsp;&nbsp;&nbsp;instance-id: i-17079a6c
203- &nbsp;&nbsp;&nbsp;instance-state: running
204- &nbsp;2:
205- &nbsp;&nbsp;&nbsp;agent-state: running
206- &nbsp;&nbsp;&nbsp;dns-name: ec2-23-20-194-198.compute-1.amazonaws.com
207- &nbsp;&nbsp;&nbsp;instance-id: i-d7079aac
208- &nbsp;&nbsp;&nbsp;instance-state: running
209- services:
210- &nbsp;mysql:
211- &nbsp;&nbsp;&nbsp;charm: cs:precise/mysql-3
212- &nbsp;&nbsp;&nbsp;relations: {}
213- &nbsp;&nbsp;&nbsp;units:
214- &nbsp;&nbsp;&nbsp;mysql/0:
215- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;agent-state: started
216- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;machine: 2
217- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public-address: ec2-23-20-194-198.compute-1.amazonaws.com
218- &nbsp;drupal:
219- &nbsp;&nbsp;&nbsp;charm: local:precise/drupal-3
220- &nbsp;&nbsp;&nbsp;relations: {}
221- &nbsp;&nbsp;&nbsp;units:
222- &nbsp;&nbsp;&nbsp;drupal/0:
223- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;agent-state: install-error
224- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;machine: 1
225- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public-address: ec2-50-19-24-186.compute-1.amazonaws.com</pre>
226-
227- <p>Note how mysql is listed as started, while drupal's state is install_error. This is because the install hook has an error, and did not exit cleanly (exit code 1).</p>
228- <h2>Debugging hooks</h2>
229- <p>Let's debug the install hook, from a new window:</p>
230- <pre>juju debug-hooks drupal/0</pre>
231-
232- <p>This will connect you to the drupal machine, and present a shell. The way the debug-hooks functionality works is by starting a new terminal window instead of executing a hook when it is triggered. This way you get a chance of running the hook manually, fixing any errors and re-running it again. In order to trigger re-running the install hook, from another window:</p>
233- <pre>juju resolved --retry drupal/0</pre>
234-
235- <p>Switching to the debug-hooks window, you will notice a new window named &quot;install&quot; popped up. Note that &quot;install&quot; is the name of the hook that this debug-hooks session is replacing. We change directory into the hooks directory and rerun the hook manually:</p>
236- <pre>$ cd /var/lib/juju/units/drupal-0/charm/hooks/
237- $ ./install
238- # -- snip --
239- + cd /var/ww
240- ./install: line 10: cd: /var/ww: No such file or directory
241- </pre>
242-
243- <p>Problem identified. Let's edit the script, changing ww into www. Rerunning it again should work successfully. This is why it is very good practice to write hook scripts in an idempotent manner such that rerunning them over and over always results in the same state. Do not forget to exit the install window by typing &quot;exit&quot;, this signals that the hook has finished executing successfully. If you have finished debugging, you may want to exit the debug-hooks session completely by typing &quot;exit&quot; into the very first window Window0</p>
244-
245- <p>Note</p>
246- <p>While we have fixed the script, this was done on the remote machine only. You need to update the local copy of the charm with your changes, increment the revision number in the revision file and perform a charm upgrade to push the changes, like:</p>
247- <pre>juju upgrade-charm --repository=examples/ drupal</pre>
248-
249- <p>Let's continue after having fixed the install error:</p>
250- <pre>juju add-relation mysql drupal</pre>
251-
252- <p>Watching the debug-log window, you can see debugging information to verify the hooks are working as they should. If you spot any error, you can launch debug-hooks in another window to start debugging the misbehaving hooks again. Note that since &quot;add-relation&quot; relates two charms together, you cannot really retrigger it by simply issuing &quot;resolved --retry&quot; like we did for the install hook. In order to retrigger the db-relation-changed hook, you need to remove the relation, and create it again like so:</p>
253- <pre>juju remove-relation mysql drupal
254- juju add-relation mysql drupal</pre>
255-
256- <p>The service should now be ready for use. The remaining step is to expose it to public access. While the charm signaled it needs port 80 to be open, for public accessibility, the port is not open until the administrator explicitly uses the expose command:</p>
257- <pre>juju expose drupal</pre>
258-
259- <p>Let's see a status with the ports exposed:</p>
260- <pre>
261- juju status
262-
263- machines:
264- &nbsp;0:
265- &nbsp;&nbsp;&nbsp;agent-state: running
266- &nbsp;&nbsp;&nbsp;dns-name: ec2-50-16-107-102.compute-1.amazonaws.com
267- &nbsp;&nbsp;&nbsp;instance-id: i-130c9168
268- &nbsp;&nbsp;&nbsp;instance-state: running
269- &nbsp;1:
270- &nbsp;&nbsp;&nbsp;agent-state: running
271- &nbsp;&nbsp;&nbsp;dns-name: ec2-50-19-24-186.compute-1.amazonaws.com
272- &nbsp;&nbsp;&nbsp;instance-id: i-17079a6c
273- &nbsp;&nbsp;&nbsp;instance-state: running
274- &nbsp;2:
275- &nbsp;&nbsp;&nbsp;agent-state: running
276- &nbsp;&nbsp;&nbsp;dns-name: ec2-23-20-194-198.compute-1.amazonaws.com
277- &nbsp;&nbsp;&nbsp;instance-id: i-d7079aac
278- &nbsp;&nbsp;&nbsp;instance-state: running
279- &nbsp;services:
280- &nbsp;mysql:
281- &nbsp;&nbsp;charm: cs:precise/mysql-3
282- &nbsp;&nbsp;relations:
283- &nbsp;&nbsp;&nbsp;db:
284- &nbsp;&nbsp;&nbsp;- drupal
285- &nbsp;&nbsp;&nbsp;units:
286- &nbsp;&nbsp;&nbsp;&nbsp;mysql/0:
287- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;agent-state: started
288- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;machine: 2
289- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public-address: ec2-23-20-194-198.compute-1.amazonaws.com
290- &nbsp;&nbsp;drupal:
291- &nbsp;&nbsp;&nbsp;charm: cs:precise/drupal-3
292- &nbsp;&nbsp;&nbsp;exposed: true
293- &nbsp;&nbsp;&nbsp;relations:
294- &nbsp;&nbsp;&nbsp;&nbsp;db:
295- &nbsp;&nbsp;&nbsp;&nbsp;- mysql
296- &nbsp;&nbsp;&nbsp;units:
297- &nbsp;&nbsp;&nbsp;&nbsp;drupal/0:
298- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;agent-state: started
299- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;machine: 1
300- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;open-ports:
301- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- 80/tcp
302- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public-address: ec2-50-19-24-186.compute-1.amazonaws.com</pre>
303-
304- <p>Congratulations, your charm should now be working successfully! The db-relation-changed hook previously shown is not suitable for scaling drupal to more than one node, since it always drops the database and recreates a new one. A more complete hook would need to first check whether or not the DB tables exist and act accordingly. Here is how such a hook might be written:</p>
305- <pre>#!/bin/bash
306- &nbsp;>set -eux # -x for verbose logging to juju debug-log
307- &nbsp;<p>hooksdir=$PWD
308- &nbsp;user=`relation-get user`
309- &nbsp;password=`relation-get password`
310- &nbsp;host=`relation-get host`
311- &nbsp;database=`relation-get database`
312- &nbsp;# All values are set together, so checking on a single value is enough
313- &nbsp;# If $user is not set, DB is still setting itself up, we exit awaiting next run
314- &nbsp;[ -z "$user" ] &amp;&amp; exit 0
315- &nbsp;
316- &nbsp;if $(mysql -u $user --password=$password -h $host -e 'use drupal; show tables;' | grep -q users); then
317- &nbsp;juju-log "Drupal already set-up. Adding DB info to configuration"
318- &nbsp;cd /var/www/juju/sites/default
319- &nbsp;cp default.settings.php settings.php
320- &nbsp;sed -e "s/USER/$user/" \
321- &nbsp;-e "s/PASSWORD/$password/" \
322- &nbsp;-e "s/HOST/$host/" \
323- &nbsp;-e "s/DATABASE/$database/" \
324- &nbsp;$hooksdir/drupal-settings.template &gt;&gt; settings.php
325- &nbsp;else
326- &nbsp;juju-log "Setting up Drupal for the first time"
327- &nbsp;cd /var/www/juju &amp;&amp; drush site-install -y standard \
328- &nbsp;&nbsp;--db-url=mysql://$user:$password@$host/$database \
329- &nbsp;&nbsp;--site-name=juju --clean-url=0
330- &nbsp;fi
331- &nbsp;cd /var/www/juju &amp;&amp; chown www-data sites/default/settings.php
332- &nbsp;open-port 80/tcp
333- </pre>
334-
335-
336- <p class="first admonition-title">Note</p>
337- <p class="last">Any files that you store in the hooks directory are transported as is to the deployment machine. You can drop in configuration files or templates that you can use from your hook scripts. An example of this technique is the drupal-settings.template file that is used in the previous hook. The template is rendered using sed, however any other more advanced template engine can be used</p>
338-
339- <p><p>Here is the template file used:</p>
340- <pre>$databases = array (
341- &nbsp;'default' =&gt;
342- &nbsp;array (
343- &nbsp;&nbsp;&nbsp;&nbsp;'default' =&gt;
344- &nbsp;&nbsp;&nbsp;&nbsp;array (
345- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'database' =&gt; 'DATABASE',
346- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'username' =&gt; 'USER',
347- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'password' =&gt; 'PASSWORD',
348- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'host' =&gt; 'HOST',
349- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'port' =&gt; '',
350- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'driver' =&gt; 'mysql',
351- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'prefix' =&gt; '',
352- &nbsp;&nbsp;&nbsp;&nbsp;),
353- &nbsp;&nbsp;),
354- &nbsp;);
355- </pre>
356+<h1>Your First charm starts here!</h1>
357+<p>Okay, so you have read up all the background info on what a charm is, how it works, and what parts of the charm do what, now it is time to dust off your favourite code editor (no arguments please) and get charming!</p>
358+<p>For this example, we are imagining that we want to create a charm for <a href="http://vanillaforums.org/" >the Vanilla forum software</a></p>
359+
360+<span class="number"> 1 </span>
361+ <div class="step" id="step01">
362+ <h2>Prepare yourself</h2>
363+ <p>As we are writing a charm, it makes sense to create it in a local charm repository (see how to deploy from a local repository <a href="./charms-deploying.html" target="#local">here</a>) to make it easy to test in your Juju environment.</p>
364+ <p>Go to your home directory (or wherever is appropriate and make the appropriate file structure:</p>
365+ <pre>cd ~<br>mkdir -p charms/precise/vanilla </pre>
366+
367+ </div>
368+
369+<span class="number" > 2 </span>
370+ <div class="step" id="step02">
371+ <h2>Create the README file</h2>
372+ <p>Fire up your text editor and create a readme file.</p>
373+ <p>This step is especially important if you intend making your charm public, but it is very useful even if your charm will only ever be seen by you. The README is a good place to make nots about how the charm works, what information it expects to communicate and how.</p>
374+ <p>Although a plain text file is fine, you can also write your README file using Markdown (in which case use the .md suffix). The advantage of this, other than it looks quite neat, is that the Charm Store will render Mardown properly online, so your README will look and read better.</p>
375+ <p>Here is a quick example README file for our Vanilla charm:</p>
376+ <pre class="prettyprint lang-yaml">
377+# Overview
378+
379+Vanilla is a powerful open source web-based forum. This charm will deploy the forum software and connect it to a running MySQL database. This charm will install the Vanilla files in /var/www/vanilla/
380+
381+# Installation
382+
383+To deploy this charm you will need at a minimum: a cloud environment, working Juju installation and a successful bootstrap. Once bootstrapped, deploy the MySQL charm and then this Vanilla charm:
384+
385+ juju deploy mysql
386+ juju deploy vanilla
387+
388+Add a relation between the two of them:
389+
390+ juju add-relation mysql vanilla
391+
392+And finally expose the Vanilla service:
393+
394+ juju expose vanilla</pre>
395+
396+ <p>Obviously, you can include any useful info you wish.</p>
397+ </div>
398+
399+<span class="number" > 3 </span>
400+ <div class="step" id="step03">
401+ <h2>Make some metadata.yaml</h2>
402+ <p>The <strong>metadata.yaml</strong> file is really important. This is the file that Juju reads to find out what a charm is, what it does and what it needs to do it.</p>
403+ <p>The yaml syntax is at once simple, but exact, so if you have any future problems with Juju not recognising your charm, this is the first port of call! the information is stored in simple <span class="pre">&LT;key&GT; : &LT;value&GT;</span> associations. The first four are pretty self explanitory:</p>
404+ <pre class="prettyprint lang-basic">
405+name: vanilla
406+summary: Vanilla is an open-source, pluggable, multi-lingual forum.
407+maintainer: Your Name &lt;your@email.tld&gt;
408+description: |
409+ Vanilla is designed to deploy and grow small communities to scale.
410+ This charm deploys Vanilla Forums as outlined by the Vanilla Forums
411+ installation guide.
412+</pre>
413+ <p>The summary should be a brief description of the service being deployed, whereas the description can go into more detail.</p>
414+ <p>The next value to define is the category. This is primarily for organising the charm in the charm store. the available categories are:</p>
415+ <ul>
416+ <li><strong>databases -</strong> MySQL, Postgres, couchDB, etc.</li>
417+ <li><strong>file-servers - </strong>storage apps such as ceph</li>
418+ <li><strong>applications -</strong>applications like mediawiki, wordpress</li>
419+ <li><strong>cache-proxy - </strong>services such as haproxy and Varnish.</li>
420+ <li><strong>app-servers - </strong>infrastructure services like Apache and Tomcat</li>
421+ <li><strong>miscellaneous - </strong>anything which doesn't neatly fit anywhere above.</li>
422+ </ul>
423+ <p>Your charm can belong to more than one category, though in almost all cases it should be in just one. Because there could be more than one entry here, the yaml is formatted as a list:</p>
424+ <pre class="prettyprint lang-yaml">categories:<br> - applications</pre>
425+ <p>Next we need to explain which services are actually provided by this service. This is done using an indent for each service provided, followed by a description of the interface. The interface name is important as it can be used elsewhere in the environment to relate back to this charm, e.g. when writing hooks.</p>
426+ <p>Our Vanilla charm is a web-based service which exposes a simple http interface:</p>
427+ <pre class="prettyprint lang-yaml">provides:<br> website:<br> interface: http</pre>
428+ <p>The name given here is important as it will be used in hooks that we write later, and the interface name will be used by other charms which may want to relate to this one.</p>
429+ <p>Similarly we also need to provide a "requires" section. In this case we need a database. Checking out the metadata of the MySQL charm we can see that it provides this via the interface name "mysql", so we can use this name in our metadata.</p>
430+ <p>The final file should look like this:</p>
431+ <pre class="prettyprint lang-yaml">
432+name: vanilla
433+summary: Vanilla is an open-source, pluggable, themeable, multi-lingual forum.
434+maintainer: Your Name &lt;your@email.tld&gt;
435+description: |
436+ Vanilla is designed to deploy and grow small communities to scale.
437+ This charm deploys Vanilla Forums as outlined by the Vanilla Forums installation guide.
438+categories:
439+ - applications
440+provides:
441+ website:
442+ interface: http
443+requires:
444+ database:
445+ interface: mysql</pre>
446+ <p>
447+ <p>For some charms you will want a "peers" section also. This follows the same format, and its used for optional connections, such as you might use for interconnecting services in a cluster
448+ </p>
449+
450+
451+ </div>
452+<span class="number" > 4 </span>
453+ <div class="step" id="step04">
454+ <h2>Writing hooks</h2>
455+ <p>As you will know from your through reading of <a href="./authors-charm-anatomy.html"> the anatomy of a charm</a> The hooks are the important scripts that actually do things. You can write hooks in whatever language you can reasonably expect to execute on an Ubuntu server</p>
456+ <p>For our charm, the hooks we will need to create are:</p>
457+ <ul>
458+ <li>start - for when the service needs to be started.</li>
459+ <li>stop - for stopping it again.</li></ul>
460+ <li>install - for actually fetching and installing the Vanilla code.</li>
461+ <li>database-relation-changed - this will run when we connect (or re-connect, or disconnect) our service to the MySQL database. This hook will need to manage this connection.</li>
462+ <li>website-relation-joined - this will run when/if a service connects to our charm.</li>
463+ </ul>
464+ <p>So first up we should create the hooks directory, and start creating our first hook:</p>
465+ <pre>mkdir hooks<br>cd hooks<br>vi start</pre>
466+ <p>(Use your favourite editor, naturally - no flames please)</p>
467+ <p>We have started with the start hook, because it is pretty simple. Our charm will be served up by apache, so all we need to do to start the service is make sure apache is running:</p>
468+ <pre class="prettyprint lang-bash">#!/bin/bash<br>set -e<br>service apache2 restart</pre>
469+ <p>A bit of explanation for this one. As we are writing in bash, and we need the files to be executable, we start with a hash-bang line indicating this is a bash file.
470+ the <span class="pre">set -e</span> line means that if any subsequent command returns false (non-zero) the script will stop and raise an error - this is important so that Juju can work out if things are running properly.
471+ </p>
472+ <p>The final line starts the apache webservice, thus also starting our Vanilla service. Why do we call 'restart'? One of the important ideas behind hooks is that they should be 'idempotent'. That means that the opration should be capable of being run many times without changing the intended result (basically). in this case, we don't want an error if apache is actually already running, we just want it to run and reload any config changes.</p>
473+ <p>Once you have saved the file, it is important to make sure that you set it to be executable too!</p>
474+ <pre>chmod +x start</pre>
475+ <p>With the easy bit out of the way, how about the install hook? This needs to install any dependencies, fetch the actual software and do any other config and service jobs that need to happen. here is an example for our vanilla charm:</p>
476+
477+<pre class="prettyprint">
478+#!/bin/bash
479+
480+set -e # If any command fails, stop execution of the hook with that error
481+
482+apt-get install -y apache2 php5-cgi php5-mysql curl php5-gd wget libapache2-mod-php5
483+
484+dl="https://github.com/vanillaforums/Garden/archive/Vanilla_2.0.18.8.tar.gz"
485+
486+# Grab Vanilla from upstream.
487+juju-log "Fetching $dl"
488+wget "$dl" -O /tmp/vanilla.tar.gz
489+
490+# IDEMPOTENCY is very important in all charm hooks, even the install hook.
491+if [ -f /var/www/vanilla/conf/config.php ]; then
492+ cp /var/www/vanilla/conf/config.php /tmp/
493+ rm -rf /var/www/vanilla
494+fi
495+
496+# Extract to a known location
497+juju-log "Extracting Vanilla"
498+tar -xvzf /tmp/vanilla.tar.gz -C /var/www/
499+mv /var/www/Garden-Vanilla* /var/www/vanilla
500+
501+if [ -f /tmp/config.php ]; then
502+ mv /tmp/config.php /var/www/vanilla/conf/
503+fi
504+
505+chmod -R 777 /var/www/vanilla/conf /var/www/vanilla/uploads /var/www/vanilla/cache
506+
507+juju-log "Creating apache2 configuration"
508+cat &lt;&lt;EOF $gt; /etc/apache2/sites-available/vanilla
509+&lt;VirtualHost *:80&gt;
510+ ServerAdmin webmaster@localhost
511+ DocumentRoot /var/www/vanilla
512+
513+ &lt;Directory /var/www/vanilla&gt;
514+ Options Indexes FollowSymLinks MultiViews
515+ AllowOverride All
516+ Order allow,deny
517+ allow from all
518+ &lt;/Directory&gt;
519+
520+ ErrorLog \${APACHE_LOG_DIR}/vanilla.log
521+ LogLevel warn
522+
523+ CustomLog \${APACHE_LOG_DIR}/access.log combined
524+&lt;/VirtualHost&gt;
525+EOF
526+
527+a2dissite 000-default
528+a2ensite vanilla
529+
530+service apache2 reload
531+
532+juju-log "Files extracted, waiting for other events before we do anything else!"
533+</pre>
534+ <p>We aren't going to go for a line-by-line explanation of that, but there are a few things worth noting</p>
535+ <p>Firstly, note the use of the -y option of the apt-get command. this assumes a 'yes' answer to any questions and removes any manual install options (e.g. services that run config dialogs when they install</p>
536+ <p>In our script, we are fetching the tarball of the Vanilla software. In these cases, it is obviously always better to point to a specific, permanent link to a version of the software. </p>
537+ <p>Also, you will notice that we have used the <span class="pre">juju-log</span> command. This basically spits messages out into the juju log, which is very useful for testing and debugging. We will cover that in more detail later in this walkthrough. </p>
538+ <p>The next step is to create the relationship hooks... </p>
539+ <p>We know from our metadata that we have a connection called 'database', so we can have hooks that relate to that. Note that we don't have to create hooks for all possible events if they are not required - if Juju doesn't find a hook file for a paticular action, it just assumes everything is okay and carries on. It is up to you to decide which events require a hook.</p>
540+ <p>Let's take a stab at the 'database-relation-changed' hook:</p>
541+ <pre class="prettyprint">#!/bin/bash
542+
543+set -e # If any command fails, stop execution of the hook with that error
544+
545+db_user=`relation-get user`
546+db_db=`relation-get database`
547+db_pass=`relation-get password`
548+db_host=`relation-get private-address`
549+
550+if [ -z "$db_db" ]; then
551+ juju-log "No database information sent yet. Silently exiting"
552+ exit 0
553+fi
554+
555+vanilla_config="/var/www/vanilla/conf/config.php"
556+
557+cat <<EOF > $vanilla_config
558+&LT;?php if (!defined('APPLICATION')) exit();
559+
560+\$Configuration['Database']['Host'] = '$db_host';
561+\$Configuration['Database']['Name'] = '$db_db';
562+\$Configuration['Database']['User'] = '$db_user';
563+\$Configuration['Database']['Password'] = '$db_pass';
564+EOF
565+
566+juju-log "Make the application port available, now that we know we have a site to expose"
567+
568+open-port 80
569+
570+</pre>
571+
572+<p>You will notice that this script uses the backticked command <span class="pre">relation-get</span>. This is a Juju helper command that fetches the named values from the corresponding hook on the service we are connecting to.
573+Usually there will be some indication of what these values are, but you can always inspect the corresponding hoks to find out. In this case we know that when connected, the MySQL charm will create a database and generate random values for things like a username and password.</p>
574+<p>These values will all be set at one time, so the next little bit of script just checks one value to see if it exists - if not the corresponding charm hasn't set the values yet.</p>
575+<p>When it has the values we can use these to modify the config file for Vanilla in the relevant place, and finally open the port to make the service active.</p>
576+<p>The final hook we need to write is for other services which may want to consume Vanilla, 'website-relation-joined'.</p>
577+<pre class="prettyprint">#!/bin/sh
578+
579+relation-set hostname=`unit-get private-address` port=80
580+</pre>
581+<p>Here we can see the other end of the information sharing - in this case <span class="pre">relation-set</span> exposes the given values to the connecting charm. In this case one of the commands is backticked, as <span class="pre">unit-get</span> is another helper command, in this case one which returns the requested value form the machine the charm is running on, specifically here it's IP address.
582+ So, any connecting charm will be able to ask for the values 'hostname' and 'port'. Remember, once you have finished writing your hooks make sure you 'chmod +x' them.</p>
583+<p>For our simplistic charm, that is all the hooks we need for the moment, so now we can test it out!</p>
584+</div>
585+<span class="number"> 5 </span>
586+ <div class="step" id="step05">
587+ <h2>Testing</h2>
588+ <p>Before we congratulate ourselves too much, we should check that the charm actually works. To help with this, we should open a new terminal window and run the following command:</p>
589+ <pre>juju debug-log</pre>
590+ <p>This starts a process to tail the juju log file and show us just exactly what is happening. It won't do much to begin with, but you should see messages appearing when we start to deploy our charm.</p>
591+ <p>Following our own recipe, in another terminal we should now do the following (assuming you already have a bootstrapped environment):</p>
592+ <pre>juju deploy mysql
593+juju deploy --repository=/home/evilnick/localcharms/ local:precise/vanilla
594+juju add-relation mysql vanilla
595+juju expose vanilla
596+</pre>
597+<p>We used the local deploy options to deploy our charm - substitute the path for your own environment. Everything should now be working away, and your log window will look something like this:</p>
598+
599+ <img src="./media/author-charm-writing-debug.png" width='600' alt="Step five - debug">
600+<p>If you wait for all the Juju operations to finish and run a <span class="pre">juju status</span> command, you will be able to retrieve the public address for the Vanilla forum we just deployed. Copy it into your browser and you should see the setup page (prepopulated with the database config) waiting for any changes. Congratulations!</p>
601+ <img src="./media/author-charm-writing-vanilla.png" width='600' alt="Step five - vanilla">
602+
603+
604+ </div>
605+
606+<span class="number"> 6 </span>
607+ <div class="step" id="step06">
608+ <h2>Tidying up</h2>
609+ <p>With the charm working properly, you may consider everything a job well done. If your charm is really great and you want to share it, particularly on the charm store, then there are a couple of things you ought to add.</p>
610+ <ol>
611+
612+ <li>Create a file called 'copyright' and place whatever license information you require in there. </li>
613+ <li>Add a beautiful icon (<a href="./authors-charm-icon.html">there is a guide to making one here</a>) so others can recognise it in the charm store!</li>
614+ </ol>
615+ </div>
616+
617 </article>
618 </div>
619 </div>
620@@ -498,6 +423,7 @@
621 <p><a href="https://bugs.launchpad.net/juju-website/+filebug">Report a bug on this site</a></p>
622 </div>
623 </div>
624+ <script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js?skin=sunburst"></script>
625 </footer>
626
627 <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
628
629=== modified file 'htmldocs/charms-deploying.html'
630--- htmldocs/charms-deploying.html 2013-07-15 13:01:43 +0000
631+++ htmldocs/charms-deploying.html 2013-07-25 01:29:31 +0000
632@@ -116,7 +116,7 @@
633 <p>which follows the format:</p>
634 <pre><code>&LT;repository&GT;:&LT;series&GT;&LT;service&GT;</code></pre>
635
636- <h1>Deploying from a local repository</h1>
637+ <h1 id="local">Deploying from a local repository</h1>
638 <p>There are many cases when you may wish to deploy charms from a local filesytem source rather than the charm store:</p>
639 <ul>
640 <li>When testing charms you have written.</li>
641@@ -126,7 +126,7 @@
642 <p>... and probably a lot more times which you can imagine yourselves.</p>
643 <p>Juju can be pointed at a local directory to source charms from using the <span class="pre">--repository=&LT;path/to/files&GT;</span> switch like this:</p>
644
645- <pre><code>juju deploy --repository=/usr/share/charms/ vsftpd</code></pre>
646+ <pre><code>juju deploy --repository=/usr/share/charms/ local:precise/vsftpd</code></pre>
647 <p>You can also make use of standard filesystem shortcuts, so the following examples are also valid:</p>
648 <pre><code>juju deploy --repository=. haproxy</code></pre>
649 <pre><code>juju deploy --repository=~/charms/ wordpress</code></pre>
650
651=== added file 'htmldocs/media/author-charm-writing-debug.png'
652Binary files htmldocs/media/author-charm-writing-debug.png 1970-01-01 00:00:00 +0000 and htmldocs/media/author-charm-writing-debug.png 2013-07-25 01:29:31 +0000 differ
653=== added file 'htmldocs/media/author-charm-writing-log.png'
654Binary files htmldocs/media/author-charm-writing-log.png 1970-01-01 00:00:00 +0000 and htmldocs/media/author-charm-writing-log.png 2013-07-25 01:29:31 +0000 differ
655=== added file 'htmldocs/media/author-charm-writing-vanilla.png'
656Binary files htmldocs/media/author-charm-writing-vanilla.png 1970-01-01 00:00:00 +0000 and htmldocs/media/author-charm-writing-vanilla.png 2013-07-25 01:29:31 +0000 differ
657=== modified file 'skel/footer.tpl'
658--- skel/footer.tpl 2013-07-15 13:01:12 +0000
659+++ skel/footer.tpl 2013-07-25 01:29:31 +0000
660@@ -50,4 +50,5 @@
661 <p><a href="https://bugs.launchpad.net/juju-website/+filebug">Report a bug on this site</a></p>
662 </div>
663 </div>
664+ <script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js?skin=sunburst"></script>
665 </footer>

Subscribers

People subscribed via source and target branches