Merge lp:~josvaz/charms/trusty/bip/charmhelpers-cleanup into lp:charms/trusty/bip

Proposed by Jose L. VG
Status: Needs review
Proposed branch: lp:~josvaz/charms/trusty/bip/charmhelpers-cleanup
Merge into: lp:charms/trusty/bip
Diff against target: 16017 lines (+4357/-11008)
106 files modified
charm-helpers.yaml (+5/-0)
hooks/hooks.py (+8/-12)
lib/charm-helpers/LICENSE.txt (+0/-661)
lib/charm-helpers/MANIFEST.in (+0/-6)
lib/charm-helpers/Makefile (+0/-42)
lib/charm-helpers/README.test (+0/-7)
lib/charm-helpers/README.txt (+0/-8)
lib/charm-helpers/REVISION (+0/-1)
lib/charm-helpers/VERSION (+0/-1)
lib/charm-helpers/bin/README (+0/-1)
lib/charm-helpers/bin/contrib/charmsupport/charmsupport (+0/-31)
lib/charm-helpers/bin/contrib/saltstack/salt-call (+0/-11)
lib/charm-helpers/charmhelpers/contrib/charmhelpers/IMPORT (+0/-4)
lib/charm-helpers/charmhelpers/contrib/charmhelpers/__init__.py (+0/-183)
lib/charm-helpers/charmhelpers/contrib/charmsupport/IMPORT (+0/-14)
lib/charm-helpers/charmhelpers/contrib/charmsupport/nrpe.py (+0/-217)
lib/charm-helpers/charmhelpers/contrib/charmsupport/volumes.py (+0/-156)
lib/charm-helpers/charmhelpers/contrib/hahelpers/apache.py (+0/-58)
lib/charm-helpers/charmhelpers/contrib/hahelpers/ceph.py (+0/-278)
lib/charm-helpers/charmhelpers/contrib/hahelpers/cluster.py (+0/-180)
lib/charm-helpers/charmhelpers/contrib/jujugui/IMPORT (+0/-4)
lib/charm-helpers/charmhelpers/contrib/jujugui/utils.py (+0/-602)
lib/charm-helpers/charmhelpers/contrib/network/ovs/__init__.py (+0/-72)
lib/charm-helpers/charmhelpers/contrib/openstack/context.py (+0/-271)
lib/charm-helpers/charmhelpers/contrib/openstack/templates/__init__.py (+0/-2)
lib/charm-helpers/charmhelpers/contrib/openstack/templates/ceph.conf (+0/-11)
lib/charm-helpers/charmhelpers/contrib/openstack/templates/haproxy.cfg (+0/-37)
lib/charm-helpers/charmhelpers/contrib/openstack/templates/openstack_https_frontend (+0/-23)
lib/charm-helpers/charmhelpers/contrib/openstack/templating.py (+0/-261)
lib/charm-helpers/charmhelpers/contrib/openstack/utils.py (+0/-271)
lib/charm-helpers/charmhelpers/contrib/saltstack/__init__.py (+0/-126)
lib/charm-helpers/charmhelpers/contrib/storage/linux/loopback.py (+0/-59)
lib/charm-helpers/charmhelpers/contrib/storage/linux/lvm.py (+0/-88)
lib/charm-helpers/charmhelpers/contrib/storage/linux/utils.py (+0/-25)
lib/charm-helpers/charmhelpers/contrib/templating/pyformat.py (+0/-13)
lib/charm-helpers/charmhelpers/core/hookenv.py (+0/-340)
lib/charm-helpers/charmhelpers/core/host.py (+0/-272)
lib/charm-helpers/charmhelpers/fetch/__init__.py (+0/-152)
lib/charm-helpers/charmhelpers/fetch/archiveurl.py (+0/-48)
lib/charm-helpers/charmhelpers/payload/__init__.py (+0/-1)
lib/charm-helpers/charmhelpers/payload/archive.py (+0/-57)
lib/charm-helpers/charmhelpers/payload/execd.py (+0/-50)
lib/charm-helpers/debian/compat (+0/-1)
lib/charm-helpers/debian/control (+0/-20)
lib/charm-helpers/debian/rules (+0/-9)
lib/charm-helpers/debian/source/format (+0/-1)
lib/charm-helpers/scripts/README (+0/-1)
lib/charm-helpers/scripts/update-revno (+0/-11)
lib/charm-helpers/setup.cfg (+0/-4)
lib/charm-helpers/setup.py (+0/-39)
lib/charm-helpers/tarmac_tests.sh (+0/-6)
lib/charm-helpers/test_requirements.txt (+0/-18)
lib/charm-helpers/tests/contrib/charmhelpers/test_charmhelpers.py (+0/-291)
lib/charm-helpers/tests/contrib/charmsupport/test_nrpe.py (+0/-222)
lib/charm-helpers/tests/contrib/hahelpers/test_apache_utils.py (+0/-44)
lib/charm-helpers/tests/contrib/hahelpers/test_ceph_utils.py (+0/-59)
lib/charm-helpers/tests/contrib/hahelpers/test_cluster_utils.py (+0/-276)
lib/charm-helpers/tests/contrib/jujugui/config/apache-ports.template (+0/-2)
lib/charm-helpers/tests/contrib/jujugui/config/apache-site.template (+0/-30)
lib/charm-helpers/tests/contrib/jujugui/config/config.js.template (+0/-26)
lib/charm-helpers/tests/contrib/jujugui/config/haproxy.cfg.template (+0/-48)
lib/charm-helpers/tests/contrib/jujugui/config/haproxy.conf (+0/-22)
lib/charm-helpers/tests/contrib/jujugui/config/juju-api-agent.conf.template (+0/-14)
lib/charm-helpers/tests/contrib/jujugui/config/juju-api-improv.conf.template (+0/-12)
lib/charm-helpers/tests/contrib/jujugui/deploy.test (+0/-232)
lib/charm-helpers/tests/contrib/jujugui/test_utils.py (+0/-659)
lib/charm-helpers/tests/contrib/jujugui/unit.test (+0/-11)
lib/charm-helpers/tests/contrib/network/test_ovs.py (+0/-202)
lib/charm-helpers/tests/contrib/openstack/test_openstack_utils.py (+0/-417)
lib/charm-helpers/tests/contrib/openstack/test_os_contexts.py (+0/-454)
lib/charm-helpers/tests/contrib/openstack/test_os_templating.py (+0/-232)
lib/charm-helpers/tests/contrib/saltstack/test_saltstates.py (+0/-156)
lib/charm-helpers/tests/contrib/storage/test_linux_storage_loopback.py (+0/-73)
lib/charm-helpers/tests/contrib/storage/test_linux_storage_lvm.py (+0/-70)
lib/charm-helpers/tests/contrib/storage/test_linux_storage_utils.py (+0/-23)
lib/charm-helpers/tests/contrib/templating/test_pyformat.py (+0/-36)
lib/charm-helpers/tests/core/test_hookenv.py (+0/-789)
lib/charm-helpers/tests/core/test_host.py (+0/-747)
lib/charm-helpers/tests/fetch/test_archiveurl.py (+0/-89)
lib/charm-helpers/tests/fetch/test_fetch.py (+0/-256)
lib/charm-helpers/tests/payload/test_archive.py (+0/-131)
lib/charm-helpers/tests/payload/test_execd.py (+0/-151)
lib/charm-helpers/tests/tools/test_charm_helper_sync.py (+0/-197)
lib/charm-helpers/tools/charm_helpers_sync/README (+0/-89)
lib/charm-helpers/tools/charm_helpers_sync/charm_helpers_sync.py (+0/-168)
lib/charm-helpers/tools/charm_helpers_sync/example-config.yaml (+0/-14)
lib/charmhelpers/__init__.py (+36/-0)
lib/charmhelpers/core/__init__.py (+13/-0)
lib/charmhelpers/core/decorators.py (+55/-0)
lib/charmhelpers/core/files.py (+43/-0)
lib/charmhelpers/core/fstab.py (+132/-0)
lib/charmhelpers/core/hookenv.py (+1007/-0)
lib/charmhelpers/core/host.py (+765/-0)
lib/charmhelpers/core/hugepage.py (+69/-0)
lib/charmhelpers/core/kernel.py (+66/-0)
lib/charmhelpers/core/services/__init__.py (+16/-0)
lib/charmhelpers/core/services/base.py (+351/-0)
lib/charmhelpers/core/services/helpers.py (+290/-0)
lib/charmhelpers/core/strutils.py (+70/-0)
lib/charmhelpers/core/sysctl.py (+54/-0)
lib/charmhelpers/core/templating.py (+84/-0)
lib/charmhelpers/core/unitdata.py (+518/-0)
lib/charmhelpers/fetch/__init__.py (+467/-0)
lib/charmhelpers/fetch/archiveurl.py (+165/-0)
lib/charmhelpers/fetch/bzrurl.py (+75/-0)
lib/charmhelpers/fetch/giturl.py (+68/-0)
To merge this branch: bzr merge lp:~josvaz/charms/trusty/bip/charmhelpers-cleanup
Reviewer Review Type Date Requested Status
Review Queue (community) automated testing Approve
Brad Marshall (community) Approve
charmers Pending
Review via email: mp+301499@code.launchpad.net

Description of the change

To post a comment you must log in.
Jose L. VG (josvaz) wrote :

This has been tested on a juju setup on GCE

Pete Vander Giessen (petevg) wrote :

Tests pass, and code looks good.

We are currently blocked from merging/promulgating by https://bugs.launchpad.net/charms/+source/bip/+bug/1612324 (charm needs to move out of the charmers namespace), however.

Jose L. VG (josvaz) wrote :

Anything I should be fixing here?

Pete Vander Giessen (petevg) wrote :

Dropping in an update here. The next steps for this change need to be:

@bradm needs to update his branch of this charm with the latest from ~charmers, then merge this PR into it. Then he needs to promulgate a charm from his namespace, and request that a charmer merge it.

That should update the charm store so that bip gets pointed at bradm's namespace (since he's the maintainer), and should allow bradm to more easily maintain the charm going forward.

@josvaz: you most likely don't need to do anything else with this PR; the maintainer (bradm) just needs to do some housekeeping to get things merged.

Brad Marshall (brad-marshall) wrote :

This looks good to me, I'm going to merge it into my branch and move over to the new namespace way of doing things. Apologies for the delay, I've been busy with other things, if someone wants to help out with this charm please let me know.

review: Approve
Jose L. VG (josvaz) wrote :

No worries, thanks Brad!

Please, let me know if/when this MP needs to/should be marked Merged.

Review Queue (review-queue) wrote :

The results (PASS) are in and available here: http://juju-ci.vapour.ws/job/charm-bundle-test-aws/5551/

review: Approve (automated testing)
Review Queue (review-queue) wrote :

The results (PASS) are in and available here: http://juju-ci.vapour.ws/job/charm-bundle-test-lxc/5450/

review: Approve (automated testing)

Unmerged revisions

30. By Jose L. VG on 2016-07-29

Got charmhelpers for fetch as apt_install is now there

29. By Jose L. VG on 2016-07-29

Moved from charm-helpers/charmhelpers to just charmhelpers (plus clean-up)

28. By Jose L. VG on 2016-07-29

Result of removing charmhelpers and re-syncing it

27. By Jose L. VG on 2016-07-29

Added charm-helpers.yaml to manage helpers with charm_helpers_sync tool

26. By Jose L. VG on 2016-07-29

Flake8 suggested fixes, including unused imports

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'charm-helpers.yaml'
2--- charm-helpers.yaml 1970-01-01 00:00:00 +0000
3+++ charm-helpers.yaml 2016-07-29 14:56:08 +0000
4@@ -0,0 +1,5 @@
5+destination: lib/charmhelpers
6+branch: lp:charm-helpers
7+include:
8+ - core
9+ - fetch
10
11=== modified file 'hooks/hooks.py'
12--- hooks/hooks.py 2016-07-20 10:26:23 +0000
13+++ hooks/hooks.py 2016-07-29 14:56:08 +0000
14@@ -3,7 +3,6 @@
15 # Copyright 2013 Canonical Ltd. All rights reserved.
16 # Author: Brad Marshall <brad.marshall@canonical.com>
17
18-import glob
19 import os
20 import sys
21 import json
22@@ -11,14 +10,13 @@
23
24 local_copy = os.path.join(
25 os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
26- "lib", "charm-helpers")
27+ "lib")
28 if os.path.exists(local_copy) and os.path.isdir(local_copy):
29 sys.path.insert(0, local_copy)
30
31+from charmhelpers.fetch import apt_install
32
33 from charmhelpers.core.host import (
34- apt_install,
35- mkdir,
36 service,
37 service_start,
38 service_stop,
39@@ -29,9 +27,6 @@
40 Hooks,
41 config,
42 open_port,
43- relation_get,
44- relation_id,
45- remote_unit,
46 )
47
48 hook_dir = os.path.abspath(os.path.dirname(__file__))
49@@ -61,19 +56,19 @@
50 def update_config():
51 from jinja2 import Environment, FileSystemLoader
52 if network_config:
53- ## Try originally supported JSON formatted config
54+ # Try originally supported JSON formatted config
55 try:
56 network = json.loads(network_config)
57- ## else use YAML (current):
58+ # else use YAML (current):
59 except ValueError:
60 network = yaml.load(network_config)
61 else:
62 network = {}
63 if user_config:
64- ## Try originally supported JSON formatted config
65+ # Try originally supported JSON formatted config
66 try:
67 user = json.loads(user_config)
68- ## else use YAML (current):
69+ # else use YAML (current):
70 except ValueError:
71 user = yaml.load(user_config)
72 else:
73@@ -94,7 +89,8 @@
74 'backlog_msg_only': backlog_msg_only,
75 'backlog_always': backlog_always,
76 }
77- template = template_env.get_template('bip_conf.template').render(templ_vars)
78+ template = template_env.get_template('bip_conf.template').render(
79+ templ_vars)
80 with open(bip_conf, 'w') as bip_conf_config:
81 bip_conf_config.write(str(template))
82
83
84=== removed directory 'lib/charm-helpers'
85=== removed file 'lib/charm-helpers/LICENSE.txt'
86--- lib/charm-helpers/LICENSE.txt 2013-07-18 06:08:49 +0000
87+++ lib/charm-helpers/LICENSE.txt 1970-01-01 00:00:00 +0000
88@@ -1,661 +0,0 @@
89- GNU AFFERO GENERAL PUBLIC LICENSE
90- Version 3, 19 November 2007
91-
92- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
93- Everyone is permitted to copy and distribute verbatim copies
94- of this license document, but changing it is not allowed.
95-
96- Preamble
97-
98- The GNU Affero General Public License is a free, copyleft license for
99-software and other kinds of works, specifically designed to ensure
100-cooperation with the community in the case of network server software.
101-
102- The licenses for most software and other practical works are designed
103-to take away your freedom to share and change the works. By contrast,
104-our General Public Licenses are intended to guarantee your freedom to
105-share and change all versions of a program--to make sure it remains free
106-software for all its users.
107-
108- When we speak of free software, we are referring to freedom, not
109-price. Our General Public Licenses are designed to make sure that you
110-have the freedom to distribute copies of free software (and charge for
111-them if you wish), that you receive source code or can get it if you
112-want it, that you can change the software or use pieces of it in new
113-free programs, and that you know you can do these things.
114-
115- Developers that use our General Public Licenses protect your rights
116-with two steps: (1) assert copyright on the software, and (2) offer
117-you this License which gives you legal permission to copy, distribute
118-and/or modify the software.
119-
120- A secondary benefit of defending all users' freedom is that
121-improvements made in alternate versions of the program, if they
122-receive widespread use, become available for other developers to
123-incorporate. Many developers of free software are heartened and
124-encouraged by the resulting cooperation. However, in the case of
125-software used on network servers, this result may fail to come about.
126-The GNU General Public License permits making a modified version and
127-letting the public access it on a server without ever releasing its
128-source code to the public.
129-
130- The GNU Affero General Public License is designed specifically to
131-ensure that, in such cases, the modified source code becomes available
132-to the community. It requires the operator of a network server to
133-provide the source code of the modified version running there to the
134-users of that server. Therefore, public use of a modified version, on
135-a publicly accessible server, gives the public access to the source
136-code of the modified version.
137-
138- An older license, called the Affero General Public License and
139-published by Affero, was designed to accomplish similar goals. This is
140-a different license, not a version of the Affero GPL, but Affero has
141-released a new version of the Affero GPL which permits relicensing under
142-this license.
143-
144- The precise terms and conditions for copying, distribution and
145-modification follow.
146-
147- TERMS AND CONDITIONS
148-
149- 0. Definitions.
150-
151- "This License" refers to version 3 of the GNU Affero General Public License.
152-
153- "Copyright" also means copyright-like laws that apply to other kinds of
154-works, such as semiconductor masks.
155-
156- "The Program" refers to any copyrightable work licensed under this
157-License. Each licensee is addressed as "you". "Licensees" and
158-"recipients" may be individuals or organizations.
159-
160- To "modify" a work means to copy from or adapt all or part of the work
161-in a fashion requiring copyright permission, other than the making of an
162-exact copy. The resulting work is called a "modified version" of the
163-earlier work or a work "based on" the earlier work.
164-
165- A "covered work" means either the unmodified Program or a work based
166-on the Program.
167-
168- To "propagate" a work means to do anything with it that, without
169-permission, would make you directly or secondarily liable for
170-infringement under applicable copyright law, except executing it on a
171-computer or modifying a private copy. Propagation includes copying,
172-distribution (with or without modification), making available to the
173-public, and in some countries other activities as well.
174-
175- To "convey" a work means any kind of propagation that enables other
176-parties to make or receive copies. Mere interaction with a user through
177-a computer network, with no transfer of a copy, is not conveying.
178-
179- An interactive user interface displays "Appropriate Legal Notices"
180-to the extent that it includes a convenient and prominently visible
181-feature that (1) displays an appropriate copyright notice, and (2)
182-tells the user that there is no warranty for the work (except to the
183-extent that warranties are provided), that licensees may convey the
184-work under this License, and how to view a copy of this License. If
185-the interface presents a list of user commands or options, such as a
186-menu, a prominent item in the list meets this criterion.
187-
188- 1. Source Code.
189-
190- The "source code" for a work means the preferred form of the work
191-for making modifications to it. "Object code" means any non-source
192-form of a work.
193-
194- A "Standard Interface" means an interface that either is an official
195-standard defined by a recognized standards body, or, in the case of
196-interfaces specified for a particular programming language, one that
197-is widely used among developers working in that language.
198-
199- The "System Libraries" of an executable work include anything, other
200-than the work as a whole, that (a) is included in the normal form of
201-packaging a Major Component, but which is not part of that Major
202-Component, and (b) serves only to enable use of the work with that
203-Major Component, or to implement a Standard Interface for which an
204-implementation is available to the public in source code form. A
205-"Major Component", in this context, means a major essential component
206-(kernel, window system, and so on) of the specific operating system
207-(if any) on which the executable work runs, or a compiler used to
208-produce the work, or an object code interpreter used to run it.
209-
210- The "Corresponding Source" for a work in object code form means all
211-the source code needed to generate, install, and (for an executable
212-work) run the object code and to modify the work, including scripts to
213-control those activities. However, it does not include the work's
214-System Libraries, or general-purpose tools or generally available free
215-programs which are used unmodified in performing those activities but
216-which are not part of the work. For example, Corresponding Source
217-includes interface definition files associated with source files for
218-the work, and the source code for shared libraries and dynamically
219-linked subprograms that the work is specifically designed to require,
220-such as by intimate data communication or control flow between those
221-subprograms and other parts of the work.
222-
223- The Corresponding Source need not include anything that users
224-can regenerate automatically from other parts of the Corresponding
225-Source.
226-
227- The Corresponding Source for a work in source code form is that
228-same work.
229-
230- 2. Basic Permissions.
231-
232- All rights granted under this License are granted for the term of
233-copyright on the Program, and are irrevocable provided the stated
234-conditions are met. This License explicitly affirms your unlimited
235-permission to run the unmodified Program. The output from running a
236-covered work is covered by this License only if the output, given its
237-content, constitutes a covered work. This License acknowledges your
238-rights of fair use or other equivalent, as provided by copyright law.
239-
240- You may make, run and propagate covered works that you do not
241-convey, without conditions so long as your license otherwise remains
242-in force. You may convey covered works to others for the sole purpose
243-of having them make modifications exclusively for you, or provide you
244-with facilities for running those works, provided that you comply with
245-the terms of this License in conveying all material for which you do
246-not control copyright. Those thus making or running the covered works
247-for you must do so exclusively on your behalf, under your direction
248-and control, on terms that prohibit them from making any copies of
249-your copyrighted material outside their relationship with you.
250-
251- Conveying under any other circumstances is permitted solely under
252-the conditions stated below. Sublicensing is not allowed; section 10
253-makes it unnecessary.
254-
255- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
256-
257- No covered work shall be deemed part of an effective technological
258-measure under any applicable law fulfilling obligations under article
259-11 of the WIPO copyright treaty adopted on 20 December 1996, or
260-similar laws prohibiting or restricting circumvention of such
261-measures.
262-
263- When you convey a covered work, you waive any legal power to forbid
264-circumvention of technological measures to the extent such circumvention
265-is effected by exercising rights under this License with respect to
266-the covered work, and you disclaim any intention to limit operation or
267-modification of the work as a means of enforcing, against the work's
268-users, your or third parties' legal rights to forbid circumvention of
269-technological measures.
270-
271- 4. Conveying Verbatim Copies.
272-
273- You may convey verbatim copies of the Program's source code as you
274-receive it, in any medium, provided that you conspicuously and
275-appropriately publish on each copy an appropriate copyright notice;
276-keep intact all notices stating that this License and any
277-non-permissive terms added in accord with section 7 apply to the code;
278-keep intact all notices of the absence of any warranty; and give all
279-recipients a copy of this License along with the Program.
280-
281- You may charge any price or no price for each copy that you convey,
282-and you may offer support or warranty protection for a fee.
283-
284- 5. Conveying Modified Source Versions.
285-
286- You may convey a work based on the Program, or the modifications to
287-produce it from the Program, in the form of source code under the
288-terms of section 4, provided that you also meet all of these conditions:
289-
290- a) The work must carry prominent notices stating that you modified
291- it, and giving a relevant date.
292-
293- b) The work must carry prominent notices stating that it is
294- released under this License and any conditions added under section
295- 7. This requirement modifies the requirement in section 4 to
296- "keep intact all notices".
297-
298- c) You must license the entire work, as a whole, under this
299- License to anyone who comes into possession of a copy. This
300- License will therefore apply, along with any applicable section 7
301- additional terms, to the whole of the work, and all its parts,
302- regardless of how they are packaged. This License gives no
303- permission to license the work in any other way, but it does not
304- invalidate such permission if you have separately received it.
305-
306- d) If the work has interactive user interfaces, each must display
307- Appropriate Legal Notices; however, if the Program has interactive
308- interfaces that do not display Appropriate Legal Notices, your
309- work need not make them do so.
310-
311- A compilation of a covered work with other separate and independent
312-works, which are not by their nature extensions of the covered work,
313-and which are not combined with it such as to form a larger program,
314-in or on a volume of a storage or distribution medium, is called an
315-"aggregate" if the compilation and its resulting copyright are not
316-used to limit the access or legal rights of the compilation's users
317-beyond what the individual works permit. Inclusion of a covered work
318-in an aggregate does not cause this License to apply to the other
319-parts of the aggregate.
320-
321- 6. Conveying Non-Source Forms.
322-
323- You may convey a covered work in object code form under the terms
324-of sections 4 and 5, provided that you also convey the
325-machine-readable Corresponding Source under the terms of this License,
326-in one of these ways:
327-
328- a) Convey the object code in, or embodied in, a physical product
329- (including a physical distribution medium), accompanied by the
330- Corresponding Source fixed on a durable physical medium
331- customarily used for software interchange.
332-
333- b) Convey the object code in, or embodied in, a physical product
334- (including a physical distribution medium), accompanied by a
335- written offer, valid for at least three years and valid for as
336- long as you offer spare parts or customer support for that product
337- model, to give anyone who possesses the object code either (1) a
338- copy of the Corresponding Source for all the software in the
339- product that is covered by this License, on a durable physical
340- medium customarily used for software interchange, for a price no
341- more than your reasonable cost of physically performing this
342- conveying of source, or (2) access to copy the
343- Corresponding Source from a network server at no charge.
344-
345- c) Convey individual copies of the object code with a copy of the
346- written offer to provide the Corresponding Source. This
347- alternative is allowed only occasionally and noncommercially, and
348- only if you received the object code with such an offer, in accord
349- with subsection 6b.
350-
351- d) Convey the object code by offering access from a designated
352- place (gratis or for a charge), and offer equivalent access to the
353- Corresponding Source in the same way through the same place at no
354- further charge. You need not require recipients to copy the
355- Corresponding Source along with the object code. If the place to
356- copy the object code is a network server, the Corresponding Source
357- may be on a different server (operated by you or a third party)
358- that supports equivalent copying facilities, provided you maintain
359- clear directions next to the object code saying where to find the
360- Corresponding Source. Regardless of what server hosts the
361- Corresponding Source, you remain obligated to ensure that it is
362- available for as long as needed to satisfy these requirements.
363-
364- e) Convey the object code using peer-to-peer transmission, provided
365- you inform other peers where the object code and Corresponding
366- Source of the work are being offered to the general public at no
367- charge under subsection 6d.
368-
369- A separable portion of the object code, whose source code is excluded
370-from the Corresponding Source as a System Library, need not be
371-included in conveying the object code work.
372-
373- A "User Product" is either (1) a "consumer product", which means any
374-tangible personal property which is normally used for personal, family,
375-or household purposes, or (2) anything designed or sold for incorporation
376-into a dwelling. In determining whether a product is a consumer product,
377-doubtful cases shall be resolved in favor of coverage. For a particular
378-product received by a particular user, "normally used" refers to a
379-typical or common use of that class of product, regardless of the status
380-of the particular user or of the way in which the particular user
381-actually uses, or expects or is expected to use, the product. A product
382-is a consumer product regardless of whether the product has substantial
383-commercial, industrial or non-consumer uses, unless such uses represent
384-the only significant mode of use of the product.
385-
386- "Installation Information" for a User Product means any methods,
387-procedures, authorization keys, or other information required to install
388-and execute modified versions of a covered work in that User Product from
389-a modified version of its Corresponding Source. The information must
390-suffice to ensure that the continued functioning of the modified object
391-code is in no case prevented or interfered with solely because
392-modification has been made.
393-
394- If you convey an object code work under this section in, or with, or
395-specifically for use in, a User Product, and the conveying occurs as
396-part of a transaction in which the right of possession and use of the
397-User Product is transferred to the recipient in perpetuity or for a
398-fixed term (regardless of how the transaction is characterized), the
399-Corresponding Source conveyed under this section must be accompanied
400-by the Installation Information. But this requirement does not apply
401-if neither you nor any third party retains the ability to install
402-modified object code on the User Product (for example, the work has
403-been installed in ROM).
404-
405- The requirement to provide Installation Information does not include a
406-requirement to continue to provide support service, warranty, or updates
407-for a work that has been modified or installed by the recipient, or for
408-the User Product in which it has been modified or installed. Access to a
409-network may be denied when the modification itself materially and
410-adversely affects the operation of the network or violates the rules and
411-protocols for communication across the network.
412-
413- Corresponding Source conveyed, and Installation Information provided,
414-in accord with this section must be in a format that is publicly
415-documented (and with an implementation available to the public in
416-source code form), and must require no special password or key for
417-unpacking, reading or copying.
418-
419- 7. Additional Terms.
420-
421- "Additional permissions" are terms that supplement the terms of this
422-License by making exceptions from one or more of its conditions.
423-Additional permissions that are applicable to the entire Program shall
424-be treated as though they were included in this License, to the extent
425-that they are valid under applicable law. If additional permissions
426-apply only to part of the Program, that part may be used separately
427-under those permissions, but the entire Program remains governed by
428-this License without regard to the additional permissions.
429-
430- When you convey a copy of a covered work, you may at your option
431-remove any additional permissions from that copy, or from any part of
432-it. (Additional permissions may be written to require their own
433-removal in certain cases when you modify the work.) You may place
434-additional permissions on material, added by you to a covered work,
435-for which you have or can give appropriate copyright permission.
436-
437- Notwithstanding any other provision of this License, for material you
438-add to a covered work, you may (if authorized by the copyright holders of
439-that material) supplement the terms of this License with terms:
440-
441- a) Disclaiming warranty or limiting liability differently from the
442- terms of sections 15 and 16 of this License; or
443-
444- b) Requiring preservation of specified reasonable legal notices or
445- author attributions in that material or in the Appropriate Legal
446- Notices displayed by works containing it; or
447-
448- c) Prohibiting misrepresentation of the origin of that material, or
449- requiring that modified versions of such material be marked in
450- reasonable ways as different from the original version; or
451-
452- d) Limiting the use for publicity purposes of names of licensors or
453- authors of the material; or
454-
455- e) Declining to grant rights under trademark law for use of some
456- trade names, trademarks, or service marks; or
457-
458- f) Requiring indemnification of licensors and authors of that
459- material by anyone who conveys the material (or modified versions of
460- it) with contractual assumptions of liability to the recipient, for
461- any liability that these contractual assumptions directly impose on
462- those licensors and authors.
463-
464- All other non-permissive additional terms are considered "further
465-restrictions" within the meaning of section 10. If the Program as you
466-received it, or any part of it, contains a notice stating that it is
467-governed by this License along with a term that is a further
468-restriction, you may remove that term. If a license document contains
469-a further restriction but permits relicensing or conveying under this
470-License, you may add to a covered work material governed by the terms
471-of that license document, provided that the further restriction does
472-not survive such relicensing or conveying.
473-
474- If you add terms to a covered work in accord with this section, you
475-must place, in the relevant source files, a statement of the
476-additional terms that apply to those files, or a notice indicating
477-where to find the applicable terms.
478-
479- Additional terms, permissive or non-permissive, may be stated in the
480-form of a separately written license, or stated as exceptions;
481-the above requirements apply either way.
482-
483- 8. Termination.
484-
485- You may not propagate or modify a covered work except as expressly
486-provided under this License. Any attempt otherwise to propagate or
487-modify it is void, and will automatically terminate your rights under
488-this License (including any patent licenses granted under the third
489-paragraph of section 11).
490-
491- However, if you cease all violation of this License, then your
492-license from a particular copyright holder is reinstated (a)
493-provisionally, unless and until the copyright holder explicitly and
494-finally terminates your license, and (b) permanently, if the copyright
495-holder fails to notify you of the violation by some reasonable means
496-prior to 60 days after the cessation.
497-
498- Moreover, your license from a particular copyright holder is
499-reinstated permanently if the copyright holder notifies you of the
500-violation by some reasonable means, this is the first time you have
501-received notice of violation of this License (for any work) from that
502-copyright holder, and you cure the violation prior to 30 days after
503-your receipt of the notice.
504-
505- Termination of your rights under this section does not terminate the
506-licenses of parties who have received copies or rights from you under
507-this License. If your rights have been terminated and not permanently
508-reinstated, you do not qualify to receive new licenses for the same
509-material under section 10.
510-
511- 9. Acceptance Not Required for Having Copies.
512-
513- You are not required to accept this License in order to receive or
514-run a copy of the Program. Ancillary propagation of a covered work
515-occurring solely as a consequence of using peer-to-peer transmission
516-to receive a copy likewise does not require acceptance. However,
517-nothing other than this License grants you permission to propagate or
518-modify any covered work. These actions infringe copyright if you do
519-not accept this License. Therefore, by modifying or propagating a
520-covered work, you indicate your acceptance of this License to do so.
521-
522- 10. Automatic Licensing of Downstream Recipients.
523-
524- Each time you convey a covered work, the recipient automatically
525-receives a license from the original licensors, to run, modify and
526-propagate that work, subject to this License. You are not responsible
527-for enforcing compliance by third parties with this License.
528-
529- An "entity transaction" is a transaction transferring control of an
530-organization, or substantially all assets of one, or subdividing an
531-organization, or merging organizations. If propagation of a covered
532-work results from an entity transaction, each party to that
533-transaction who receives a copy of the work also receives whatever
534-licenses to the work the party's predecessor in interest had or could
535-give under the previous paragraph, plus a right to possession of the
536-Corresponding Source of the work from the predecessor in interest, if
537-the predecessor has it or can get it with reasonable efforts.
538-
539- You may not impose any further restrictions on the exercise of the
540-rights granted or affirmed under this License. For example, you may
541-not impose a license fee, royalty, or other charge for exercise of
542-rights granted under this License, and you may not initiate litigation
543-(including a cross-claim or counterclaim in a lawsuit) alleging that
544-any patent claim is infringed by making, using, selling, offering for
545-sale, or importing the Program or any portion of it.
546-
547- 11. Patents.
548-
549- A "contributor" is a copyright holder who authorizes use under this
550-License of the Program or a work on which the Program is based. The
551-work thus licensed is called the contributor's "contributor version".
552-
553- A contributor's "essential patent claims" are all patent claims
554-owned or controlled by the contributor, whether already acquired or
555-hereafter acquired, that would be infringed by some manner, permitted
556-by this License, of making, using, or selling its contributor version,
557-but do not include claims that would be infringed only as a
558-consequence of further modification of the contributor version. For
559-purposes of this definition, "control" includes the right to grant
560-patent sublicenses in a manner consistent with the requirements of
561-this License.
562-
563- Each contributor grants you a non-exclusive, worldwide, royalty-free
564-patent license under the contributor's essential patent claims, to
565-make, use, sell, offer for sale, import and otherwise run, modify and
566-propagate the contents of its contributor version.
567-
568- In the following three paragraphs, a "patent license" is any express
569-agreement or commitment, however denominated, not to enforce a patent
570-(such as an express permission to practice a patent or covenant not to
571-sue for patent infringement). To "grant" such a patent license to a
572-party means to make such an agreement or commitment not to enforce a
573-patent against the party.
574-
575- If you convey a covered work, knowingly relying on a patent license,
576-and the Corresponding Source of the work is not available for anyone
577-to copy, free of charge and under the terms of this License, through a
578-publicly available network server or other readily accessible means,
579-then you must either (1) cause the Corresponding Source to be so
580-available, or (2) arrange to deprive yourself of the benefit of the
581-patent license for this particular work, or (3) arrange, in a manner
582-consistent with the requirements of this License, to extend the patent
583-license to downstream recipients. "Knowingly relying" means you have
584-actual knowledge that, but for the patent license, your conveying the
585-covered work in a country, or your recipient's use of the covered work
586-in a country, would infringe one or more identifiable patents in that
587-country that you have reason to believe are valid.
588-
589- If, pursuant to or in connection with a single transaction or
590-arrangement, you convey, or propagate by procuring conveyance of, a
591-covered work, and grant a patent license to some of the parties
592-receiving the covered work authorizing them to use, propagate, modify
593-or convey a specific copy of the covered work, then the patent license
594-you grant is automatically extended to all recipients of the covered
595-work and works based on it.
596-
597- A patent license is "discriminatory" if it does not include within
598-the scope of its coverage, prohibits the exercise of, or is
599-conditioned on the non-exercise of one or more of the rights that are
600-specifically granted under this License. You may not convey a covered
601-work if you are a party to an arrangement with a third party that is
602-in the business of distributing software, under which you make payment
603-to the third party based on the extent of your activity of conveying
604-the work, and under which the third party grants, to any of the
605-parties who would receive the covered work from you, a discriminatory
606-patent license (a) in connection with copies of the covered work
607-conveyed by you (or copies made from those copies), or (b) primarily
608-for and in connection with specific products or compilations that
609-contain the covered work, unless you entered into that arrangement,
610-or that patent license was granted, prior to 28 March 2007.
611-
612- Nothing in this License shall be construed as excluding or limiting
613-any implied license or other defenses to infringement that may
614-otherwise be available to you under applicable patent law.
615-
616- 12. No Surrender of Others' Freedom.
617-
618- If conditions are imposed on you (whether by court order, agreement or
619-otherwise) that contradict the conditions of this License, they do not
620-excuse you from the conditions of this License. If you cannot convey a
621-covered work so as to satisfy simultaneously your obligations under this
622-License and any other pertinent obligations, then as a consequence you may
623-not convey it at all. For example, if you agree to terms that obligate you
624-to collect a royalty for further conveying from those to whom you convey
625-the Program, the only way you could satisfy both those terms and this
626-License would be to refrain entirely from conveying the Program.
627-
628- 13. Remote Network Interaction; Use with the GNU General Public License.
629-
630- Notwithstanding any other provision of this License, if you modify the
631-Program, your modified version must prominently offer all users
632-interacting with it remotely through a computer network (if your version
633-supports such interaction) an opportunity to receive the Corresponding
634-Source of your version by providing access to the Corresponding Source
635-from a network server at no charge, through some standard or customary
636-means of facilitating copying of software. This Corresponding Source
637-shall include the Corresponding Source for any work covered by version 3
638-of the GNU General Public License that is incorporated pursuant to the
639-following paragraph.
640-
641- Notwithstanding any other provision of this License, you have
642-permission to link or combine any covered work with a work licensed
643-under version 3 of the GNU General Public License into a single
644-combined work, and to convey the resulting work. The terms of this
645-License will continue to apply to the part which is the covered work,
646-but the work with which it is combined will remain governed by version
647-3 of the GNU General Public License.
648-
649- 14. Revised Versions of this License.
650-
651- The Free Software Foundation may publish revised and/or new versions of
652-the GNU Affero General Public License from time to time. Such new versions
653-will be similar in spirit to the present version, but may differ in detail to
654-address new problems or concerns.
655-
656- Each version is given a distinguishing version number. If the
657-Program specifies that a certain numbered version of the GNU Affero General
658-Public License "or any later version" applies to it, you have the
659-option of following the terms and conditions either of that numbered
660-version or of any later version published by the Free Software
661-Foundation. If the Program does not specify a version number of the
662-GNU Affero General Public License, you may choose any version ever published
663-by the Free Software Foundation.
664-
665- If the Program specifies that a proxy can decide which future
666-versions of the GNU Affero General Public License can be used, that proxy's
667-public statement of acceptance of a version permanently authorizes you
668-to choose that version for the Program.
669-
670- Later license versions may give you additional or different
671-permissions. However, no additional obligations are imposed on any
672-author or copyright holder as a result of your choosing to follow a
673-later version.
674-
675- 15. Disclaimer of Warranty.
676-
677- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
678-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
679-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
680-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
681-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
682-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
683-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
684-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
685-
686- 16. Limitation of Liability.
687-
688- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
689-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
690-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
691-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
692-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
693-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
694-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
695-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
696-SUCH DAMAGES.
697-
698- 17. Interpretation of Sections 15 and 16.
699-
700- If the disclaimer of warranty and limitation of liability provided
701-above cannot be given local legal effect according to their terms,
702-reviewing courts shall apply local law that most closely approximates
703-an absolute waiver of all civil liability in connection with the
704-Program, unless a warranty or assumption of liability accompanies a
705-copy of the Program in return for a fee.
706-
707- END OF TERMS AND CONDITIONS
708-
709- How to Apply These Terms to Your New Programs
710-
711- If you develop a new program, and you want it to be of the greatest
712-possible use to the public, the best way to achieve this is to make it
713-free software which everyone can redistribute and change under these terms.
714-
715- To do so, attach the following notices to the program. It is safest
716-to attach them to the start of each source file to most effectively
717-state the exclusion of warranty; and each file should have at least
718-the "copyright" line and a pointer to where the full notice is found.
719-
720- <one line to give the program's name and a brief idea of what it does.>
721- Copyright (C) <year> <name of author>
722-
723- This program is free software: you can redistribute it and/or modify
724- it under the terms of the GNU Affero General Public License as published by
725- the Free Software Foundation, either version 3 of the License, or
726- (at your option) any later version.
727-
728- This program is distributed in the hope that it will be useful,
729- but WITHOUT ANY WARRANTY; without even the implied warranty of
730- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
731- GNU Affero General Public License for more details.
732-
733- You should have received a copy of the GNU Affero General Public License
734- along with this program. If not, see <http://www.gnu.org/licenses/>.
735-
736-Also add information on how to contact you by electronic and paper mail.
737-
738- If your software can interact with users remotely through a computer
739-network, you should also make sure that it provides a way for users to
740-get its source. For example, if your program is a web application, its
741-interface could display a "Source" link that leads users to an archive
742-of the code. There are many ways you could offer source, and different
743-solutions will be better for different programs; see section 13 for the
744-specific requirements.
745-
746- You should also get your employer (if you work as a programmer) or school,
747-if any, to sign a "copyright disclaimer" for the program, if necessary.
748-For more information on this, and how to apply and follow the GNU AGPL, see
749-<http://www.gnu.org/licenses/>.
750
751=== removed file 'lib/charm-helpers/MANIFEST.in'
752--- lib/charm-helpers/MANIFEST.in 2013-07-18 06:08:49 +0000
753+++ lib/charm-helpers/MANIFEST.in 1970-01-01 00:00:00 +0000
754@@ -1,6 +0,0 @@
755-include *.txt
756-include Makefile
757-include VERSION
758-include MANIFEST.in
759-include scripts/*
760-recursive-include debian *
761
762=== removed file 'lib/charm-helpers/Makefile'
763--- lib/charm-helpers/Makefile 2013-07-18 06:08:49 +0000
764+++ lib/charm-helpers/Makefile 1970-01-01 00:00:00 +0000
765@@ -1,42 +0,0 @@
766-PROJECT=charmhelpers
767-PYTHON := /usr/bin/env python
768-SUITE=unstable
769-TESTS=tests/
770-
771-all:
772- @echo "make source - Create source package"
773- @echo "make sdeb - Create debian source package"
774- @echo "make deb - Create debian package"
775- @echo "make clean"
776- @echo "make userinstall - Install locally"
777-
778-sdeb: source
779- scripts/build source
780-
781-deb: source
782- scripts/build
783-
784-source: setup.py
785- scripts/update-revno
786- python setup.py sdist
787-
788-clean:
789- python setup.py clean
790- rm -rf build/ MANIFEST
791- find . -name '*.pyc' -delete
792- rm -rf dist/*
793- dh_clean
794-
795-userinstall:
796- scripts/update-revno
797- python setup.py install --user
798-
799-test:
800- @echo Starting tests...
801- @$(PYTHON) /usr/bin/nosetests --nologcapture tests/
802-
803-lint:
804- @echo Checking for Python syntax...
805- @flake8 --ignore=E123,E501 $(PROJECT) $(TESTS) && echo OK
806-
807-build: test lint
808
809=== removed file 'lib/charm-helpers/README.test'
810--- lib/charm-helpers/README.test 2013-07-18 06:08:49 +0000
811+++ lib/charm-helpers/README.test 1970-01-01 00:00:00 +0000
812@@ -1,7 +0,0 @@
813-Required Packages for Running Tests
814------------------------------------
815-python-shelltoolbox
816-python-tempita
817-python-nose
818-python-mock
819-python-testtools
820
821=== removed file 'lib/charm-helpers/README.txt'
822--- lib/charm-helpers/README.txt 2013-07-18 06:08:49 +0000
823+++ lib/charm-helpers/README.txt 1970-01-01 00:00:00 +0000
824@@ -1,8 +0,0 @@
825-============
826-CharmHelpers
827-============
828-
829-CharmHelpers provides an opinionated set of tools for building Juju
830-charms that work together. In addition to basic tasks like interact-
831-ing with the charm environment and the machine it runs on, it also
832-helps keep you build hooks and establish relations effortlessly.
833
834=== removed file 'lib/charm-helpers/REVISION'
835--- lib/charm-helpers/REVISION 2013-07-18 06:08:49 +0000
836+++ lib/charm-helpers/REVISION 1970-01-01 00:00:00 +0000
837@@ -1,1 +0,0 @@
838-54
839
840=== removed file 'lib/charm-helpers/VERSION'
841--- lib/charm-helpers/VERSION 2013-07-18 06:08:49 +0000
842+++ lib/charm-helpers/VERSION 1970-01-01 00:00:00 +0000
843@@ -1,1 +0,0 @@
844-0.1.2
845
846=== removed directory 'lib/charm-helpers/bin'
847=== removed file 'lib/charm-helpers/bin/README'
848--- lib/charm-helpers/bin/README 2013-07-18 06:08:49 +0000
849+++ lib/charm-helpers/bin/README 1970-01-01 00:00:00 +0000
850@@ -1,1 +0,0 @@
851-This directory contains executables for accessing charmhelpers functionality
852
853=== removed directory 'lib/charm-helpers/bin/contrib'
854=== removed directory 'lib/charm-helpers/bin/contrib/charmsupport'
855=== removed file 'lib/charm-helpers/bin/contrib/charmsupport/charmsupport'
856--- lib/charm-helpers/bin/contrib/charmsupport/charmsupport 2013-07-18 06:08:49 +0000
857+++ lib/charm-helpers/bin/contrib/charmsupport/charmsupport 1970-01-01 00:00:00 +0000
858@@ -1,31 +0,0 @@
859-#!/usr/bin/env python
860-
861-import argparse
862-from charmhelpers.contrib.charmsupport import execd
863-
864-
865-def run_execd(args):
866- execd.execd_run(args.module, args.dir, die_on_error=True)
867-
868-
869-def parse_args():
870- parser = argparse.ArgumentParser(description='Perform common charm tasks')
871- subparsers = parser.add_subparsers(help='Commands')
872-
873- execd_parser = subparsers.add_parser('execd',
874- help='Execute a directory of commands')
875- execd_parser.add_argument('--module', default='charm-pre-install',
876- help='module to run (default: charm-pre-install)')
877- execd_parser.add_argument('--dir',
878- help="Override the exec.d directory path")
879- execd_parser.set_defaults(func=run_execd)
880-
881- return parser.parse_args()
882-
883-
884-def main():
885- arguments = parse_args()
886- arguments.func(arguments)
887-
888-if __name__ == '__main__':
889- exit(main())
890
891=== removed directory 'lib/charm-helpers/bin/contrib/saltstack'
892=== removed file 'lib/charm-helpers/bin/contrib/saltstack/salt-call'
893--- lib/charm-helpers/bin/contrib/saltstack/salt-call 2013-07-18 06:08:49 +0000
894+++ lib/charm-helpers/bin/contrib/saltstack/salt-call 1970-01-01 00:00:00 +0000
895@@ -1,11 +0,0 @@
896-#!/usr/bin/env python
897-'''
898-Directly call a salt command in the modules, does not require a running salt
899-minion to run.
900-'''
901-
902-from salt.scripts import salt_call
903-
904-
905-if __name__ == '__main__':
906- salt_call()
907
908=== removed directory 'lib/charm-helpers/charmhelpers'
909=== removed file 'lib/charm-helpers/charmhelpers/__init__.py'
910=== removed directory 'lib/charm-helpers/charmhelpers/contrib'
911=== removed file 'lib/charm-helpers/charmhelpers/contrib/__init__.py'
912=== removed directory 'lib/charm-helpers/charmhelpers/contrib/charmhelpers'
913=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmhelpers/IMPORT'
914--- lib/charm-helpers/charmhelpers/contrib/charmhelpers/IMPORT 2013-07-18 06:08:49 +0000
915+++ lib/charm-helpers/charmhelpers/contrib/charmhelpers/IMPORT 1970-01-01 00:00:00 +0000
916@@ -1,4 +0,0 @@
917-Source lp:charm-tools/trunk
918-
919-charm-tools/helpers/python/charmhelpers/__init__.py -> charmhelpers/charmhelpers/contrib/charmhelpers/__init__.py
920-charm-tools/helpers/python/charmhelpers/tests/test_charmhelpers.py -> charmhelpers/tests/contrib/charmhelpers/test_charmhelpers.py
921
922=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmhelpers/__init__.py'
923--- lib/charm-helpers/charmhelpers/contrib/charmhelpers/__init__.py 2013-07-18 06:08:49 +0000
924+++ lib/charm-helpers/charmhelpers/contrib/charmhelpers/__init__.py 1970-01-01 00:00:00 +0000
925@@ -1,183 +0,0 @@
926-# Copyright 2012 Canonical Ltd. This software is licensed under the
927-# GNU Affero General Public License version 3 (see the file LICENSE).
928-
929-import warnings
930-warnings.warn("contrib.charmhelpers is deprecated", DeprecationWarning)
931-
932-"""Helper functions for writing Juju charms in Python."""
933-
934-__metaclass__ = type
935-__all__ = [
936- #'get_config', # core.hookenv.config()
937- #'log', # core.hookenv.log()
938- #'log_entry', # core.hookenv.log()
939- #'log_exit', # core.hookenv.log()
940- #'relation_get', # core.hookenv.relation_get()
941- #'relation_set', # core.hookenv.relation_set()
942- #'relation_ids', # core.hookenv.relation_ids()
943- #'relation_list', # core.hookenv.relation_units()
944- #'config_get', # core.hookenv.config()
945- #'unit_get', # core.hookenv.unit_get()
946- #'open_port', # core.hookenv.open_port()
947- #'close_port', # core.hookenv.close_port()
948- #'service_control', # core.host.service()
949- 'unit_info', # client-side, NOT IMPLEMENTED
950- 'wait_for_machine', # client-side, NOT IMPLEMENTED
951- 'wait_for_page_contents', # client-side, NOT IMPLEMENTED
952- 'wait_for_relation', # client-side, NOT IMPLEMENTED
953- 'wait_for_unit', # client-side, NOT IMPLEMENTED
954- ]
955-
956-import operator
957-from shelltoolbox import (
958- command,
959-)
960-import tempfile
961-import time
962-import urllib2
963-import yaml
964-
965-SLEEP_AMOUNT = 0.1
966-# We create a juju_status Command here because it makes testing much,
967-# much easier.
968-juju_status = lambda: command('juju')('status')
969-
970-# re-implemented as charmhelpers.fetch.configure_sources()
971-#def configure_source(update=False):
972-# source = config_get('source')
973-# if ((source.startswith('ppa:') or
974-# source.startswith('cloud:') or
975-# source.startswith('http:'))):
976-# run('add-apt-repository', source)
977-# if source.startswith("http:"):
978-# run('apt-key', 'import', config_get('key'))
979-# if update:
980-# run('apt-get', 'update')
981-
982-# DEPRECATED: client-side only
983-def make_charm_config_file(charm_config):
984- charm_config_file = tempfile.NamedTemporaryFile()
985- charm_config_file.write(yaml.dump(charm_config))
986- charm_config_file.flush()
987- # The NamedTemporaryFile instance is returned instead of just the name
988- # because we want to take advantage of garbage collection-triggered
989- # deletion of the temp file when it goes out of scope in the caller.
990- return charm_config_file
991-
992-
993-# DEPRECATED: client-side only
994-def unit_info(service_name, item_name, data=None, unit=None):
995- if data is None:
996- data = yaml.safe_load(juju_status())
997- service = data['services'].get(service_name)
998- if service is None:
999- # XXX 2012-02-08 gmb:
1000- # This allows us to cope with the race condition that we
1001- # have between deploying a service and having it come up in
1002- # `juju status`. We could probably do with cleaning it up so
1003- # that it fails a bit more noisily after a while.
1004- return ''
1005- units = service['units']
1006- if unit is not None:
1007- item = units[unit][item_name]
1008- else:
1009- # It might seem odd to sort the units here, but we do it to
1010- # ensure that when no unit is specified, the first unit for the
1011- # service (or at least the one with the lowest number) is the
1012- # one whose data gets returned.
1013- sorted_unit_names = sorted(units.keys())
1014- item = units[sorted_unit_names[0]][item_name]
1015- return item
1016-
1017-
1018-# DEPRECATED: client-side only
1019-def get_machine_data():
1020- return yaml.safe_load(juju_status())['machines']
1021-
1022-
1023-# DEPRECATED: client-side only
1024-def wait_for_machine(num_machines=1, timeout=300):
1025- """Wait `timeout` seconds for `num_machines` machines to come up.
1026-
1027- This wait_for... function can be called by other wait_for functions
1028- whose timeouts might be too short in situations where only a bare
1029- Juju setup has been bootstrapped.
1030-
1031- :return: A tuple of (num_machines, time_taken). This is used for
1032- testing.
1033- """
1034- # You may think this is a hack, and you'd be right. The easiest way
1035- # to tell what environment we're working in (LXC vs EC2) is to check
1036- # the dns-name of the first machine. If it's localhost we're in LXC
1037- # and we can just return here.
1038- if get_machine_data()[0]['dns-name'] == 'localhost':
1039- return 1, 0
1040- start_time = time.time()
1041- while True:
1042- # Drop the first machine, since it's the Zookeeper and that's
1043- # not a machine that we need to wait for. This will only work
1044- # for EC2 environments, which is why we return early above if
1045- # we're in LXC.
1046- machine_data = get_machine_data()
1047- non_zookeeper_machines = [
1048- machine_data[key] for key in machine_data.keys()[1:]]
1049- if len(non_zookeeper_machines) >= num_machines:
1050- all_machines_running = True
1051- for machine in non_zookeeper_machines:
1052- if machine.get('instance-state') != 'running':
1053- all_machines_running = False
1054- break
1055- if all_machines_running:
1056- break
1057- if time.time() - start_time >= timeout:
1058- raise RuntimeError('timeout waiting for service to start')
1059- time.sleep(SLEEP_AMOUNT)
1060- return num_machines, time.time() - start_time
1061-
1062-
1063-# DEPRECATED: client-side only
1064-def wait_for_unit(service_name, timeout=480):
1065- """Wait `timeout` seconds for a given service name to come up."""
1066- wait_for_machine(num_machines=1)
1067- start_time = time.time()
1068- while True:
1069- state = unit_info(service_name, 'agent-state')
1070- if 'error' in state or state == 'started':
1071- break
1072- if time.time() - start_time >= timeout:
1073- raise RuntimeError('timeout waiting for service to start')
1074- time.sleep(SLEEP_AMOUNT)
1075- if state != 'started':
1076- raise RuntimeError('unit did not start, agent-state: ' + state)
1077-
1078-
1079-# DEPRECATED: client-side only
1080-def wait_for_relation(service_name, relation_name, timeout=120):
1081- """Wait `timeout` seconds for a given relation to come up."""
1082- start_time = time.time()
1083- while True:
1084- relation = unit_info(service_name, 'relations').get(relation_name)
1085- if relation is not None and relation['state'] == 'up':
1086- break
1087- if time.time() - start_time >= timeout:
1088- raise RuntimeError('timeout waiting for relation to be up')
1089- time.sleep(SLEEP_AMOUNT)
1090-
1091-
1092-# DEPRECATED: client-side only
1093-def wait_for_page_contents(url, contents, timeout=120, validate=None):
1094- if validate is None:
1095- validate = operator.contains
1096- start_time = time.time()
1097- while True:
1098- try:
1099- stream = urllib2.urlopen(url)
1100- except (urllib2.HTTPError, urllib2.URLError):
1101- pass
1102- else:
1103- page = stream.read()
1104- if validate(page, contents):
1105- return page
1106- if time.time() - start_time >= timeout:
1107- raise RuntimeError('timeout waiting for contents of ' + url)
1108- time.sleep(SLEEP_AMOUNT)
1109
1110=== removed directory 'lib/charm-helpers/charmhelpers/contrib/charmsupport'
1111=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmsupport/IMPORT'
1112--- lib/charm-helpers/charmhelpers/contrib/charmsupport/IMPORT 2013-07-18 06:08:49 +0000
1113+++ lib/charm-helpers/charmhelpers/contrib/charmsupport/IMPORT 1970-01-01 00:00:00 +0000
1114@@ -1,14 +0,0 @@
1115-Source: lp:charmsupport/trunk
1116-
1117-charmsupport/charmsupport/execd.py -> charm-helpers/charmhelpers/contrib/charmsupport/execd.py
1118-charmsupport/charmsupport/hookenv.py -> charm-helpers/charmhelpers/contrib/charmsupport/hookenv.py
1119-charmsupport/charmsupport/host.py -> charm-helpers/charmhelpers/contrib/charmsupport/host.py
1120-charmsupport/charmsupport/nrpe.py -> charm-helpers/charmhelpers/contrib/charmsupport/nrpe.py
1121-charmsupport/charmsupport/volumes.py -> charm-helpers/charmhelpers/contrib/charmsupport/volumes.py
1122-
1123-charmsupport/tests/test_execd.py -> charm-helpers/tests/contrib/charmsupport/test_execd.py
1124-charmsupport/tests/test_hookenv.py -> charm-helpers/tests/contrib/charmsupport/test_hookenv.py
1125-charmsupport/tests/test_host.py -> charm-helpers/tests/contrib/charmsupport/test_host.py
1126-charmsupport/tests/test_nrpe.py -> charm-helpers/tests/contrib/charmsupport/test_nrpe.py
1127-
1128-charmsupport/bin/charmsupport -> charm-helpers/bin/contrib/charmsupport/charmsupport
1129
1130=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmsupport/__init__.py'
1131=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmsupport/nrpe.py'
1132--- lib/charm-helpers/charmhelpers/contrib/charmsupport/nrpe.py 2013-07-18 06:08:49 +0000
1133+++ lib/charm-helpers/charmhelpers/contrib/charmsupport/nrpe.py 1970-01-01 00:00:00 +0000
1134@@ -1,217 +0,0 @@
1135-"""Compatibility with the nrpe-external-master charm"""
1136-# Copyright 2012 Canonical Ltd.
1137-#
1138-# Authors:
1139-# Matthew Wedgwood <matthew.wedgwood@canonical.com>
1140-
1141-import subprocess
1142-import pwd
1143-import grp
1144-import os
1145-import re
1146-import shlex
1147-import yaml
1148-
1149-from charmhelpers.core.hookenv import (
1150- config,
1151- local_unit,
1152- log,
1153- relation_ids,
1154- relation_set,
1155- )
1156-from charmhelpers.core.host import service
1157-
1158-# This module adds compatibility with the nrpe-external-master and plain nrpe
1159-# subordinate charms. To use it in your charm:
1160-#
1161-# 1. Update metadata.yaml
1162-#
1163-# provides:
1164-# (...)
1165-# nrpe-external-master:
1166-# interface: nrpe-external-master
1167-# scope: container
1168-#
1169-# and/or
1170-#
1171-# provides:
1172-# (...)
1173-# local-monitors:
1174-# interface: local-monitors
1175-# scope: container
1176-
1177-#
1178-# 2. Add the following to config.yaml
1179-#
1180-# nagios_context:
1181-# default: "juju"
1182-# type: string
1183-# description: |
1184-# Used by the nrpe subordinate charms.
1185-# A string that will be prepended to instance name to set the host name
1186-# in nagios. So for instance the hostname would be something like:
1187-# juju-myservice-0
1188-# If you're running multiple environments with the same services in them
1189-# this allows you to differentiate between them.
1190-#
1191-# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master
1192-#
1193-# 4. Update your hooks.py with something like this:
1194-#
1195-# from charmsupport.nrpe import NRPE
1196-# (...)
1197-# def update_nrpe_config():
1198-# nrpe_compat = NRPE()
1199-# nrpe_compat.add_check(
1200-# shortname = "myservice",
1201-# description = "Check MyService",
1202-# check_cmd = "check_http -w 2 -c 10 http://localhost"
1203-# )
1204-# nrpe_compat.add_check(
1205-# "myservice_other",
1206-# "Check for widget failures",
1207-# check_cmd = "/srv/myapp/scripts/widget_check"
1208-# )
1209-# nrpe_compat.write()
1210-#
1211-# def config_changed():
1212-# (...)
1213-# update_nrpe_config()
1214-#
1215-# def nrpe_external_master_relation_changed():
1216-# update_nrpe_config()
1217-#
1218-# def local_monitors_relation_changed():
1219-# update_nrpe_config()
1220-#
1221-# 5. ln -s hooks.py nrpe-external-master-relation-changed
1222-# ln -s hooks.py local-monitors-relation-changed
1223-
1224-
1225-class CheckException(Exception):
1226- pass
1227-
1228-
1229-class Check(object):
1230- shortname_re = '[A-Za-z0-9-_]+$'
1231- service_template = ("""
1232-#---------------------------------------------------
1233-# This file is Juju managed
1234-#---------------------------------------------------
1235-define service {{
1236- use active-service
1237- host_name {nagios_hostname}
1238- service_description {nagios_hostname}[{shortname}] """
1239- """{description}
1240- check_command check_nrpe!{command}
1241- servicegroups {nagios_servicegroup}
1242-}}
1243-""")
1244-
1245- def __init__(self, shortname, description, check_cmd):
1246- super(Check, self).__init__()
1247- # XXX: could be better to calculate this from the service name
1248- if not re.match(self.shortname_re, shortname):
1249- raise CheckException("shortname must match {}".format(
1250- Check.shortname_re))
1251- self.shortname = shortname
1252- self.command = "check_{}".format(shortname)
1253- # Note: a set of invalid characters is defined by the
1254- # Nagios server config
1255- # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()=
1256- self.description = description
1257- self.check_cmd = self._locate_cmd(check_cmd)
1258-
1259- def _locate_cmd(self, check_cmd):
1260- search_path = (
1261- '/',
1262- os.path.join(os.environ['CHARM_DIR'],
1263- 'files/nrpe-external-master'),
1264- '/usr/lib/nagios/plugins',
1265- )
1266- parts = shlex.split(check_cmd)
1267- for path in search_path:
1268- if os.path.exists(os.path.join(path, parts[0])):
1269- command = os.path.join(path, parts[0])
1270- if len(parts) > 1:
1271- command += " " + " ".join(parts[1:])
1272- return command
1273- log('Check command not found: {}'.format(parts[0]))
1274- return ''
1275-
1276- def write(self, nagios_context, hostname):
1277- nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
1278- self.command)
1279- with open(nrpe_check_file, 'w') as nrpe_check_config:
1280- nrpe_check_config.write("# check {}\n".format(self.shortname))
1281- nrpe_check_config.write("command[{}]={}\n".format(
1282- self.command, self.check_cmd))
1283-
1284- if not os.path.exists(NRPE.nagios_exportdir):
1285- log('Not writing service config as {} is not accessible'.format(
1286- NRPE.nagios_exportdir))
1287- else:
1288- self.write_service_config(nagios_context, hostname)
1289-
1290- def write_service_config(self, nagios_context, hostname):
1291- for f in os.listdir(NRPE.nagios_exportdir):
1292- if re.search('.*{}.cfg'.format(self.command), f):
1293- os.remove(os.path.join(NRPE.nagios_exportdir, f))
1294-
1295- templ_vars = {
1296- 'nagios_hostname': hostname,
1297- 'nagios_servicegroup': nagios_context,
1298- 'description': self.description,
1299- 'shortname': self.shortname,
1300- 'command': self.command,
1301- }
1302- nrpe_service_text = Check.service_template.format(**templ_vars)
1303- nrpe_service_file = '{}/service__{}_{}.cfg'.format(
1304- NRPE.nagios_exportdir, hostname, self.command)
1305- with open(nrpe_service_file, 'w') as nrpe_service_config:
1306- nrpe_service_config.write(str(nrpe_service_text))
1307-
1308- def run(self):
1309- subprocess.call(self.check_cmd)
1310-
1311-
1312-class NRPE(object):
1313- nagios_logdir = '/var/log/nagios'
1314- nagios_exportdir = '/var/lib/nagios/export'
1315- nrpe_confdir = '/etc/nagios/nrpe.d'
1316-
1317- def __init__(self):
1318- super(NRPE, self).__init__()
1319- self.config = config()
1320- self.nagios_context = self.config['nagios_context']
1321- self.unit_name = local_unit().replace('/', '-')
1322- self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
1323- self.checks = []
1324-
1325- def add_check(self, *args, **kwargs):
1326- self.checks.append(Check(*args, **kwargs))
1327-
1328- def write(self):
1329- try:
1330- nagios_uid = pwd.getpwnam('nagios').pw_uid
1331- nagios_gid = grp.getgrnam('nagios').gr_gid
1332- except:
1333- log("Nagios user not set up, nrpe checks not updated")
1334- return
1335-
1336- if not os.path.exists(NRPE.nagios_logdir):
1337- os.mkdir(NRPE.nagios_logdir)
1338- os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid)
1339-
1340- nrpe_monitors = {}
1341- monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}}
1342- for nrpecheck in self.checks:
1343- nrpecheck.write(self.nagios_context, self.hostname)
1344- nrpe_monitors[nrpecheck.shortname] = {
1345- "command": nrpecheck.command,
1346- }
1347-
1348- service('restart', 'nagios-nrpe-server')
1349-
1350- for rid in relation_ids("local-monitors"):
1351- relation_set(relation_id=rid, monitors=yaml.dump(monitors))
1352
1353=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmsupport/volumes.py'
1354--- lib/charm-helpers/charmhelpers/contrib/charmsupport/volumes.py 2013-07-18 06:08:49 +0000
1355+++ lib/charm-helpers/charmhelpers/contrib/charmsupport/volumes.py 1970-01-01 00:00:00 +0000
1356@@ -1,156 +0,0 @@
1357-'''
1358-Functions for managing volumes in juju units. One volume is supported per unit.
1359-Subordinates may have their own storage, provided it is on its own partition.
1360-
1361-Configuration stanzas:
1362- volume-ephemeral:
1363- type: boolean
1364- default: true
1365- description: >
1366- If false, a volume is mounted as sepecified in "volume-map"
1367- If true, ephemeral storage will be used, meaning that log data
1368- will only exist as long as the machine. YOU HAVE BEEN WARNED.
1369- volume-map:
1370- type: string
1371- default: {}
1372- description: >
1373- YAML map of units to device names, e.g:
1374- "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }"
1375- Service units will raise a configure-error if volume-ephemeral
1376- is 'true' and no volume-map value is set. Use 'juju set' to set a
1377- value and 'juju resolved' to complete configuration.
1378-
1379-Usage:
1380- from charmsupport.volumes import configure_volume, VolumeConfigurationError
1381- from charmsupport.hookenv import log, ERROR
1382- def post_mount_hook():
1383- stop_service('myservice')
1384- def post_mount_hook():
1385- start_service('myservice')
1386-
1387- if __name__ == '__main__':
1388- try:
1389- configure_volume(before_change=pre_mount_hook,
1390- after_change=post_mount_hook)
1391- except VolumeConfigurationError:
1392- log('Storage could not be configured', ERROR)
1393-'''
1394-
1395-# XXX: Known limitations
1396-# - fstab is neither consulted nor updated
1397-
1398-import os
1399-from charmhelpers.core import hookenv
1400-from charmhelpers.core import host
1401-import yaml
1402-
1403-
1404-MOUNT_BASE = '/srv/juju/volumes'
1405-
1406-
1407-class VolumeConfigurationError(Exception):
1408- '''Volume configuration data is missing or invalid'''
1409- pass
1410-
1411-
1412-def get_config():
1413- '''Gather and sanity-check volume configuration data'''
1414- volume_config = {}
1415- config = hookenv.config()
1416-
1417- errors = False
1418-
1419- if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
1420- volume_config['ephemeral'] = True
1421- else:
1422- volume_config['ephemeral'] = False
1423-
1424- try:
1425- volume_map = yaml.safe_load(config.get('volume-map', '{}'))
1426- except yaml.YAMLError as e:
1427- hookenv.log("Error parsing YAML volume-map: {}".format(e),
1428- hookenv.ERROR)
1429- errors = True
1430- if volume_map is None:
1431- # probably an empty string
1432- volume_map = {}
1433- elif not isinstance(volume_map, dict):
1434- hookenv.log("Volume-map should be a dictionary, not {}".format(
1435- type(volume_map)))
1436- errors = True
1437-
1438- volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME'])
1439- if volume_config['device'] and volume_config['ephemeral']:
1440- # asked for ephemeral storage but also defined a volume ID
1441- hookenv.log('A volume is defined for this unit, but ephemeral '
1442- 'storage was requested', hookenv.ERROR)
1443- errors = True
1444- elif not volume_config['device'] and not volume_config['ephemeral']:
1445- # asked for permanent storage but did not define volume ID
1446- hookenv.log('Ephemeral storage was requested, but there is no volume '
1447- 'defined for this unit.', hookenv.ERROR)
1448- errors = True
1449-
1450- unit_mount_name = hookenv.local_unit().replace('/', '-')
1451- volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
1452-
1453- if errors:
1454- return None
1455- return volume_config
1456-
1457-
1458-def mount_volume(config):
1459- if os.path.exists(config['mountpoint']):
1460- if not os.path.isdir(config['mountpoint']):
1461- hookenv.log('Not a directory: {}'.format(config['mountpoint']))
1462- raise VolumeConfigurationError()
1463- else:
1464- host.mkdir(config['mountpoint'])
1465- if os.path.ismount(config['mountpoint']):
1466- unmount_volume(config)
1467- if not host.mount(config['device'], config['mountpoint'], persist=True):
1468- raise VolumeConfigurationError()
1469-
1470-
1471-def unmount_volume(config):
1472- if os.path.ismount(config['mountpoint']):
1473- if not host.umount(config['mountpoint'], persist=True):
1474- raise VolumeConfigurationError()
1475-
1476-
1477-def managed_mounts():
1478- '''List of all mounted managed volumes'''
1479- return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
1480-
1481-
1482-def configure_volume(before_change=lambda: None, after_change=lambda: None):
1483- '''Set up storage (or don't) according to the charm's volume configuration.
1484- Returns the mount point or "ephemeral". before_change and after_change
1485- are optional functions to be called if the volume configuration changes.
1486- '''
1487-
1488- config = get_config()
1489- if not config:
1490- hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
1491- raise VolumeConfigurationError()
1492-
1493- if config['ephemeral']:
1494- if os.path.ismount(config['mountpoint']):
1495- before_change()
1496- unmount_volume(config)
1497- after_change()
1498- return 'ephemeral'
1499- else:
1500- # persistent storage
1501- if os.path.ismount(config['mountpoint']):
1502- mounts = dict(managed_mounts())
1503- if mounts.get(config['mountpoint']) != config['device']:
1504- before_change()
1505- unmount_volume(config)
1506- mount_volume(config)
1507- after_change()
1508- else:
1509- before_change()
1510- mount_volume(config)
1511- after_change()
1512- return config['mountpoint']
1513
1514=== removed directory 'lib/charm-helpers/charmhelpers/contrib/hahelpers'
1515=== removed file 'lib/charm-helpers/charmhelpers/contrib/hahelpers/__init__.py'
1516=== removed file 'lib/charm-helpers/charmhelpers/contrib/hahelpers/apache.py'
1517--- lib/charm-helpers/charmhelpers/contrib/hahelpers/apache.py 2013-07-18 06:08:49 +0000
1518+++ lib/charm-helpers/charmhelpers/contrib/hahelpers/apache.py 1970-01-01 00:00:00 +0000
1519@@ -1,58 +0,0 @@
1520-#
1521-# Copyright 2012 Canonical Ltd.
1522-#
1523-# This file is sourced from lp:openstack-charm-helpers
1524-#
1525-# Authors:
1526-# James Page <james.page@ubuntu.com>
1527-# Adam Gandelman <adamg@ubuntu.com>
1528-#
1529-
1530-import subprocess
1531-
1532-from charmhelpers.core.hookenv import (
1533- config as config_get,
1534- relation_get,
1535- relation_ids,
1536- related_units as relation_list,
1537- log,
1538- INFO,
1539-)
1540-
1541-
1542-def get_cert():
1543- cert = config_get('ssl_cert')
1544- key = config_get('ssl_key')
1545- if not (cert and key):
1546- log("Inspecting identity-service relations for SSL certificate.",
1547- level=INFO)
1548- cert = key = None
1549- for r_id in relation_ids('identity-service'):
1550- for unit in relation_list(r_id):
1551- if not cert:
1552- cert = relation_get('ssl_cert',
1553- rid=r_id, unit=unit)
1554- if not key:
1555- key = relation_get('ssl_key',
1556- rid=r_id, unit=unit)
1557- return (cert, key)
1558-
1559-
1560-def get_ca_cert():
1561- ca_cert = None
1562- log("Inspecting identity-service relations for CA SSL certificate.",
1563- level=INFO)
1564- for r_id in relation_ids('identity-service'):
1565- for unit in relation_list(r_id):
1566- if not ca_cert:
1567- ca_cert = relation_get('ca_cert',
1568- rid=r_id, unit=unit)
1569- return ca_cert
1570-
1571-
1572-def install_ca_cert(ca_cert):
1573- if ca_cert:
1574- with open('/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt',
1575- 'w') as crt:
1576- crt.write(ca_cert)
1577- subprocess.check_call(['update-ca-certificates', '--fresh'])
1578
1579=== removed file 'lib/charm-helpers/charmhelpers/contrib/hahelpers/ceph.py'
1580--- lib/charm-helpers/charmhelpers/contrib/hahelpers/ceph.py 2013-07-18 06:08:49 +0000
1581+++ lib/charm-helpers/charmhelpers/contrib/hahelpers/ceph.py 1970-01-01 00:00:00 +0000
1582@@ -1,278 +0,0 @@
1583-#
1584-# Copyright 2012 Canonical Ltd.
1585-#
1586-# This file is sourced from lp:openstack-charm-helpers
1587-#
1588-# Authors:
1589-# James Page <james.page@ubuntu.com>
1590-# Adam Gandelman <adamg@ubuntu.com>
1591-#
1592-
1593-import commands
1594-import os
1595-import shutil
1596-
1597-from subprocess import (
1598- check_call,
1599- check_output,
1600- CalledProcessError
1601-)
1602-
1603-from charmhelpers.core.hookenv import (
1604- relation_get,
1605- relation_ids,
1606- related_units,
1607- log,
1608- INFO,
1609-)
1610-
1611-from charmhelpers.core.host import (
1612- apt_install,
1613- mount,
1614- mounts,
1615- service_start,
1616- service_stop,
1617- umount,
1618-)
1619-
1620-KEYRING = '/etc/ceph/ceph.client.%s.keyring'
1621-KEYFILE = '/etc/ceph/ceph.client.%s.key'
1622-
1623-CEPH_CONF = """[global]
1624- auth supported = %(auth)s
1625- keyring = %(keyring)s
1626- mon host = %(mon_hosts)s
1627-"""
1628-
1629-
1630-def running(service):
1631- # this local util can be dropped as soon the following branch lands
1632- # in lp:charm-helpers
1633- # https://code.launchpad.net/~gandelman-a/charm-helpers/service_running/
1634- try:
1635- output = check_output(['service', service, 'status'])
1636- except CalledProcessError:
1637- return False
1638- else:
1639- if ("start/running" in output or "is running" in output):
1640- return True
1641- else:
1642- return False
1643-
1644-
1645-def install():
1646- ceph_dir = "/etc/ceph"
1647- if not os.path.isdir(ceph_dir):
1648- os.mkdir(ceph_dir)
1649- apt_install('ceph-common', fatal=True)
1650-
1651-
1652-def rbd_exists(service, pool, rbd_img):
1653- (rc, out) = commands.getstatusoutput('rbd list --id %s --pool %s' %
1654- (service, pool))
1655- return rbd_img in out
1656-
1657-
1658-def create_rbd_image(service, pool, image, sizemb):
1659- cmd = [
1660- 'rbd',
1661- 'create',
1662- image,
1663- '--size',
1664- str(sizemb),
1665- '--id',
1666- service,
1667- '--pool',
1668- pool
1669- ]
1670- check_call(cmd)
1671-
1672-
1673-def pool_exists(service, name):
1674- (rc, out) = commands.getstatusoutput("rados --id %s lspools" % service)
1675- return name in out
1676-
1677-
1678-def create_pool(service, name):
1679- cmd = [
1680- 'rados',
1681- '--id',
1682- service,
1683- 'mkpool',
1684- name
1685- ]
1686- check_call(cmd)
1687-
1688-
1689-def keyfile_path(service):
1690- return KEYFILE % service
1691-
1692-
1693-def keyring_path(service):
1694- return KEYRING % service
1695-
1696-
1697-def create_keyring(service, key):
1698- keyring = keyring_path(service)
1699- if os.path.exists(keyring):
1700- log('ceph: Keyring exists at %s.' % keyring, level=INFO)
1701- cmd = [
1702- 'ceph-authtool',
1703- keyring,
1704- '--create-keyring',
1705- '--name=client.%s' % service,
1706- '--add-key=%s' % key
1707- ]
1708- check_call(cmd)
1709- log('ceph: Created new ring at %s.' % keyring, level=INFO)
1710-
1711-
1712-def create_key_file(service, key):
1713- # create a file containing the key
1714- keyfile = keyfile_path(service)
1715- if os.path.exists(keyfile):
1716- log('ceph: Keyfile exists at %s.' % keyfile, level=INFO)
1717- fd = open(keyfile, 'w')
1718- fd.write(key)
1719- fd.close()
1720- log('ceph: Created new keyfile at %s.' % keyfile, level=INFO)
1721-
1722-
1723-def get_ceph_nodes():
1724- hosts = []
1725- for r_id in relation_ids('ceph'):
1726- for unit in related_units(r_id):
1727- hosts.append(relation_get('private-address', unit=unit, rid=r_id))
1728- return hosts
1729-
1730-
1731-def configure(service, key, auth):
1732- create_keyring(service, key)
1733- create_key_file(service, key)
1734- hosts = get_ceph_nodes()
1735- mon_hosts = ",".join(map(str, hosts))
1736- keyring = keyring_path(service)
1737- with open('/etc/ceph/ceph.conf', 'w') as ceph_conf:
1738- ceph_conf.write(CEPH_CONF % locals())
1739- modprobe_kernel_module('rbd')
1740-
1741-
1742-def image_mapped(image_name):
1743- (rc, out) = commands.getstatusoutput('rbd showmapped')
1744- return image_name in out
1745-
1746-
1747-def map_block_storage(service, pool, image):
1748- cmd = [
1749- 'rbd',
1750- 'map',
1751- '%s/%s' % (pool, image),
1752- '--user',
1753- service,
1754- '--secret',
1755- keyfile_path(service),
1756- ]
1757- check_call(cmd)
1758-
1759-
1760-def filesystem_mounted(fs):
1761- return fs in [f for m, f in mounts()]
1762-
1763-
1764-def make_filesystem(blk_device, fstype='ext4'):
1765- log('ceph: Formatting block device %s as filesystem %s.' %
1766- (blk_device, fstype), level=INFO)
1767- cmd = ['mkfs', '-t', fstype, blk_device]
1768- check_call(cmd)
1769-
1770-
1771-def place_data_on_ceph(service, blk_device, data_src_dst, fstype='ext4'):
1772- # mount block device into /mnt
1773- mount(blk_device, '/mnt')
1774-
1775- # copy data to /mnt
1776- try:
1777- copy_files(data_src_dst, '/mnt')
1778- except:
1779- pass
1780-
1781- # umount block device
1782- umount('/mnt')
1783-
1784- _dir = os.stat(data_src_dst)
1785- uid = _dir.st_uid
1786- gid = _dir.st_gid
1787-
1788- # re-mount where the data should originally be
1789- mount(blk_device, data_src_dst, persist=True)
1790-
1791- # ensure original ownership of new mount.
1792- cmd = ['chown', '-R', '%s:%s' % (uid, gid), data_src_dst]
1793- check_call(cmd)
1794-
1795-
1796-# TODO: re-use
1797-def modprobe_kernel_module(module):
1798- log('ceph: Loading kernel module', level=INFO)
1799- cmd = ['modprobe', module]
1800- check_call(cmd)
1801- cmd = 'echo %s >> /etc/modules' % module
1802- check_call(cmd, shell=True)
1803-
1804-
1805-def copy_files(src, dst, symlinks=False, ignore=None):
1806- for item in os.listdir(src):
1807- s = os.path.join(src, item)
1808- d = os.path.join(dst, item)
1809- if os.path.isdir(s):
1810- shutil.copytree(s, d, symlinks, ignore)
1811- else:
1812- shutil.copy2(s, d)
1813-
1814-
1815-def ensure_ceph_storage(service, pool, rbd_img, sizemb, mount_point,
1816- blk_device, fstype, system_services=[]):
1817- """
1818- To be called from the current cluster leader.
1819- Ensures given pool and RBD image exists, is mapped to a block device,
1820- and the device is formatted and mounted at the given mount_point.
1821-
1822- If formatting a device for the first time, data existing at mount_point
1823- will be migrated to the RBD device before being remounted.
1824-
1825- All services listed in system_services will be stopped prior to data
1826- migration and restarted when complete.
1827- """
1828- # Ensure pool, RBD image, RBD mappings are in place.
1829- if not pool_exists(service, pool):
1830- log('ceph: Creating new pool %s.' % pool, level=INFO)
1831- create_pool(service, pool)
1832-
1833- if not rbd_exists(service, pool, rbd_img):
1834- log('ceph: Creating RBD image (%s).' % rbd_img, level=INFO)
1835- create_rbd_image(service, pool, rbd_img, sizemb)
1836-
1837- if not image_mapped(rbd_img):
1838- log('ceph: Mapping RBD Image as a Block Device.', level=INFO)
1839- map_block_storage(service, pool, rbd_img)
1840-
1841- # make file system
1842- # TODO: What happens if for whatever reason this is run again and
1843- # the data is already in the rbd device and/or is mounted??
1844- # When it is mounted already, it will fail to make the fs
1845- # XXX: This is really sketchy! Need to at least add an fstab entry
1846- # otherwise this hook will blow away existing data if its executed
1847- # after a reboot.
1848- if not filesystem_mounted(mount_point):
1849- make_filesystem(blk_device, fstype)
1850-
1851- for svc in system_services:
1852- if running(svc):
1853- log('Stopping services %s prior to migrating data.' % svc,
1854- level=INFO)
1855- service_stop(svc)
1856-
1857- place_data_on_ceph(service, blk_device, mount_point, fstype)
1858-
1859- for svc in system_services:
1860- service_start(svc)
1861
1862=== removed file 'lib/charm-helpers/charmhelpers/contrib/hahelpers/cluster.py'
1863--- lib/charm-helpers/charmhelpers/contrib/hahelpers/cluster.py 2013-07-18 06:08:49 +0000
1864+++ lib/charm-helpers/charmhelpers/contrib/hahelpers/cluster.py 1970-01-01 00:00:00 +0000
1865@@ -1,180 +0,0 @@
1866-#
1867-# Copyright 2012 Canonical Ltd.
1868-#
1869-# Authors:
1870-# James Page <james.page@ubuntu.com>
1871-# Adam Gandelman <adamg@ubuntu.com>
1872-#
1873-
1874-import subprocess
1875-import os
1876-
1877-from socket import gethostname as get_unit_hostname
1878-
1879-from charmhelpers.core.hookenv import (
1880- log,
1881- relation_ids,
1882- related_units as relation_list,
1883- relation_get,
1884- config as config_get,
1885- INFO,
1886- ERROR,
1887-)
1888-
1889-
1890-class HAIncompleteConfig(Exception):
1891- pass
1892-
1893-
1894-def is_clustered():
1895- for r_id in (relation_ids('ha') or []):
1896- for unit in (relation_list(r_id) or []):
1897- clustered = relation_get('clustered',
1898- rid=r_id,
1899- unit=unit)
1900- if clustered:
1901- return True
1902- return False
1903-
1904-
1905-def is_leader(resource):
1906- cmd = [
1907- "crm", "resource",
1908- "show", resource
1909- ]
1910- try:
1911- status = subprocess.check_output(cmd)
1912- except subprocess.CalledProcessError:
1913- return False
1914- else:
1915- if get_unit_hostname() in status:
1916- return True
1917- else:
1918- return False
1919-
1920-
1921-def peer_units():
1922- peers = []
1923- for r_id in (relation_ids('cluster') or []):
1924- for unit in (relation_list(r_id) or []):
1925- peers.append(unit)
1926- return peers
1927-
1928-
1929-def oldest_peer(peers):
1930- local_unit_no = int(os.getenv('JUJU_UNIT_NAME').split('/')[1])
1931- for peer in peers:
1932- remote_unit_no = int(peer.split('/')[1])
1933- if remote_unit_no < local_unit_no:
1934- return False
1935- return True
1936-
1937-
1938-def eligible_leader(resource):
1939- if is_clustered():
1940- if not is_leader(resource):
1941- log('Deferring action to CRM leader.', level=INFO)
1942- return False
1943- else:
1944- peers = peer_units()
1945- if peers and not oldest_peer(peers):
1946- log('Deferring action to oldest service unit.', level=INFO)
1947- return False
1948- return True
1949-
1950-
1951-def https():
1952- '''
1953- Determines whether enough data has been provided in configuration
1954- or relation data to configure HTTPS
1955- .
1956- returns: boolean
1957- '''
1958- if config_get('use-https') == "yes":
1959- return True
1960- if config_get('ssl_cert') and config_get('ssl_key'):
1961- return True
1962- for r_id in relation_ids('identity-service'):
1963- for unit in relation_list(r_id):
1964- if None not in [
1965- relation_get('https_keystone', rid=r_id, unit=unit),
1966- relation_get('ssl_cert', rid=r_id, unit=unit),
1967- relation_get('ssl_key', rid=r_id, unit=unit),
1968- relation_get('ca_cert', rid=r_id, unit=unit),
1969- ]:
1970- return True
1971- return False
1972-
1973-
1974-def determine_api_port(public_port):
1975- '''
1976- Determine correct API server listening port based on
1977- existence of HTTPS reverse proxy and/or haproxy.
1978-
1979- public_port: int: standard public port for given service
1980-
1981- returns: int: the correct listening port for the API service
1982- '''
1983- i = 0
1984- if len(peer_units()) > 0 or is_clustered():
1985- i += 1
1986- if https():
1987- i += 1
1988- return public_port - (i * 10)
1989-
1990-
1991-def determine_haproxy_port(public_port):
1992- '''
1993- Description: Determine correct proxy listening port based on public IP +
1994- existence of HTTPS reverse proxy.
1995-
1996- public_port: int: standard public port for given service
1997-
1998- returns: int: the correct listening port for the HAProxy service
1999- '''
2000- i = 0
2001- if https():
2002- i += 1
2003- return public_port - (i * 10)
2004-
2005-
2006-def get_hacluster_config():
2007- '''
2008- Obtains all relevant configuration from charm configuration required
2009- for initiating a relation to hacluster:
2010-
2011- ha-bindiface, ha-mcastport, vip, vip_iface, vip_cidr
2012-
2013- returns: dict: A dict containing settings keyed by setting name.
2014- raises: HAIncompleteConfig if settings are missing.
2015- '''
2016- settings = ['ha-bindiface', 'ha-mcastport', 'vip', 'vip_iface', 'vip_cidr']
2017- conf = {}
2018- for setting in settings:
2019- conf[setting] = config_get(setting)
2020- missing = []
2021- [missing.append(s) for s, v in conf.iteritems() if v is None]
2022- if missing:
2023- log('Insufficient config data to configure hacluster.', level=ERROR)
2024- raise HAIncompleteConfig
2025- return conf
2026-
2027-
2028-def canonical_url(configs, vip_setting='vip'):
2029- '''
2030- Returns the correct HTTP URL to this host given the state of HTTPS
2031- configuration and hacluster.
2032-
2033- :configs : OSTemplateRenderer: A config tempating object to inspect for
2034- a complete https context.
2035- :vip_setting: str: Setting in charm config that specifies
2036- VIP address.
2037- '''
2038- scheme = 'http'
2039- if 'https' in configs.complete_contexts():
2040- scheme = 'https'
2041- if is_clustered():
2042- addr = config_get(vip_setting)
2043- else:
2044- addr = get_unit_hostname()
2045- return '%s://%s' % (scheme, addr)
2046
2047=== removed directory 'lib/charm-helpers/charmhelpers/contrib/jujugui'
2048=== removed file 'lib/charm-helpers/charmhelpers/contrib/jujugui/IMPORT'
2049--- lib/charm-helpers/charmhelpers/contrib/jujugui/IMPORT 2013-07-18 06:08:49 +0000
2050+++ lib/charm-helpers/charmhelpers/contrib/jujugui/IMPORT 1970-01-01 00:00:00 +0000
2051@@ -1,4 +0,0 @@
2052-Source: lp:charms/juju-gui
2053-
2054-juju-gui/hooks/utils.py -> charm-helpers/charmhelpers/contrib/jujugui/utils.py
2055-juju-gui/tests/test_utils.py -> charm-helpers/tests/contrib/jujugui/test_utils.py
2056
2057=== removed file 'lib/charm-helpers/charmhelpers/contrib/jujugui/__init__.py'
2058=== removed file 'lib/charm-helpers/charmhelpers/contrib/jujugui/utils.py'
2059--- lib/charm-helpers/charmhelpers/contrib/jujugui/utils.py 2013-07-18 06:08:49 +0000
2060+++ lib/charm-helpers/charmhelpers/contrib/jujugui/utils.py 1970-01-01 00:00:00 +0000
2061@@ -1,602 +0,0 @@
2062-"""Juju GUI charm utilities."""
2063-
2064-__all__ = [
2065- 'AGENT',
2066- 'APACHE',
2067- 'API_PORT',
2068- 'CURRENT_DIR',
2069- 'HAPROXY',
2070- 'IMPROV',
2071- 'JUJU_DIR',
2072- 'JUJU_GUI_DIR',
2073- 'JUJU_GUI_SITE',
2074- 'JUJU_PEM',
2075- 'WEB_PORT',
2076- 'bzr_checkout',
2077- 'chain',
2078- 'cmd_log',
2079- 'fetch_api',
2080- 'fetch_gui',
2081- 'find_missing_packages',
2082- 'first_path_in_dir',
2083- 'get_api_address',
2084- 'get_npm_cache_archive_url',
2085- 'get_release_file_url',
2086- 'get_staging_dependencies',
2087- 'get_zookeeper_address',
2088- 'legacy_juju',
2089- 'log_hook',
2090- 'merge',
2091- 'parse_source',
2092- 'prime_npm_cache',
2093- 'render_to_file',
2094- 'save_or_create_certificates',
2095- 'setup_apache',
2096- 'setup_gui',
2097- 'start_agent',
2098- 'start_gui',
2099- 'start_improv',
2100- 'write_apache_config',
2101-]
2102-
2103-from contextlib import contextmanager
2104-import errno
2105-import json
2106-import os
2107-import logging
2108-import shutil
2109-from subprocess import CalledProcessError
2110-import tempfile
2111-from urlparse import urlparse
2112-
2113-import apt
2114-import tempita
2115-
2116-from launchpadlib.launchpad import Launchpad
2117-from shelltoolbox import (
2118- Serializer,
2119- apt_get_install,
2120- command,
2121- environ,
2122- install_extra_repositories,
2123- run,
2124- script_name,
2125- search_file,
2126- su,
2127-)
2128-from charmhelpers.core.host import (
2129- service_start,
2130-)
2131-from charmhelpers.core.hookenv import (
2132- log,
2133- config,
2134- unit_get,
2135-)
2136-
2137-
2138-AGENT = 'juju-api-agent'
2139-APACHE = 'apache2'
2140-IMPROV = 'juju-api-improv'
2141-HAPROXY = 'haproxy'
2142-
2143-API_PORT = 8080
2144-WEB_PORT = 8000
2145-
2146-CURRENT_DIR = os.getcwd()
2147-JUJU_DIR = os.path.join(CURRENT_DIR, 'juju')
2148-JUJU_GUI_DIR = os.path.join(CURRENT_DIR, 'juju-gui')
2149-JUJU_GUI_SITE = '/etc/apache2/sites-available/juju-gui'
2150-JUJU_GUI_PORTS = '/etc/apache2/ports.conf'
2151-JUJU_PEM = 'juju.includes-private-key.pem'
2152-BUILD_REPOSITORIES = ('ppa:chris-lea/node.js-legacy',)
2153-DEB_BUILD_DEPENDENCIES = (
2154- 'bzr', 'imagemagick', 'make', 'nodejs', 'npm',
2155-)
2156-DEB_STAGE_DEPENDENCIES = (
2157- 'zookeeper',
2158-)
2159-
2160-
2161-# Store the configuration from on invocation to the next.
2162-config_json = Serializer('/tmp/config.json')
2163-# Bazaar checkout command.
2164-bzr_checkout = command('bzr', 'co', '--lightweight')
2165-# Whether or not the charm is deployed using juju-core.
2166-# If juju-core has been used to deploy the charm, an agent.conf file must
2167-# be present in the charm parent directory.
2168-legacy_juju = lambda: not os.path.exists(
2169- os.path.join(CURRENT_DIR, '..', 'agent.conf'))
2170-
2171-
2172-def _get_build_dependencies():
2173- """Install deb dependencies for building."""
2174- log('Installing build dependencies.')
2175- cmd_log(install_extra_repositories(*BUILD_REPOSITORIES))
2176- cmd_log(apt_get_install(*DEB_BUILD_DEPENDENCIES))
2177-
2178-
2179-def get_api_address(unit_dir):
2180- """Return the Juju API address stored in the uniter agent.conf file."""
2181- import yaml # python-yaml is only installed if juju-core is used.
2182- # XXX 2013-03-27 frankban bug=1161443:
2183- # currently the uniter agent.conf file does not include the API
2184- # address. For now retrieve it from the machine agent file.
2185- base_dir = os.path.abspath(os.path.join(unit_dir, '..'))
2186- for dirname in os.listdir(base_dir):
2187- if dirname.startswith('machine-'):
2188- agent_conf = os.path.join(base_dir, dirname, 'agent.conf')
2189- break
2190- else:
2191- raise IOError('Juju agent configuration file not found.')
2192- contents = yaml.load(open(agent_conf))
2193- return contents['apiinfo']['addrs'][0]
2194-
2195-
2196-def get_staging_dependencies():
2197- """Install deb dependencies for the stage (improv) environment."""
2198- log('Installing stage dependencies.')
2199- cmd_log(apt_get_install(*DEB_STAGE_DEPENDENCIES))
2200-
2201-
2202-def first_path_in_dir(directory):
2203- """Return the full path of the first file/dir in *directory*."""
2204- return os.path.join(directory, os.listdir(directory)[0])
2205-
2206-
2207-def _get_by_attr(collection, attr, value):
2208- """Return the first item in collection having attr == value.
2209-
2210- Return None if the item is not found.
2211- """
2212- for item in collection:
2213- if getattr(item, attr) == value:
2214- return item
2215-
2216-
2217-def get_release_file_url(project, series_name, release_version):
2218- """Return the URL of the release file hosted in Launchpad.
2219-
2220- The returned URL points to a release file for the given project, series
2221- name and release version.
2222- The argument *project* is a project object as returned by launchpadlib.
2223- The arguments *series_name* and *release_version* are strings. If
2224- *release_version* is None, the URL of the latest release will be returned.
2225- """
2226- series = _get_by_attr(project.series, 'name', series_name)
2227- if series is None:
2228- raise ValueError('%r: series not found' % series_name)
2229- # Releases are returned by Launchpad in reverse date order.
2230- releases = list(series.releases)
2231- if not releases:
2232- raise ValueError('%r: series does not contain releases' % series_name)
2233- if release_version is not None:
2234- release = _get_by_attr(releases, 'version', release_version)
2235- if release is None:
2236- raise ValueError('%r: release not found' % release_version)
2237- releases = [release]
2238- for release in releases:
2239- for file_ in release.files:
2240- if str(file_).endswith('.tgz'):
2241- return file_.file_link
2242- raise ValueError('%r: file not found' % release_version)
2243-
2244-
2245-def get_zookeeper_address(agent_file_path):
2246- """Retrieve the Zookeeper address contained in the given *agent_file_path*.
2247-
2248- The *agent_file_path* is a path to a file containing a line similar to the
2249- following::
2250-
2251- env JUJU_ZOOKEEPER="address"
2252- """
2253- line = search_file('JUJU_ZOOKEEPER', agent_file_path).strip()
2254- return line.split('=')[1].strip('"')
2255-
2256-
2257-@contextmanager
2258-def log_hook():
2259- """Log when a hook starts and stops its execution.
2260-
2261- Also log to stdout possible CalledProcessError exceptions raised executing
2262- the hook.
2263- """
2264- script = script_name()
2265- log(">>> Entering {}".format(script))
2266- try:
2267- yield
2268- except CalledProcessError as err:
2269- log('Exception caught:')
2270- log(err.output)
2271- raise
2272- finally:
2273- log("<<< Exiting {}".format(script))
2274-
2275-
2276-def parse_source(source):
2277- """Parse the ``juju-gui-source`` option.
2278-
2279- Return a tuple of two elements representing info on how to deploy Juju GUI.
2280- Examples:
2281- - ('stable', None): latest stable release;
2282- - ('stable', '0.1.0'): stable release v0.1.0;
2283- - ('trunk', None): latest trunk release;
2284- - ('trunk', '0.1.0+build.1'): trunk release v0.1.0 bzr revision 1;
2285- - ('branch', 'lp:juju-gui'): release is made from a branch;
2286- - ('url', 'http://example.com/gui'): release from a downloaded file.
2287- """
2288- if source.startswith('url:'):
2289- source = source[4:]
2290- # Support file paths, including relative paths.
2291- if urlparse(source).scheme == '':
2292- if not source.startswith('/'):
2293- source = os.path.join(os.path.abspath(CURRENT_DIR), source)
2294- source = "file://%s" % source
2295- return 'url', source
2296- if source in ('stable', 'trunk'):
2297- return source, None
2298- if source.startswith('lp:') or source.startswith('http://'):
2299- return 'branch', source
2300- if 'build' in source:
2301- return 'trunk', source
2302- return 'stable', source
2303-
2304-
2305-def render_to_file(template_name, context, destination):
2306- """Render the given *template_name* into *destination* using *context*.
2307-
2308- The tempita template language is used to render contents
2309- (see http://pythonpaste.org/tempita/).
2310- The argument *template_name* is the name or path of the template file:
2311- it may be either a path relative to ``../config`` or an absolute path.
2312- The argument *destination* is a file path.
2313- The argument *context* is a dict-like object.
2314- """
2315- template_path = os.path.abspath(template_name)
2316- template = tempita.Template.from_filename(template_path)
2317- with open(destination, 'w') as stream:
2318- stream.write(template.substitute(context))
2319-
2320-
2321-results_log = None
2322-
2323-
2324-def _setupLogging():
2325- global results_log
2326- if results_log is not None:
2327- return
2328- cfg = config()
2329- logging.basicConfig(
2330- filename=cfg['command-log-file'],
2331- level=logging.INFO,
2332- format="%(asctime)s: %(name)s@%(levelname)s %(message)s")
2333- results_log = logging.getLogger('juju-gui')
2334-
2335-
2336-def cmd_log(results):
2337- global results_log
2338- if not results:
2339- return
2340- if results_log is None:
2341- _setupLogging()
2342- # Since 'results' may be multi-line output, start it on a separate line
2343- # from the logger timestamp, etc.
2344- results_log.info('\n' + results)
2345-
2346-
2347-def start_improv(staging_env, ssl_cert_path,
2348- config_path='/etc/init/juju-api-improv.conf'):
2349- """Start a simulated juju environment using ``improv.py``."""
2350- log('Setting up staging start up script.')
2351- context = {
2352- 'juju_dir': JUJU_DIR,
2353- 'keys': ssl_cert_path,
2354- 'port': API_PORT,
2355- 'staging_env': staging_env,
2356- }
2357- render_to_file('config/juju-api-improv.conf.template', context, config_path)
2358- log('Starting the staging backend.')
2359- with su('root'):
2360- service_start(IMPROV)
2361-
2362-
2363-def start_agent(
2364- ssl_cert_path, config_path='/etc/init/juju-api-agent.conf',
2365- read_only=False):
2366- """Start the Juju agent and connect to the current environment."""
2367- # Retrieve the Zookeeper address from the start up script.
2368- unit_dir = os.path.realpath(os.path.join(CURRENT_DIR, '..'))
2369- agent_file = '/etc/init/juju-{0}.conf'.format(os.path.basename(unit_dir))
2370- zookeeper = get_zookeeper_address(agent_file)
2371- log('Setting up API agent start up script.')
2372- context = {
2373- 'juju_dir': JUJU_DIR,
2374- 'keys': ssl_cert_path,
2375- 'port': API_PORT,
2376- 'zookeeper': zookeeper,
2377- 'read_only': read_only
2378- }
2379- render_to_file('config/juju-api-agent.conf.template', context, config_path)
2380- log('Starting API agent.')
2381- with su('root'):
2382- service_start(AGENT)
2383-
2384-
2385-def start_gui(
2386- console_enabled, login_help, readonly, in_staging, ssl_cert_path,
2387- charmworld_url, serve_tests, haproxy_path='/etc/haproxy/haproxy.cfg',
2388- config_js_path=None, secure=True, sandbox=False):
2389- """Set up and start the Juju GUI server."""
2390- with su('root'):
2391- run('chown', '-R', 'ubuntu:', JUJU_GUI_DIR)
2392- # XXX 2013-02-05 frankban bug=1116320:
2393- # External insecure resources are still loaded when testing in the
2394- # debug environment. For now, switch to the production environment if
2395- # the charm is configured to serve tests.
2396- if in_staging and not serve_tests:
2397- build_dirname = 'build-debug'
2398- else:
2399- build_dirname = 'build-prod'
2400- build_dir = os.path.join(JUJU_GUI_DIR, build_dirname)
2401- log('Generating the Juju GUI configuration file.')
2402- is_legacy_juju = legacy_juju()
2403- user, password = None, None
2404- if (is_legacy_juju and in_staging) or sandbox:
2405- user, password = 'admin', 'admin'
2406- else:
2407- user, password = None, None
2408-
2409- api_backend = 'python' if is_legacy_juju else 'go'
2410- if secure:
2411- protocol = 'wss'
2412- else:
2413- log('Running in insecure mode! Port 80 will serve unencrypted.')
2414- protocol = 'ws'
2415-
2416- context = {
2417- 'raw_protocol': protocol,
2418- 'address': unit_get('public-address'),
2419- 'console_enabled': json.dumps(console_enabled),
2420- 'login_help': json.dumps(login_help),
2421- 'password': json.dumps(password),
2422- 'api_backend': json.dumps(api_backend),
2423- 'readonly': json.dumps(readonly),
2424- 'user': json.dumps(user),
2425- 'protocol': json.dumps(protocol),
2426- 'sandbox': json.dumps(sandbox),
2427- 'charmworld_url': json.dumps(charmworld_url),
2428- }
2429- if config_js_path is None:
2430- config_js_path = os.path.join(
2431- build_dir, 'juju-ui', 'assets', 'config.js')
2432- render_to_file('config/config.js.template', context, config_js_path)
2433-
2434- write_apache_config(build_dir, serve_tests)
2435-
2436- log('Generating haproxy configuration file.')
2437- if is_legacy_juju:
2438- # The PyJuju API agent is listening on localhost.
2439- api_address = '127.0.0.1:{0}'.format(API_PORT)
2440- else:
2441- # Retrieve the juju-core API server address.
2442- api_address = get_api_address(os.path.join(CURRENT_DIR, '..'))
2443- context = {
2444- 'api_address': api_address,
2445- 'api_pem': JUJU_PEM,
2446- 'legacy_juju': is_legacy_juju,
2447- 'ssl_cert_path': ssl_cert_path,
2448- # In PyJuju environments, use the same certificate for both HTTPS and
2449- # WebSocket connections. In juju-core the system already has the proper
2450- # certificate installed.
2451- 'web_pem': JUJU_PEM,
2452- 'web_port': WEB_PORT,
2453- 'secure': secure
2454- }
2455- render_to_file('config/haproxy.cfg.template', context, haproxy_path)
2456- log('Starting Juju GUI.')
2457-
2458-
2459-def write_apache_config(build_dir, serve_tests=False):
2460- log('Generating the apache site configuration file.')
2461- context = {
2462- 'port': WEB_PORT,
2463- 'serve_tests': serve_tests,
2464- 'server_root': build_dir,
2465- 'tests_root': os.path.join(JUJU_GUI_DIR, 'test', ''),
2466- }
2467- render_to_file('config/apache-ports.template', context, JUJU_GUI_PORTS)
2468- render_to_file('config/apache-site.template', context, JUJU_GUI_SITE)
2469-
2470-
2471-def get_npm_cache_archive_url(Launchpad=Launchpad):
2472- """Figure out the URL of the most recent NPM cache archive on Launchpad."""
2473- launchpad = Launchpad.login_anonymously('Juju GUI charm', 'production')
2474- project = launchpad.projects['juju-gui']
2475- # Find the URL of the most recently created NPM cache archive.
2476- npm_cache_url = get_release_file_url(project, 'npm-cache', None)
2477- return npm_cache_url
2478-
2479-
2480-def prime_npm_cache(npm_cache_url):
2481- """Download NPM cache archive and prime the NPM cache with it."""
2482- # Download the cache archive and then uncompress it into the NPM cache.
2483- npm_cache_archive = os.path.join(CURRENT_DIR, 'npm-cache.tgz')
2484- cmd_log(run('curl', '-L', '-o', npm_cache_archive, npm_cache_url))
2485- npm_cache_dir = os.path.expanduser('~/.npm')
2486- # The NPM cache directory probably does not exist, so make it if not.
2487- try:
2488- os.mkdir(npm_cache_dir)
2489- except OSError, e:
2490- # If the directory already exists then ignore the error.
2491- if e.errno != errno.EEXIST: # File exists.
2492- raise
2493- uncompress = command('tar', '-x', '-z', '-C', npm_cache_dir, '-f')
2494- cmd_log(uncompress(npm_cache_archive))
2495-
2496-
2497-def fetch_gui(juju_gui_source, logpath):
2498- """Retrieve the Juju GUI release/branch."""
2499- # Retrieve a Juju GUI release.
2500- origin, version_or_branch = parse_source(juju_gui_source)
2501- if origin == 'branch':
2502- # Make sure we have the dependencies necessary for us to actually make
2503- # a build.
2504- _get_build_dependencies()
2505- # Create a release starting from a branch.
2506- juju_gui_source_dir = os.path.join(CURRENT_DIR, 'juju-gui-source')
2507- log('Retrieving Juju GUI source checkout from %s.' % version_or_branch)
2508- cmd_log(run('rm', '-rf', juju_gui_source_dir))
2509- cmd_log(bzr_checkout(version_or_branch, juju_gui_source_dir))
2510- log('Preparing a Juju GUI release.')
2511- logdir = os.path.dirname(logpath)
2512- fd, name = tempfile.mkstemp(prefix='make-distfile-', dir=logdir)
2513- log('Output from "make distfile" sent to %s' % name)
2514- with environ(NO_BZR='1'):
2515- run('make', '-C', juju_gui_source_dir, 'distfile',
2516- stdout=fd, stderr=fd)
2517- release_tarball = first_path_in_dir(
2518- os.path.join(juju_gui_source_dir, 'releases'))
2519- else:
2520- log('Retrieving Juju GUI release.')
2521- if origin == 'url':
2522- file_url = version_or_branch
2523- else:
2524- # Retrieve a release from Launchpad.
2525- launchpad = Launchpad.login_anonymously(
2526- 'Juju GUI charm', 'production')
2527- project = launchpad.projects['juju-gui']
2528- file_url = get_release_file_url(project, origin, version_or_branch)
2529- log('Downloading release file from %s.' % file_url)
2530- release_tarball = os.path.join(CURRENT_DIR, 'release.tgz')
2531- cmd_log(run('curl', '-L', '-o', release_tarball, file_url))
2532- return release_tarball
2533-
2534-
2535-def fetch_api(juju_api_branch):
2536- """Retrieve the Juju branch."""
2537- # Retrieve Juju API source checkout.
2538- log('Retrieving Juju API source checkout.')
2539- cmd_log(run('rm', '-rf', JUJU_DIR))
2540- cmd_log(bzr_checkout(juju_api_branch, JUJU_DIR))
2541-
2542-
2543-def setup_gui(release_tarball):
2544- """Set up Juju GUI."""
2545- # Uncompress the release tarball.
2546- log('Installing Juju GUI.')
2547- release_dir = os.path.join(CURRENT_DIR, 'release')
2548- cmd_log(run('rm', '-rf', release_dir))
2549- os.mkdir(release_dir)
2550- uncompress = command('tar', '-x', '-z', '-C', release_dir, '-f')
2551- cmd_log(uncompress(release_tarball))
2552- # Link the Juju GUI dir to the contents of the release tarball.
2553- cmd_log(run('ln', '-sf', first_path_in_dir(release_dir), JUJU_GUI_DIR))
2554-
2555-
2556-def setup_apache():
2557- """Set up apache."""
2558- log('Setting up apache.')
2559- if not os.path.exists(JUJU_GUI_SITE):
2560- cmd_log(run('touch', JUJU_GUI_SITE))
2561- cmd_log(run('chown', 'ubuntu:', JUJU_GUI_SITE))
2562- cmd_log(
2563- run('ln', '-s', JUJU_GUI_SITE,
2564- '/etc/apache2/sites-enabled/juju-gui'))
2565-
2566- if not os.path.exists(JUJU_GUI_PORTS):
2567- cmd_log(run('touch', JUJU_GUI_PORTS))
2568- cmd_log(run('chown', 'ubuntu:', JUJU_GUI_PORTS))
2569-
2570- with su('root'):
2571- run('a2dissite', 'default')
2572- run('a2ensite', 'juju-gui')
2573-
2574-
2575-def save_or_create_certificates(
2576- ssl_cert_path, ssl_cert_contents, ssl_key_contents):
2577- """Generate the SSL certificates.
2578-
2579- If both *ssl_cert_contents* and *ssl_key_contents* are provided, use them
2580- as certificates; otherwise, generate them.
2581-
2582- Also create a pem file, suitable for use in the haproxy configuration,
2583- concatenating the key and the certificate files.
2584- """
2585- crt_path = os.path.join(ssl_cert_path, 'juju.crt')
2586- key_path = os.path.join(ssl_cert_path, 'juju.key')
2587- if not os.path.exists(ssl_cert_path):
2588- os.makedirs(ssl_cert_path)
2589- if ssl_cert_contents and ssl_key_contents:
2590- # Save the provided certificates.
2591- with open(crt_path, 'w') as cert_file:
2592- cert_file.write(ssl_cert_contents)
2593- with open(key_path, 'w') as key_file:
2594- key_file.write(ssl_key_contents)
2595- else:
2596- # Generate certificates.
2597- # See http://superuser.com/questions/226192/openssl-without-prompt
2598- cmd_log(run(
2599- 'openssl', 'req', '-new', '-newkey', 'rsa:4096',
2600- '-days', '365', '-nodes', '-x509', '-subj',
2601- # These are arbitrary test values for the certificate.
2602- '/C=GB/ST=Juju/L=GUI/O=Ubuntu/CN=juju.ubuntu.com',
2603- '-keyout', key_path, '-out', crt_path))
2604- # Generate the pem file.
2605- pem_path = os.path.join(ssl_cert_path, JUJU_PEM)
2606- if os.path.exists(pem_path):
2607- os.remove(pem_path)
2608- with open(pem_path, 'w') as pem_file:
2609- shutil.copyfileobj(open(key_path), pem_file)
2610- shutil.copyfileobj(open(crt_path), pem_file)
2611-
2612-
2613-def find_missing_packages(*packages):
2614- """Given a list of packages, return the packages which are not installed.
2615- """
2616- cache = apt.Cache()
2617- missing = set()
2618- for pkg_name in packages:
2619- try:
2620- pkg = cache[pkg_name]
2621- except KeyError:
2622- missing.add(pkg_name)
2623- continue
2624- if pkg.is_installed:
2625- continue
2626- missing.add(pkg_name)
2627- return missing
2628-
2629-
2630-## Backend support decorators
2631-
2632-def chain(name):
2633- """Helper method to compose a set of mixin objects into a callable.
2634-
2635- Each method is called in the context of its mixin instance, and its
2636- argument is the Backend instance.
2637- """
2638- # Chain method calls through all implementing mixins.
2639- def method(self):
2640- for mixin in self.mixins:
2641- a_callable = getattr(type(mixin), name, None)
2642- if a_callable:
2643- a_callable(mixin, self)
2644-
2645- method.__name__ = name
2646- return method
2647-
2648-
2649-def merge(name):
2650- """Helper to merge a property from a set of strategy objects
2651- into a unified set.
2652- """
2653- # Return merged property from every providing mixin as a set.
2654- @property
2655- def method(self):
2656- result = set()
2657- for mixin in self.mixins:
2658- segment = getattr(type(mixin), name, None)
2659- if segment and isinstance(segment, (list, tuple, set)):
2660- result |= set(segment)
2661-
2662- return result
2663- return method
2664
2665=== removed directory 'lib/charm-helpers/charmhelpers/contrib/network'
2666=== removed file 'lib/charm-helpers/charmhelpers/contrib/network/__init__.py'
2667=== removed directory 'lib/charm-helpers/charmhelpers/contrib/network/ovs'
2668=== removed file 'lib/charm-helpers/charmhelpers/contrib/network/ovs/__init__.py'
2669--- lib/charm-helpers/charmhelpers/contrib/network/ovs/__init__.py 2013-07-18 06:08:49 +0000
2670+++ lib/charm-helpers/charmhelpers/contrib/network/ovs/__init__.py 1970-01-01 00:00:00 +0000
2671@@ -1,72 +0,0 @@
2672-''' Helpers for interacting with OpenvSwitch '''
2673-import subprocess
2674-import os
2675-from charmhelpers.core.hookenv import (
2676- log, WARNING
2677-)
2678-from charmhelpers.core.host import (
2679- service
2680-)
2681-
2682-
2683-def add_bridge(name):
2684- ''' Add the named bridge to openvswitch '''
2685- log('Creating bridge {}'.format(name))
2686- subprocess.check_call(["ovs-vsctl", "--", "--may-exist", "add-br", name])
2687-
2688-
2689-def del_bridge(name):
2690- ''' Delete the named bridge from openvswitch '''
2691- log('Deleting bridge {}'.format(name))
2692- subprocess.check_call(["ovs-vsctl", "--", "--if-exists", "del-br", name])
2693-
2694-
2695-def add_bridge_port(name, port):
2696- ''' Add a port to the named openvswitch bridge '''
2697- log('Adding port {} to bridge {}'.format(port, name))
2698- subprocess.check_call(["ovs-vsctl", "--", "--may-exist", "add-port",
2699- name, port])
2700- subprocess.check_call(["ip", "link", "set", port, "up"])
2701-
2702-
2703-def del_bridge_port(name, port):
2704- ''' Delete a port from the named openvswitch bridge '''
2705- log('Deleting port {} from bridge {}'.format(port, name))
2706- subprocess.check_call(["ovs-vsctl", "--", "--if-exists", "del-port",
2707- name, port])
2708- subprocess.check_call(["ip", "link", "set", port, "down"])
2709-
2710-
2711-def set_manager(manager):
2712- ''' Set the controller for the local openvswitch '''
2713- log('Setting manager for local ovs to {}'.format(manager))
2714- subprocess.check_call(['ovs-vsctl', 'set-manager',
2715- 'ssl:{}'.format(manager)])
2716-
2717-
2718-CERT_PATH = '/etc/openvswitch/ovsclient-cert.pem'
2719-
2720-
2721-def get_certificate():
2722- ''' Read openvswitch certificate from disk '''
2723- if os.path.exists(CERT_PATH):
2724- log('Reading ovs certificate from {}'.format(CERT_PATH))
2725- with open(CERT_PATH, 'r') as cert:
2726- full_cert = cert.read()
2727- begin_marker = "-----BEGIN CERTIFICATE-----"
2728- end_marker = "-----END CERTIFICATE-----"
2729- begin_index = full_cert.find(begin_marker)
2730- end_index = full_cert.rfind(end_marker)
2731- if end_index == -1 or begin_index == -1:
2732- raise RuntimeError("Certificate does not contain valid begin"
2733- " and end markers.")
2734- full_cert = full_cert[begin_index:(end_index + len(end_marker))]
2735- return full_cert
2736- else:
2737- log('Certificate not found', level=WARNING)
2738- return None
2739-
2740-
2741-def full_restart():
2742- ''' Full restart and reload of openvswitch '''
2743- service('force-reload-kmod', 'openvswitch-switch')
2744
2745=== removed directory 'lib/charm-helpers/charmhelpers/contrib/openstack'
2746=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/__init__.py'
2747=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/context.py'
2748--- lib/charm-helpers/charmhelpers/contrib/openstack/context.py 2013-07-18 06:08:49 +0000
2749+++ lib/charm-helpers/charmhelpers/contrib/openstack/context.py 1970-01-01 00:00:00 +0000
2750@@ -1,271 +0,0 @@
2751-import os
2752-
2753-from base64 import b64decode
2754-
2755-from subprocess import (
2756- check_call
2757-)
2758-
2759-from charmhelpers.core.hookenv import (
2760- config,
2761- local_unit,
2762- log,
2763- relation_get,
2764- relation_ids,
2765- related_units,
2766- unit_get,
2767-)
2768-
2769-from charmhelpers.contrib.hahelpers.cluster import (
2770- determine_api_port,
2771- determine_haproxy_port,
2772- https,
2773- is_clustered,
2774- peer_units,
2775-)
2776-
2777-from charmhelpers.contrib.hahelpers.apache import (
2778- get_cert,
2779- get_ca_cert,
2780-)
2781-
2782-CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
2783-
2784-
2785-class OSContextError(Exception):
2786- pass
2787-
2788-
2789-def context_complete(ctxt):
2790- _missing = []
2791- for k, v in ctxt.iteritems():
2792- if v is None or v == '':
2793- _missing.append(k)
2794- if _missing:
2795- log('Missing required data: %s' % ' '.join(_missing), level='INFO')
2796- return False
2797- return True
2798-
2799-
2800-class OSContextGenerator(object):
2801- interfaces = []
2802-
2803- def __call__(self):
2804- raise NotImplementedError
2805-
2806-
2807-class SharedDBContext(OSContextGenerator):
2808- interfaces = ['shared-db']
2809-
2810- def __call__(self):
2811- log('Generating template context for shared-db')
2812- conf = config()
2813- try:
2814- database = conf['database']
2815- username = conf['database-user']
2816- except KeyError as e:
2817- log('Could not generate shared_db context. '
2818- 'Missing required charm config options: %s.' % e)
2819- raise OSContextError
2820- ctxt = {}
2821- for rid in relation_ids('shared-db'):
2822- for unit in related_units(rid):
2823- ctxt = {
2824- 'database_host': relation_get('db_host', rid=rid,
2825- unit=unit),
2826- 'database': database,
2827- 'database_user': username,
2828- 'database_password': relation_get('password', rid=rid,
2829- unit=unit)
2830- }
2831- if not context_complete(ctxt):
2832- return {}
2833- return ctxt
2834-
2835-
2836-class IdentityServiceContext(OSContextGenerator):
2837- interfaces = ['identity-service']
2838-
2839- def __call__(self):
2840- log('Generating template context for identity-service')
2841- ctxt = {}
2842-
2843- for rid in relation_ids('identity-service'):
2844- for unit in related_units(rid):
2845- ctxt = {
2846- 'service_port': relation_get('service_port', rid=rid,
2847- unit=unit),
2848- 'service_host': relation_get('service_host', rid=rid,
2849- unit=unit),
2850- 'auth_host': relation_get('auth_host', rid=rid, unit=unit),
2851- 'auth_port': relation_get('auth_port', rid=rid, unit=unit),
2852- 'admin_tenant_name': relation_get('service_tenant',
2853- rid=rid, unit=unit),
2854- 'admin_user': relation_get('service_username', rid=rid,
2855- unit=unit),
2856- 'admin_password': relation_get('service_password', rid=rid,
2857- unit=unit),
2858- # XXX: Hard-coded http.
2859- 'service_protocol': 'http',
2860- 'auth_protocol': 'http',
2861- }
2862- if not context_complete(ctxt):
2863- return {}
2864- return ctxt
2865-
2866-
2867-class AMQPContext(OSContextGenerator):
2868- interfaces = ['amqp']
2869-
2870- def __call__(self):
2871- log('Generating template context for amqp')
2872- conf = config()
2873- try:
2874- username = conf['rabbit-user']
2875- vhost = conf['rabbit-vhost']
2876- except KeyError as e:
2877- log('Could not generate shared_db context. '
2878- 'Missing required charm config options: %s.' % e)
2879- raise OSContextError
2880-
2881- ctxt = {}
2882- for rid in relation_ids('amqp'):
2883- for unit in related_units(rid):
2884- if relation_get('clustered', rid=rid, unit=unit):
2885- rabbitmq_host = relation_get('vip', rid=rid, unit=unit)
2886- else:
2887- rabbitmq_host = relation_get('private-address',
2888- rid=rid, unit=unit)
2889- ctxt = {
2890- 'rabbitmq_host': rabbitmq_host,
2891- 'rabbitmq_user': username,
2892- 'rabbitmq_password': relation_get('password', rid=rid,
2893- unit=unit),
2894- 'rabbitmq_virtual_host': vhost,
2895- }
2896- if not context_complete(ctxt):
2897- return {}
2898- return ctxt
2899-
2900-
2901-class CephContext(OSContextGenerator):
2902- interfaces = ['ceph']
2903-
2904- def __call__(self):
2905- '''This generates context for /etc/ceph/ceph.conf templates'''
2906- log('Generating tmeplate context for ceph')
2907- mon_hosts = []
2908- auth = None
2909- for rid in relation_ids('ceph'):
2910- for unit in related_units(rid):
2911- mon_hosts.append(relation_get('private-address', rid=rid,
2912- unit=unit))
2913- auth = relation_get('auth', rid=rid, unit=unit)
2914-
2915- ctxt = {
2916- 'mon_hosts': ' '.join(mon_hosts),
2917- 'auth': auth,
2918- }
2919- if not context_complete(ctxt):
2920- return {}
2921- return ctxt
2922-
2923-
2924-class HAProxyContext(OSContextGenerator):
2925- interfaces = ['cluster']
2926-
2927- def __call__(self):
2928- '''
2929- Builds half a context for the haproxy template, which describes
2930- all peers to be included in the cluster. Each charm needs to include
2931- its own context generator that describes the port mapping.
2932- '''
2933- if not relation_ids('cluster'):
2934- return {}
2935-
2936- cluster_hosts = {}
2937- l_unit = local_unit().replace('/', '-')
2938- cluster_hosts[l_unit] = unit_get('private-address')
2939-
2940- for rid in relation_ids('cluster'):
2941- for unit in related_units(rid):
2942- _unit = unit.replace('/', '-')
2943- addr = relation_get('private-address', rid=rid, unit=unit)
2944- cluster_hosts[_unit] = addr
2945-
2946- ctxt = {
2947- 'units': cluster_hosts,
2948- }
2949- if len(cluster_hosts.keys()) > 1:
2950- # Enable haproxy when we have enough peers.
2951- log('Ensuring haproxy enabled in /etc/default/haproxy.')
2952- with open('/etc/default/haproxy', 'w') as out:
2953- out.write('ENABLED=1\n')
2954- return ctxt
2955- log('HAProxy context is incomplete, this unit has no peers.')
2956- return {}
2957-
2958-
2959-class ApacheSSLContext(OSContextGenerator):
2960- """
2961- Generates a context for an apache vhost configuration that configures
2962- HTTPS reverse proxying for one or many endpoints. Generated context
2963- looks something like:
2964- {
2965- 'namespace': 'cinder',
2966- 'private_address': 'iscsi.mycinderhost.com',
2967- 'endpoints': [(8776, 8766), (8777, 8767)]
2968- }
2969-
2970- The endpoints list consists of a tuples mapping external ports
2971- to internal ports.
2972- """
2973- interfaces = ['https']
2974-
2975- # charms should inherit this context and set external ports
2976- # and service namespace accordingly.
2977- external_ports = []
2978- service_namespace = None
2979-
2980- def enable_modules(self):
2981- cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http']
2982- check_call(cmd)
2983-
2984- def configure_cert(self):
2985- if not os.path.isdir('/etc/apache2/ssl'):
2986- os.mkdir('/etc/apache2/ssl')
2987- ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace)
2988- if not os.path.isdir(ssl_dir):
2989- os.mkdir(ssl_dir)
2990- cert, key = get_cert()
2991- with open(os.path.join(ssl_dir, 'cert'), 'w') as cert_out:
2992- cert_out.write(b64decode(cert))
2993- with open(os.path.join(ssl_dir, 'key'), 'w') as key_out:
2994- key_out.write(b64decode(key))
2995- ca_cert = get_ca_cert()
2996- if ca_cert:
2997- with open(CA_CERT_PATH, 'w') as ca_out:
2998- ca_out.write(b64decode(ca_cert))
2999-
3000- def __call__(self):
3001- if isinstance(self.external_ports, basestring):
3002- self.external_ports = [self.external_ports]
3003- if (not self.external_ports or not https()):
3004- return {}
3005-
3006- self.configure_cert()
3007- self.enable_modules()
3008-
3009- ctxt = {
3010- 'namespace': self.service_namespace,
3011- 'private_address': unit_get('private-address'),
3012- 'endpoints': []
3013- }
3014- for ext_port in self.external_ports:
3015- if peer_units() or is_clustered():
3016- int_port = determine_haproxy_port(ext_port)
3017- else:
3018- int_port = determine_api_port(ext_port)
3019- portmap = (int(ext_port), int(int_port))
3020- ctxt['endpoints'].append(portmap)
3021- return ctxt
3022
3023=== removed directory 'lib/charm-helpers/charmhelpers/contrib/openstack/templates'
3024=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/templates/__init__.py'
3025--- lib/charm-helpers/charmhelpers/contrib/openstack/templates/__init__.py 2013-07-18 06:08:49 +0000
3026+++ lib/charm-helpers/charmhelpers/contrib/openstack/templates/__init__.py 1970-01-01 00:00:00 +0000
3027@@ -1,2 +0,0 @@
3028-# dummy __init__.py to fool syncer into thinking this is a syncable python
3029-# module
3030
3031=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/templates/ceph.conf'
3032--- lib/charm-helpers/charmhelpers/contrib/openstack/templates/ceph.conf 2013-07-18 06:08:49 +0000
3033+++ lib/charm-helpers/charmhelpers/contrib/openstack/templates/ceph.conf 1970-01-01 00:00:00 +0000
3034@@ -1,11 +0,0 @@
3035-###############################################################################
3036-# [ WARNING ]
3037-# cinder configuration file maintained by Juju
3038-# local changes may be overwritten.
3039-###############################################################################
3040-{% if auth %}
3041-[global]
3042- auth_supported = {{ auth }}
3043- keyring = /etc/ceph/$cluster.$name.keyring
3044- mon host = {{ mon_hosts }}
3045-{% endif %}
3046
3047=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/templates/haproxy.cfg'
3048--- lib/charm-helpers/charmhelpers/contrib/openstack/templates/haproxy.cfg 2013-07-18 06:08:49 +0000
3049+++ lib/charm-helpers/charmhelpers/contrib/openstack/templates/haproxy.cfg 1970-01-01 00:00:00 +0000
3050@@ -1,37 +0,0 @@
3051-global
3052- log 127.0.0.1 local0
3053- log 127.0.0.1 local1 notice
3054- maxconn 20000
3055- user haproxy
3056- group haproxy
3057- spread-checks 0
3058-
3059-defaults
3060- log global
3061- mode http
3062- option httplog
3063- option dontlognull
3064- retries 3
3065- timeout queue 1000
3066- timeout connect 1000
3067- timeout client 30000
3068- timeout server 30000
3069-
3070-listen stats :8888
3071- mode http
3072- stats enable
3073- stats hide-version
3074- stats realm Haproxy\ Statistics
3075- stats uri /
3076- stats auth admin:password
3077-
3078-{% if units %}
3079-{% for service, ports in service_ports.iteritems() -%}
3080-listen {{ service }} 0.0.0.0:{{ ports[0] }}
3081- balance roundrobin
3082- option tcplog
3083- {% for unit, address in units.iteritems() -%}
3084- server {{ unit }} {{ address }}:{{ ports[1] }} check
3085- {% endfor %}
3086-{% endfor %}
3087-{% endif %}
3088
3089=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/templates/openstack_https_frontend'
3090--- lib/charm-helpers/charmhelpers/contrib/openstack/templates/openstack_https_frontend 2013-07-18 06:08:49 +0000
3091+++ lib/charm-helpers/charmhelpers/contrib/openstack/templates/openstack_https_frontend 1970-01-01 00:00:00 +0000
3092@@ -1,23 +0,0 @@
3093-{% if endpoints %}
3094-{% for ext, int in endpoints %}
3095-Listen {{ ext }}
3096-NameVirtualHost *:{{ ext }}
3097-<VirtualHost *:{{ ext }}>
3098- ServerName {{ private_address }}
3099- SSLEngine on
3100- SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert
3101- SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key
3102- ProxyPass / http://localhost:{{ int }}/
3103- ProxyPassReverse / http://localhost:{{ int }}/
3104- ProxyPreserveHost on
3105-</VirtualHost>
3106-<Proxy *>
3107- Order deny,allow
3108- Allow from all
3109-</Proxy>
3110-<Location />
3111- Order allow,deny
3112- Allow from all
3113-</Location>
3114-{% endfor %}
3115-{% endif %}
3116
3117=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/templating.py'
3118--- lib/charm-helpers/charmhelpers/contrib/openstack/templating.py 2013-07-18 06:08:49 +0000
3119+++ lib/charm-helpers/charmhelpers/contrib/openstack/templating.py 1970-01-01 00:00:00 +0000
3120@@ -1,261 +0,0 @@
3121-import os
3122-
3123-from charmhelpers.core.host import apt_install
3124-
3125-from charmhelpers.core.hookenv import (
3126- log,
3127- ERROR,
3128- INFO
3129-)
3130-
3131-from charmhelpers.contrib.openstack.utils import OPENSTACK_CODENAMES
3132-
3133-try:
3134- from jinja2 import FileSystemLoader, ChoiceLoader, Environment
3135-except ImportError:
3136- # python-jinja2 may not be installed yet, or we're running unittests.
3137- FileSystemLoader = ChoiceLoader = Environment = None
3138-
3139-
3140-class OSConfigException(Exception):
3141- pass
3142-
3143-
3144-def get_loader(templates_dir, os_release):
3145- """
3146- Create a jinja2.ChoiceLoader containing template dirs up to
3147- and including os_release. If directory template directory
3148- is missing at templates_dir, it will be omitted from the loader.
3149- templates_dir is added to the bottom of the search list as a base
3150- loading dir.
3151-
3152- A charm may also ship a templates dir with this module
3153- and it will be appended to the bottom of the search list, eg:
3154- hooks/charmhelpers/contrib/openstack/templates.
3155-
3156- :param templates_dir: str: Base template directory containing release
3157- sub-directories.
3158- :param os_release : str: OpenStack release codename to construct template
3159- loader.
3160-
3161- :returns : jinja2.ChoiceLoader constructed with a list of
3162- jinja2.FilesystemLoaders, ordered in descending
3163- order by OpenStack release.
3164- """
3165- tmpl_dirs = [(rel, os.path.join(templates_dir, rel))
3166- for rel in OPENSTACK_CODENAMES.itervalues()]
3167-
3168- if not os.path.isdir(templates_dir):
3169- log('Templates directory not found @ %s.' % templates_dir,
3170- level=ERROR)
3171- raise OSConfigException
3172-
3173- # the bottom contains tempaltes_dir and possibly a common templates dir
3174- # shipped with the helper.
3175- loaders = [FileSystemLoader(templates_dir)]
3176- helper_templates = os.path.join(os.path.dirname(__file__), 'templates')
3177- if os.path.isdir(helper_templates):
3178- loaders.append(FileSystemLoader(helper_templates))
3179-
3180- for rel, tmpl_dir in tmpl_dirs:
3181- if os.path.isdir(tmpl_dir):
3182- loaders.insert(0, FileSystemLoader(tmpl_dir))
3183- if rel == os_release:
3184- break
3185- log('Creating choice loader with dirs: %s' %
3186- [l.searchpath for l in loaders], level=INFO)
3187- return ChoiceLoader(loaders)
3188-
3189-
3190-class OSConfigTemplate(object):
3191- """
3192- Associates a config file template with a list of context generators.
3193- Responsible for constructing a template context based on those generators.
3194- """
3195- def __init__(self, config_file, contexts):
3196- self.config_file = config_file
3197-
3198- if hasattr(contexts, '__call__'):
3199- self.contexts = [contexts]
3200- else:
3201- self.contexts = contexts
3202-
3203- self._complete_contexts = []
3204-
3205- def context(self):
3206- ctxt = {}
3207- for context in self.contexts:
3208- _ctxt = context()
3209- if _ctxt:
3210- ctxt.update(_ctxt)
3211- # track interfaces for every complete context.
3212- [self._complete_contexts.append(interface)
3213- for interface in context.interfaces
3214- if interface not in self._complete_contexts]
3215- return ctxt
3216-
3217- def complete_contexts(self):
3218- '''
3219- Return a list of interfaces that have atisfied contexts.
3220- '''
3221- if self._complete_contexts:
3222- return self._complete_contexts
3223- self.context()
3224- return self._complete_contexts
3225-
3226-
3227-class OSConfigRenderer(object):
3228- """
3229- This class provides a common templating system to be used by OpenStack
3230- charms. It is intended to help charms share common code and templates,
3231- and ease the burden of managing config templates across multiple OpenStack
3232- releases.
3233-
3234- Basic usage:
3235- # import some common context generates from charmhelpers
3236- from charmhelpers.contrib.openstack import context
3237-
3238- # Create a renderer object for a specific OS release.
3239- configs = OSConfigRenderer(templates_dir='/tmp/templates',
3240- openstack_release='folsom')
3241- # register some config files with context generators.
3242- configs.register(config_file='/etc/nova/nova.conf',
3243- contexts=[context.SharedDBContext(),
3244- context.AMQPContext()])
3245- configs.register(config_file='/etc/nova/api-paste.ini',
3246- contexts=[context.IdentityServiceContext()])
3247- configs.register(config_file='/etc/haproxy/haproxy.conf',
3248- contexts=[context.HAProxyContext()])
3249- # write out a single config
3250- configs.write('/etc/nova/nova.conf')
3251- # write out all registered configs
3252- configs.write_all()
3253-
3254- Details:
3255-
3256- OpenStack Releases and template loading
3257- ---------------------------------------
3258- When the object is instantiated, it is associated with a specific OS
3259- release. This dictates how the template loader will be constructed.
3260-
3261- The constructed loader attempts to load the template from several places
3262- in the following order:
3263- - from the most recent OS release-specific template dir (if one exists)
3264- - the base templates_dir
3265- - a template directory shipped in the charm with this helper file.
3266-
3267-
3268- For the example above, '/tmp/templates' contains the following structure:
3269- /tmp/templates/nova.conf
3270- /tmp/templates/api-paste.ini
3271- /tmp/templates/grizzly/api-paste.ini
3272- /tmp/templates/havana/api-paste.ini
3273-
3274- Since it was registered with the grizzly release, it first seraches
3275- the grizzly directory for nova.conf, then the templates dir.
3276-
3277- When writing api-paste.ini, it will find the template in the grizzly
3278- directory.
3279-
3280- If the object were created with folsom, it would fall back to the
3281- base templates dir for its api-paste.ini template.
3282-
3283- This system should help manage changes in config files through
3284- openstack releases, allowing charms to fall back to the most recently
3285- updated config template for a given release
3286-
3287- The haproxy.conf, since it is not shipped in the templates dir, will
3288- be loaded from the module directory's template directory, eg
3289- $CHARM/hooks/charmhelpers/contrib/openstack/templates. This allows
3290- us to ship common templates (haproxy, apache) with the helpers.
3291-
3292- Context generators
3293- ---------------------------------------
3294- Context generators are used to generate template contexts during hook
3295- execution. Doing so may require inspecting service relations, charm
3296- config, etc. When registered, a config file is associated with a list
3297- of generators. When a template is rendered and written, all context
3298- generates are called in a chain to generate the context dictionary
3299- passed to the jinja2 template. See context.py for more info.
3300- """
3301- def __init__(self, templates_dir, openstack_release):
3302- if not os.path.isdir(templates_dir):
3303- log('Could not locate templates dir %s' % templates_dir,
3304- level=ERROR)
3305- raise OSConfigException
3306-
3307- self.templates_dir = templates_dir
3308- self.openstack_release = openstack_release
3309- self.templates = {}
3310- self._tmpl_env = None
3311-
3312- if None in [Environment, ChoiceLoader, FileSystemLoader]:
3313- # if this code is running, the object is created pre-install hook.
3314- # jinja2 shouldn't get touched until the module is reloaded on next
3315- # hook execution, with proper jinja2 bits successfully imported.
3316- apt_install('python-jinja2')
3317-
3318- def register(self, config_file, contexts):
3319- """
3320- Register a config file with a list of context generators to be called
3321- during rendering.
3322- """
3323- self.templates[config_file] = OSConfigTemplate(config_file=config_file,
3324- contexts=contexts)
3325- log('Registered config file: %s' % config_file, level=INFO)
3326-
3327- def _get_tmpl_env(self):
3328- if not self._tmpl_env:
3329- loader = get_loader(self.templates_dir, self.openstack_release)
3330- self._tmpl_env = Environment(loader=loader)
3331-
3332- def _get_template(self, template):
3333- self._get_tmpl_env()
3334- template = self._tmpl_env.get_template(template)
3335- log('Loaded template from %s' % template.filename, level=INFO)
3336- return template
3337-
3338- def render(self, config_file):
3339- if config_file not in self.templates:
3340- log('Config not registered: %s' % config_file, level=ERROR)
3341- raise OSConfigException
3342- ctxt = self.templates[config_file].context()
3343- _tmpl = os.path.basename(config_file)
3344- log('Rendering from template: %s' % _tmpl, level=INFO)
3345- template = self._get_template(_tmpl)
3346- return template.render(ctxt)
3347-
3348- def write(self, config_file):
3349- """
3350- Write a single config file, raises if config file is not registered.
3351- """
3352- if config_file not in self.templates:
3353- log('Config not registered: %s' % config_file, level=ERROR)
3354- raise OSConfigException
3355- with open(config_file, 'wb') as out:
3356- out.write(self.render(config_file))
3357- log('Wrote template %s.' % config_file, level=INFO)
3358-
3359- def write_all(self):
3360- """
3361- Write out all registered config files.
3362- """
3363- [self.write(k) for k in self.templates.iterkeys()]
3364-
3365- def set_release(self, openstack_release):
3366- """
3367- Resets the template environment and generates a new template loader
3368- based on a the new openstack release.
3369- """
3370- self._tmpl_env = None
3371- self.openstack_release = openstack_release
3372- self._get_tmpl_env()
3373-
3374- def complete_contexts(self):
3375- '''
3376- Returns a list of context interfaces that yield a complete context.
3377- '''
3378- interfaces = []
3379- [interfaces.extend(i.complete_contexts())
3380- for i in self.templates.itervalues()]
3381- return interfaces
3382
3383=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/utils.py'
3384--- lib/charm-helpers/charmhelpers/contrib/openstack/utils.py 2013-07-18 06:08:49 +0000
3385+++ lib/charm-helpers/charmhelpers/contrib/openstack/utils.py 1970-01-01 00:00:00 +0000
3386@@ -1,271 +0,0 @@
3387-#!/usr/bin/python
3388-
3389-# Common python helper functions used for OpenStack charms.
3390-
3391-from collections import OrderedDict
3392-
3393-import apt_pkg as apt
3394-import subprocess
3395-import os
3396-import sys
3397-
3398-from charmhelpers.core.hookenv import (
3399- config,
3400- log as juju_log,
3401-)
3402-
3403-from charmhelpers.core.host import (
3404- lsb_release,
3405- apt_install
3406-)
3407-
3408-CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
3409-CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
3410-
3411-UBUNTU_OPENSTACK_RELEASE = OrderedDict([
3412- ('oneiric', 'diablo'),
3413- ('precise', 'essex'),
3414- ('quantal', 'folsom'),
3415- ('raring', 'grizzly'),
3416- ('saucy', 'havana'),
3417-])
3418-
3419-
3420-OPENSTACK_CODENAMES = OrderedDict([
3421- ('2011.2', 'diablo'),
3422- ('2012.1', 'essex'),
3423- ('2012.2', 'folsom'),
3424- ('2013.1', 'grizzly'),
3425- ('2013.2', 'havana'),
3426- ('2014.1', 'icehouse'),
3427-])
3428-
3429-# The ugly duckling
3430-SWIFT_CODENAMES = {
3431- '1.4.3': 'diablo',
3432- '1.4.8': 'essex',
3433- '1.7.4': 'folsom',
3434- '1.7.6': 'grizzly',
3435- '1.7.7': 'grizzly',
3436- '1.8.0': 'grizzly',
3437- '1.9.0': 'havana',
3438- '1.9.1': 'havana',
3439-}
3440-
3441-
3442-def error_out(msg):
3443- juju_log("FATAL ERROR: %s" % msg, level='ERROR')
3444- sys.exit(1)
3445-
3446-
3447-def get_os_codename_install_source(src):
3448- '''Derive OpenStack release codename from a given installation source.'''
3449- ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
3450- rel = ''
3451- if src == 'distro':
3452- try:
3453- rel = UBUNTU_OPENSTACK_RELEASE[ubuntu_rel]
3454- except KeyError:
3455- e = 'Could not derive openstack release for '\
3456- 'this Ubuntu release: %s' % ubuntu_rel
3457- error_out(e)
3458- return rel
3459-
3460- if src.startswith('cloud:'):
3461- ca_rel = src.split(':')[1]
3462- ca_rel = ca_rel.split('%s-' % ubuntu_rel)[1].split('/')[0]
3463- return ca_rel
3464-
3465- # Best guess match based on deb string provided
3466- if src.startswith('deb') or src.startswith('ppa'):
3467- for k, v in OPENSTACK_CODENAMES.iteritems():
3468- if v in src:
3469- return v
3470-
3471-
3472-def get_os_version_install_source(src):
3473- codename = get_os_codename_install_source(src)
3474- return get_os_version_codename(codename)
3475-
3476-
3477-def get_os_codename_version(vers):
3478- '''Determine OpenStack codename from version number.'''
3479- try:
3480- return OPENSTACK_CODENAMES[vers]
3481- except KeyError:
3482- e = 'Could not determine OpenStack codename for version %s' % vers
3483- error_out(e)
3484-
3485-
3486-def get_os_version_codename(codename):
3487- '''Determine OpenStack version number from codename.'''
3488- for k, v in OPENSTACK_CODENAMES.iteritems():
3489- if v == codename:
3490- return k
3491- e = 'Could not derive OpenStack version for '\
3492- 'codename: %s' % codename
3493- error_out(e)
3494-
3495-
3496-def get_os_codename_package(package, fatal=True):
3497- '''Derive OpenStack release codename from an installed package.'''
3498- apt.init()
3499- cache = apt.Cache()
3500-
3501- try:
3502- pkg = cache[package]
3503- except:
3504- if not fatal:
3505- return None
3506- # the package is unknown to the current apt cache.
3507- e = 'Could not determine version of package with no installation '\
3508- 'candidate: %s' % package
3509- error_out(e)
3510-
3511- if not pkg.current_ver:
3512- if not fatal:
3513- return None
3514- # package is known, but no version is currently installed.
3515- e = 'Could not determine version of uninstalled package: %s' % package
3516- error_out(e)
3517-
3518- vers = apt.UpstreamVersion(pkg.current_ver.ver_str)
3519-
3520- try:
3521- if 'swift' in pkg.name:
3522- vers = vers[:5]
3523- return SWIFT_CODENAMES[vers]
3524- else:
3525- vers = vers[:6]
3526- return OPENSTACK_CODENAMES[vers]
3527- except KeyError:
3528- e = 'Could not determine OpenStack codename for version %s' % vers
3529- error_out(e)
3530-
3531-
3532-def get_os_version_package(pkg, fatal=True):
3533- '''Derive OpenStack version number from an installed package.'''
3534- codename = get_os_codename_package(pkg, fatal=fatal)
3535-
3536- if not codename:
3537- return None
3538-
3539- if 'swift' in pkg:
3540- vers_map = SWIFT_CODENAMES
3541- else:
3542- vers_map = OPENSTACK_CODENAMES
3543-
3544- for version, cname in vers_map.iteritems():
3545- if cname == codename:
3546- return version
3547- #e = "Could not determine OpenStack version for package: %s" % pkg
3548- #error_out(e)
3549-
3550-
3551-def import_key(keyid):
3552- cmd = "apt-key adv --keyserver keyserver.ubuntu.com " \
3553- "--recv-keys %s" % keyid
3554- try:
3555- subprocess.check_call(cmd.split(' '))
3556- except subprocess.CalledProcessError:
3557- error_out("Error importing repo key %s" % keyid)
3558-
3559-
3560-def configure_installation_source(rel):
3561- '''Configure apt installation source.'''
3562- if rel == 'distro':
3563- return
3564- elif rel[:4] == "ppa:":
3565- src = rel
3566- subprocess.check_call(["add-apt-repository", "-y", src])
3567- elif rel[:3] == "deb":
3568- l = len(rel.split('|'))
3569- if l == 2:
3570- src, key = rel.split('|')
3571- juju_log("Importing PPA key from keyserver for %s" % src)
3572- import_key(key)
3573- elif l == 1:
3574- src = rel
3575- with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
3576- f.write(src)
3577- elif rel[:6] == 'cloud:':
3578- ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
3579- rel = rel.split(':')[1]
3580- u_rel = rel.split('-')[0]
3581- ca_rel = rel.split('-')[1]
3582-
3583- if u_rel != ubuntu_rel:
3584- e = 'Cannot install from Cloud Archive pocket %s on this Ubuntu '\
3585- 'version (%s)' % (ca_rel, ubuntu_rel)
3586- error_out(e)
3587-
3588- if 'staging' in ca_rel:
3589- # staging is just a regular PPA.
3590- os_rel = ca_rel.split('/')[0]
3591- ppa = 'ppa:ubuntu-cloud-archive/%s-staging' % os_rel
3592- cmd = 'add-apt-repository -y %s' % ppa
3593- subprocess.check_call(cmd.split(' '))
3594- return
3595-
3596- # map charm config options to actual archive pockets.
3597- pockets = {
3598- 'folsom': 'precise-updates/folsom',
3599- 'folsom/updates': 'precise-updates/folsom',
3600- 'folsom/proposed': 'precise-proposed/folsom',
3601- 'grizzly': 'precise-updates/grizzly',
3602- 'grizzly/updates': 'precise-updates/grizzly',
3603- 'grizzly/proposed': 'precise-proposed/grizzly',
3604- 'havana': 'precise-updates/havana',
3605- 'havana/updates': 'precise-updates/havana',
3606- 'havana/proposed': 'precise-proposed/havana',
3607- }
3608-
3609- try:
3610- pocket = pockets[ca_rel]
3611- except KeyError:
3612- e = 'Invalid Cloud Archive release specified: %s' % rel
3613- error_out(e)
3614-
3615- src = "deb %s %s main" % (CLOUD_ARCHIVE_URL, pocket)
3616- apt_install('ubuntu-cloud-keyring', fatal=True)
3617-
3618- with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as f:
3619- f.write(src)
3620- else:
3621- error_out("Invalid openstack-release specified: %s" % rel)
3622-
3623-
3624-def save_script_rc(script_path="scripts/scriptrc", **env_vars):
3625- """
3626- Write an rc file in the charm-delivered directory containing
3627- exported environment variables provided by env_vars. Any charm scripts run
3628- outside the juju hook environment can source this scriptrc to obtain
3629- updated config information necessary to perform health checks or
3630- service changes.
3631- """
3632- unit_name = os.getenv('JUJU_UNIT_NAME').replace('/', '-')
3633- juju_rc_path = "/var/lib/juju/units/%s/charm/%s" % (unit_name, script_path)
3634- with open(juju_rc_path, 'wb') as rc_script:
3635- rc_script.write(
3636- "#!/bin/bash\n")
3637- [rc_script.write('export %s=%s\n' % (u, p))
3638- for u, p in env_vars.iteritems() if u != "script_path"]
3639-
3640-
3641-def openstack_upgrade_available(package):
3642- """
3643- Determines if an OpenStack upgrade is available from installation
3644- source, based on version of installed package.
3645-
3646- :param package: str: Name of installed package.
3647-
3648- :returns: bool: : Returns True if configured installation source offers
3649- a newer version of package.
3650-
3651- """
3652-
3653- src = config('openstack-origin')
3654- cur_vers = get_os_version_package(package)
3655- available_vers = get_os_version_install_source(src)
3656- apt.init()
3657- return apt.version_compare(available_vers, cur_vers) == 1
3658
3659=== removed directory 'lib/charm-helpers/charmhelpers/contrib/saltstack'
3660=== removed file 'lib/charm-helpers/charmhelpers/contrib/saltstack/__init__.py'
3661--- lib/charm-helpers/charmhelpers/contrib/saltstack/__init__.py 2013-07-18 06:08:49 +0000
3662+++ lib/charm-helpers/charmhelpers/contrib/saltstack/__init__.py 1970-01-01 00:00:00 +0000
3663@@ -1,126 +0,0 @@
3664-"""Charm Helpers saltstack - declare the state of your machines.
3665-
3666-This helper enables you to declare your machine state, rather than
3667-program it procedurally (and have to test each change to your procedures).
3668-Your install hook can be as simple as:
3669-
3670-{{{
3671-from charmhelpers.contrib.saltstack import (
3672- install_salt_support,
3673- update_machine_state,
3674-)
3675-
3676-
3677-def install():
3678- install_salt_support()
3679- update_machine_state('machine_states/dependencies.yaml')
3680- update_machine_state('machine_states/installed.yaml')
3681-}}}
3682-
3683-and won't need to change (nor will its tests) when you change the machine
3684-state.
3685-
3686-It's using a python package called salt-minion which allows various formats for
3687-specifying resources, such as:
3688-
3689-{{{
3690-/srv/{{ basedir }}:
3691- file.directory:
3692- - group: ubunet
3693- - user: ubunet
3694- - require:
3695- - user: ubunet
3696- - recurse:
3697- - user
3698- - group
3699-
3700-ubunet:
3701- group.present:
3702- - gid: 1500
3703- user.present:
3704- - uid: 1500
3705- - gid: 1500
3706- - createhome: False
3707- - require:
3708- - group: ubunet
3709-}}}
3710-
3711-The docs for all the different state definitions are at:
3712- http://docs.saltstack.com/ref/states/all/
3713-
3714-
3715-TODO:
3716- * Add test helpers which will ensure that machine state definitions
3717- are functionally (but not necessarily logically) correct (ie. getting
3718- salt to parse all state defs.
3719- * Add a link to a public bootstrap charm example / blogpost.
3720- * Find a way to obviate the need to use the grains['charm_dir'] syntax
3721- in templates.
3722-"""
3723-# Copyright 2013 Canonical Ltd.
3724-#
3725-# Authors:
3726-# Charm Helpers Developers <juju@lists.ubuntu.com>
3727-import os
3728-import subprocess
3729-import yaml
3730-
3731-import charmhelpers.core.host
3732-import charmhelpers.core.hookenv
3733-
3734-
3735-charm_dir = os.environ.get('CHARM_DIR', '')
3736-salt_grains_path = '/etc/salt/grains'
3737-
3738-
3739-def install_salt_support(from_ppa=True):
3740- """Installs the salt-minion helper for machine state.
3741-
3742- By default the salt-minion package is installed from
3743- the saltstack PPA. If from_ppa is False you must ensure
3744- that the salt-minion package is available in the apt cache.
3745- """
3746- if from_ppa:
3747- subprocess.check_call([
3748- '/usr/bin/add-apt-repository',
3749- '--yes',
3750- 'ppa:saltstack/salt',
3751- ])
3752- subprocess.check_call(['/usr/bin/apt-get', 'update'])
3753- # We install salt-common as salt-minion would run the salt-minion
3754- # daemon.
3755- charmhelpers.core.host.apt_install('salt-common')
3756-
3757-
3758-def update_machine_state(state_path):
3759- """Update the machine state using the provided state declaration."""
3760- juju_config_2_grains()
3761- subprocess.check_call([
3762- 'salt-call',
3763- '--local',
3764- 'state.template',
3765- state_path,
3766- ])
3767-
3768-
3769-def juju_config_2_grains():
3770- """Insert the juju config as salt grains for use in state templates.
3771-
3772- This includes any current relation-get data, and the charm
3773- directory.
3774- """
3775- config = charmhelpers.core.hookenv.config()
3776-
3777- # Add the charm_dir which we will need to refer to charm
3778- # file resources etc.
3779- config['charm_dir'] = charm_dir
3780- config['local_unit'] = charmhelpers.core.hookenv.local_unit()
3781- config.update(charmhelpers.core.hookenv.relation_get())
3782-
3783- # Don't use non-standard tags for unicode which will not
3784- # work when salt uses yaml.load_safe.
3785- yaml.add_representer(unicode, lambda dumper,
3786- value: dumper.represent_scalar(
3787- u'tag:yaml.org,2002:str', value))
3788- with open(salt_grains_path, "w+") as fp:
3789- fp.write(config.yaml())
3790
3791=== removed directory 'lib/charm-helpers/charmhelpers/contrib/storage'
3792=== removed file 'lib/charm-helpers/charmhelpers/contrib/storage/__init__.py'
3793=== removed directory 'lib/charm-helpers/charmhelpers/contrib/storage/linux'
3794=== removed file 'lib/charm-helpers/charmhelpers/contrib/storage/linux/__init__.py'
3795=== removed file 'lib/charm-helpers/charmhelpers/contrib/storage/linux/loopback.py'
3796--- lib/charm-helpers/charmhelpers/contrib/storage/linux/loopback.py 2013-07-18 06:08:49 +0000
3797+++ lib/charm-helpers/charmhelpers/contrib/storage/linux/loopback.py 1970-01-01 00:00:00 +0000
3798@@ -1,59 +0,0 @@
3799-
3800-import os
3801-import re
3802-
3803-from subprocess import (
3804- check_call,
3805- check_output,
3806-)
3807-
3808-
3809-##################################################
3810-# loopback device helpers.
3811-##################################################
3812-def loopback_devices():
3813- '''
3814- Parse through 'losetup -a' output to determine currently mapped
3815- loopback devices. Output is expected to look like:
3816-
3817- /dev/loop0: [0807]:961814 (/tmp/my.img)
3818-
3819- :returns: dict: a dict mapping {loopback_dev: backing_file}
3820- '''
3821- loopbacks = {}
3822- cmd = ['losetup', '-a']
3823- devs = [d.strip().split(' ') for d in
3824- check_output(cmd).splitlines() if d != '']
3825- for dev, _, f in devs:
3826- loopbacks[dev.replace(':', '')] = re.search('\((\S+)\)', f).groups()[0]
3827- return loopbacks
3828-
3829-
3830-def create_loopback(file_path):
3831- '''
3832- Create a loopback device for a given backing file.
3833-
3834- :returns: str: Full path to new loopback device (eg, /dev/loop0)
3835- '''
3836- cmd = ['losetup', '--find', file_path]
3837- return check_output(cmd).strip()
3838-
3839-
3840-def ensure_loopback_device(path, size):
3841- '''
3842- Ensure a loopback device exists for a given backing file path and size.
3843- If it a loopback device is not mapped to file, a new one will be created.
3844-
3845- TODO: Confirm size of found loopback device.
3846-
3847- :returns: str: Full path to the ensured loopback device (eg, /dev/loop0)
3848- '''
3849- for d, f in loopback_devices().iteritems():
3850- if f == path:
3851- return d
3852-
3853- if not os.path.exists(path):
3854- cmd = ['truncate', '--size', size, path]
3855- check_call(cmd)
3856-
3857- return create_loopback(path)
3858
3859=== removed file 'lib/charm-helpers/charmhelpers/contrib/storage/linux/lvm.py'
3860--- lib/charm-helpers/charmhelpers/contrib/storage/linux/lvm.py 2013-07-18 06:08:49 +0000
3861+++ lib/charm-helpers/charmhelpers/contrib/storage/linux/lvm.py 1970-01-01 00:00:00 +0000
3862@@ -1,88 +0,0 @@
3863-from subprocess import (
3864- CalledProcessError,
3865- check_call,
3866- check_output,
3867- Popen,
3868- PIPE,
3869-)
3870-
3871-
3872-##################################################
3873-# LVM helpers.
3874-##################################################
3875-def deactivate_lvm_volume_group(block_device):
3876- '''
3877- Deactivate any volume gruop associated with an LVM physical volume.
3878-
3879- :param block_device: str: Full path to LVM physical volume
3880- '''
3881- vg = list_lvm_volume_group(block_device)
3882- if vg:
3883- cmd = ['vgchange', '-an', vg]
3884- check_call(cmd)
3885-
3886-
3887-def is_lvm_physical_volume(block_device):
3888- '''
3889- Determine whether a block device is initialized as an LVM PV.
3890-
3891- :param block_device: str: Full path of block device to inspect.
3892-
3893- :returns: boolean: True if block device is a PV, False if not.
3894- '''
3895- try:
3896- check_output(['pvdisplay', block_device])
3897- return True
3898- except CalledProcessError:
3899- return False
3900-
3901-
3902-def remove_lvm_physical_volume(block_device):
3903- '''
3904- Remove LVM PV signatures from a given block device.
3905-
3906- :param block_device: str: Full path of block device to scrub.
3907- '''
3908- p = Popen(['pvremove', '-ff', block_device],
3909- stdin=PIPE)
3910- p.communicate(input='y\n')
3911-
3912-
3913-def list_lvm_volume_group(block_device):
3914- '''
3915- List LVM volume group associated with a given block device.
3916-
3917- Assumes block device is a valid LVM PV.
3918-
3919- :param block_device: str: Full path of block device to inspect.
3920-
3921- :returns: str: Name of volume group associated with block device or None
3922- '''
3923- vg = None
3924- pvd = check_output(['pvdisplay', block_device]).splitlines()
3925- for l in pvd:
3926- if l.strip().startswith('VG Name'):
3927- vg = ' '.join(l.split()).split(' ').pop()
3928- return vg
3929-
3930-
3931-def create_lvm_physical_volume(block_device):
3932- '''
3933- Initialize a block device as an LVM physical volume.
3934-
3935- :param block_device: str: Full path of block device to initialize.
3936-
3937- '''
3938- check_call(['pvcreate', block_device])
3939-
3940-
3941-def create_lvm_volume_group(volume_group, block_device):
3942- '''
3943- Create an LVM volume group backed by a given block device.
3944-
3945- Assumes block device has already been initialized as an LVM PV.
3946-
3947- :param volume_group: str: Name of volume group to create.
3948- :block_device: str: Full path of PV-initialized block device.
3949- '''
3950- check_call(['vgcreate', volume_group, block_device])
3951
3952=== removed file 'lib/charm-helpers/charmhelpers/contrib/storage/linux/utils.py'
3953--- lib/charm-helpers/charmhelpers/contrib/storage/linux/utils.py 2013-07-18 06:08:49 +0000
3954+++ lib/charm-helpers/charmhelpers/contrib/storage/linux/utils.py 1970-01-01 00:00:00 +0000
3955@@ -1,25 +0,0 @@
3956-from os import stat
3957-from stat import S_ISBLK
3958-
3959-from subprocess import (
3960- check_call
3961-)
3962-
3963-
3964-def is_block_device(path):
3965- '''
3966- Confirm device at path is a valid block device node.
3967-
3968- :returns: boolean: True if path is a block device, False if not.
3969- '''
3970- return S_ISBLK(stat(path).st_mode)
3971-
3972-
3973-def zap_disk(block_device):
3974- '''
3975- Clear a block device of partition table. Relies on sgdisk, which is
3976- installed as pat of the 'gdisk' package in Ubuntu.
3977-
3978- :param block_device: str: Full path of block device to clean.
3979- '''
3980- check_call(['sgdisk', '--zap-all', block_device])
3981
3982=== removed directory 'lib/charm-helpers/charmhelpers/contrib/templating'
3983=== removed file 'lib/charm-helpers/charmhelpers/contrib/templating/__init__.py'
3984=== removed file 'lib/charm-helpers/charmhelpers/contrib/templating/pyformat.py'
3985--- lib/charm-helpers/charmhelpers/contrib/templating/pyformat.py 2013-07-18 06:08:49 +0000
3986+++ lib/charm-helpers/charmhelpers/contrib/templating/pyformat.py 1970-01-01 00:00:00 +0000
3987@@ -1,13 +0,0 @@
3988-'''
3989-Templating using standard Python str.format() method.
3990-'''
3991-
3992-from charmhelpers.core import hookenv
3993-
3994-
3995-def render(template, extra={}, **kwargs):
3996- """Return the template rendered using Python's str.format()."""
3997- context = hookenv.execution_environment()
3998- context.update(extra)
3999- context.update(kwargs)
4000- return template.format(**context)
4001
4002=== removed directory 'lib/charm-helpers/charmhelpers/core'
4003=== removed file 'lib/charm-helpers/charmhelpers/core/__init__.py'
4004=== removed file 'lib/charm-helpers/charmhelpers/core/hookenv.py'
4005--- lib/charm-helpers/charmhelpers/core/hookenv.py 2013-07-18 06:08:49 +0000
4006+++ lib/charm-helpers/charmhelpers/core/hookenv.py 1970-01-01 00:00:00 +0000
4007@@ -1,340 +0,0 @@
4008-"Interactions with the Juju environment"
4009-# Copyright 2013 Canonical Ltd.
4010-#
4011-# Authors:
4012-# Charm Helpers Developers <juju@lists.ubuntu.com>
4013-
4014-import os
4015-import json
4016-import yaml
4017-import subprocess
4018-import UserDict
4019-
4020-CRITICAL = "CRITICAL"
4021-ERROR = "ERROR"
4022-WARNING = "WARNING"
4023-INFO = "INFO"
4024-DEBUG = "DEBUG"
4025-MARKER = object()
4026-
4027-cache = {}
4028-
4029-
4030-def cached(func):
4031- ''' Cache return values for multiple executions of func + args
4032-
4033- For example:
4034-
4035- @cached
4036- def unit_get(attribute):
4037- pass
4038-
4039- unit_get('test')
4040-
4041- will cache the result of unit_get + 'test' for future calls.
4042- '''
4043- def wrapper(*args, **kwargs):
4044- global cache
4045- key = str((func, args, kwargs))
4046- try:
4047- return cache[key]
4048- except KeyError:
4049- res = func(*args, **kwargs)
4050- cache[key] = res
4051- return res
4052- return wrapper
4053-
4054-
4055-def flush(key):
4056- ''' Flushes any entries from function cache where the
4057- key is found in the function+args '''
4058- flush_list = []
4059- for item in cache:
4060- if key in item:
4061- flush_list.append(item)
4062- for item in flush_list:
4063- del cache[item]
4064-
4065-
4066-def log(message, level=None):
4067- "Write a message to the juju log"
4068- command = ['juju-log']
4069- if level:
4070- command += ['-l', level]
4071- command += [message]
4072- subprocess.call(command)
4073-
4074-
4075-class Serializable(UserDict.IterableUserDict):
4076- "Wrapper, an object that can be serialized to yaml or json"
4077-
4078- def __init__(self, obj):
4079- # wrap the object
4080- UserDict.IterableUserDict.__init__(self)
4081- self.data = obj
4082-
4083- def __getattr__(self, attr):
4084- # See if this object has attribute.
4085- if attr in ("json", "yaml", "data"):
4086- return self.__dict__[attr]
4087- # Check for attribute in wrapped object.
4088- got = getattr(self.data, attr, MARKER)
4089- if got is not MARKER:
4090- return got
4091- # Proxy to the wrapped object via dict interface.
4092- try:
4093- return self.data[attr]
4094- except KeyError:
4095- raise AttributeError(attr)
4096-
4097- def __getstate__(self):
4098- # Pickle as a standard dictionary.
4099- return self.data
4100-
4101- def __setstate__(self, state):
4102- # Unpickle into our wrapper.
4103- self.data = state
4104-
4105- def json(self):
4106- "Serialize the object to json"
4107- return json.dumps(self.data)
4108-
4109- def yaml(self):
4110- "Serialize the object to yaml"
4111- return yaml.dump(self.data)
4112-
4113-
4114-def execution_environment():
4115- """A convenient bundling of the current execution context"""
4116- context = {}
4117- context['conf'] = config()
4118- if relation_id():
4119- context['reltype'] = relation_type()
4120- context['relid'] = relation_id()
4121- context['rel'] = relation_get()
4122- context['unit'] = local_unit()
4123- context['rels'] = relations()
4124- context['env'] = os.environ
4125- return context
4126-
4127-
4128-def in_relation_hook():
4129- "Determine whether we're running in a relation hook"
4130- return 'JUJU_RELATION' in os.environ
4131-
4132-
4133-def relation_type():
4134- "The scope for the current relation hook"
4135- return os.environ.get('JUJU_RELATION', None)
4136-
4137-
4138-def relation_id():
4139- "The relation ID for the current relation hook"
4140- return os.environ.get('JUJU_RELATION_ID', None)
4141-
4142-
4143-def local_unit():
4144- "Local unit ID"
4145- return os.environ['JUJU_UNIT_NAME']
4146-
4147-
4148-def remote_unit():
4149- "The remote unit for the current relation hook"
4150- return os.environ['JUJU_REMOTE_UNIT']
4151-
4152-
4153-def service_name():
4154- "The name service group this unit belongs to"
4155- return local_unit().split('/')[0]
4156-
4157-
4158-@cached
4159-def config(scope=None):
4160- "Juju charm configuration"
4161- config_cmd_line = ['config-get']
4162- if scope is not None:
4163- config_cmd_line.append(scope)
4164- config_cmd_line.append('--format=json')
4165- try:
4166- return json.loads(subprocess.check_output(config_cmd_line))
4167- except ValueError:
4168- return None
4169-
4170-
4171-@cached
4172-def relation_get(attribute=None, unit=None, rid=None):
4173- _args = ['relation-get', '--format=json']
4174- if rid:
4175- _args.append('-r')
4176- _args.append(rid)
4177- _args.append(attribute or '-')
4178- if unit:
4179- _args.append(unit)
4180- try:
4181- return json.loads(subprocess.check_output(_args))
4182- except ValueError:
4183- return None
4184-
4185-
4186-def relation_set(relation_id=None, relation_settings={}, **kwargs):
4187- relation_cmd_line = ['relation-set']
4188- if relation_id is not None:
4189- relation_cmd_line.extend(('-r', relation_id))
4190- for k, v in (relation_settings.items() + kwargs.items()):
4191- if v is None:
4192- relation_cmd_line.append('{}='.format(k))
4193- else:
4194- relation_cmd_line.append('{}={}'.format(k, v))
4195- subprocess.check_call(relation_cmd_line)
4196- # Flush cache of any relation-gets for local unit
4197- flush(local_unit())
4198-
4199-
4200-@cached
4201-def relation_ids(reltype=None):
4202- "A list of relation_ids"
4203- reltype = reltype or relation_type()
4204- relid_cmd_line = ['relation-ids', '--format=json']
4205- if reltype is not None:
4206- relid_cmd_line.append(reltype)
4207- return json.loads(subprocess.check_output(relid_cmd_line)) or []
4208- return []
4209-
4210-
4211-@cached
4212-def related_units(relid=None):
4213- "A list of related units"
4214- relid = relid or relation_id()
4215- units_cmd_line = ['relation-list', '--format=json']
4216- if relid is not None:
4217- units_cmd_line.extend(('-r', relid))
4218- return json.loads(subprocess.check_output(units_cmd_line)) or []
4219-
4220-
4221-@cached
4222-def relation_for_unit(unit=None, rid=None):
4223- "Get the json represenation of a unit's relation"
4224- unit = unit or remote_unit()
4225- relation = relation_get(unit=unit, rid=rid)
4226- for key in relation:
4227- if key.endswith('-list'):
4228- relation[key] = relation[key].split()
4229- relation['__unit__'] = unit
4230- return relation
4231-
4232-
4233-@cached
4234-def relations_for_id(relid=None):
4235- "Get relations of a specific relation ID"
4236- relation_data = []
4237- relid = relid or relation_ids()
4238- for unit in related_units(relid):
4239- unit_data = relation_for_unit(unit, relid)
4240- unit_data['__relid__'] = relid
4241- relation_data.append(unit_data)
4242- return relation_data
4243-
4244-
4245-@cached
4246-def relations_of_type(reltype=None):
4247- "Get relations of a specific type"
4248- relation_data = []
4249- reltype = reltype or relation_type()
4250- for relid in relation_ids(reltype):
4251- for relation in relations_for_id(relid):
4252- relation['__relid__'] = relid
4253- relation_data.append(relation)
4254- return relation_data
4255-
4256-
4257-@cached
4258-def relation_types():
4259- "Get a list of relation types supported by this charm"
4260- charmdir = os.environ.get('CHARM_DIR', '')
4261- mdf = open(os.path.join(charmdir, 'metadata.yaml'))
4262- md = yaml.safe_load(mdf)
4263- rel_types = []
4264- for key in ('provides', 'requires', 'peers'):
4265- section = md.get(key)
4266- if section:
4267- rel_types.extend(section.keys())
4268- mdf.close()
4269- return rel_types
4270-
4271-
4272-@cached
4273-def relations():
4274- rels = {}
4275- for reltype in relation_types():
4276- relids = {}
4277- for relid in relation_ids(reltype):
4278- units = {local_unit(): relation_get(unit=local_unit(), rid=relid)}
4279- for unit in related_units(relid):
4280- reldata = relation_get(unit=unit, rid=relid)
4281- units[unit] = reldata
4282- relids[relid] = units
4283- rels[reltype] = relids
4284- return rels
4285-
4286-
4287-def open_port(port, protocol="TCP"):
4288- "Open a service network port"
4289- _args = ['open-port']
4290- _args.append('{}/{}'.format(port, protocol))
4291- subprocess.check_call(_args)
4292-
4293-
4294-def close_port(port, protocol="TCP"):
4295- "Close a service network port"
4296- _args = ['close-port']
4297- _args.append('{}/{}'.format(port, protocol))
4298- subprocess.check_call(_args)
4299-
4300-
4301-@cached
4302-def unit_get(attribute):
4303- _args = ['unit-get', '--format=json', attribute]
4304- try:
4305- return json.loads(subprocess.check_output(_args))
4306- except ValueError:
4307- return None
4308-
4309-
4310-def unit_private_ip():
4311- return unit_get('private-address')
4312-
4313-
4314-class UnregisteredHookError(Exception):
4315- pass
4316-
4317-
4318-class Hooks(object):
4319- def __init__(self):
4320- super(Hooks, self).__init__()
4321- self._hooks = {}
4322-
4323- def register(self, name, function):
4324- self._hooks[name] = function
4325-
4326- def execute(self, args):
4327- hook_name = os.path.basename(args[0])
4328- if hook_name in self._hooks:
4329- self._hooks[hook_name]()
4330- else:
4331- raise UnregisteredHookError(hook_name)
4332-
4333- def hook(self, *hook_names):
4334- def wrapper(decorated):
4335- for hook_name in hook_names:
4336- self.register(hook_name, decorated)
4337- else:
4338- self.register(decorated.__name__, decorated)
4339- if '_' in decorated.__name__:
4340- self.register(
4341- decorated.__name__.replace('_', '-'), decorated)
4342- return decorated
4343- return wrapper
4344-
4345-
4346-def charm_dir():
4347- return os.environ.get('CHARM_DIR')
4348
4349=== removed file 'lib/charm-helpers/charmhelpers/core/host.py'
4350--- lib/charm-helpers/charmhelpers/core/host.py 2013-07-18 06:08:49 +0000
4351+++ lib/charm-helpers/charmhelpers/core/host.py 1970-01-01 00:00:00 +0000
4352@@ -1,272 +0,0 @@
4353-"""Tools for working with the host system"""
4354-# Copyright 2012 Canonical Ltd.
4355-#
4356-# Authors:
4357-# Nick Moffitt <nick.moffitt@canonical.com>
4358-# Matthew Wedgwood <matthew.wedgwood@canonical.com>
4359-
4360-import apt_pkg
4361-import os
4362-import pwd
4363-import grp
4364-import subprocess
4365-import hashlib
4366-
4367-from collections import OrderedDict
4368-
4369-from hookenv import log, execution_environment
4370-
4371-
4372-def service_start(service_name):
4373- service('start', service_name)
4374-
4375-
4376-def service_stop(service_name):
4377- service('stop', service_name)
4378-
4379-
4380-def service_restart(service_name):
4381- service('restart', service_name)
4382-
4383-
4384-def service_reload(service_name, restart_on_failure=False):
4385- if not service('reload', service_name) and restart_on_failure:
4386- service('restart', service_name)
4387-
4388-
4389-def service(action, service_name):
4390- cmd = ['service', service_name, action]
4391- return subprocess.call(cmd) == 0
4392-
4393-
4394-def service_running(service):
4395- try:
4396- output = subprocess.check_output(['service', service, 'status'])
4397- except subprocess.CalledProcessError:
4398- return False
4399- else:
4400- if ("start/running" in output or "is running" in output):
4401- return True
4402- else:
4403- return False
4404-
4405-
4406-def adduser(username, password=None, shell='/bin/bash', system_user=False):
4407- """Add a user"""
4408- try:
4409- user_info = pwd.getpwnam(username)
4410- log('user {0} already exists!'.format(username))
4411- except KeyError:
4412- log('creating user {0}'.format(username))
4413- cmd = ['useradd']
4414- if system_user or password is None:
4415- cmd.append('--system')
4416- else:
4417- cmd.extend([
4418- '--create-home',
4419- '--shell', shell,
4420- '--password', password,
4421- ])
4422- cmd.append(username)
4423- subprocess.check_call(cmd)
4424- user_info = pwd.getpwnam(username)
4425- return user_info
4426-
4427-
4428-def add_user_to_group(username, group):
4429- """Add a user to a group"""
4430- cmd = [
4431- 'gpasswd', '-a',
4432- username,
4433- group
4434- ]
4435- log("Adding user {} to group {}".format(username, group))
4436- subprocess.check_call(cmd)
4437-
4438-
4439-def rsync(from_path, to_path, flags='-r', options=None):
4440- """Replicate the contents of a path"""
4441- context = execution_environment()
4442- options = options or ['--delete', '--executability']
4443- cmd = ['/usr/bin/rsync', flags]
4444- cmd.extend(options)
4445- cmd.append(from_path.format(**context))
4446- cmd.append(to_path.format(**context))
4447- log(" ".join(cmd))
4448- return subprocess.check_output(cmd).strip()
4449-
4450-
4451-def symlink(source, destination):
4452- """Create a symbolic link"""
4453- context = execution_environment()
4454- log("Symlinking {} as {}".format(source, destination))
4455- cmd = [
4456- 'ln',
4457- '-sf',
4458- source.format(**context),
4459- destination.format(**context)
4460- ]
4461- subprocess.check_call(cmd)
4462-
4463-
4464-def mkdir(path, owner='root', group='root', perms=0555, force=False):
4465- """Create a directory"""
4466- context = execution_environment()
4467- log("Making dir {} {}:{} {:o}".format(path, owner, group,
4468- perms))
4469- uid = pwd.getpwnam(owner.format(**context)).pw_uid
4470- gid = grp.getgrnam(group.format(**context)).gr_gid
4471- realpath = os.path.abspath(path)
4472- if os.path.exists(realpath):
4473- if force and not os.path.isdir(realpath):
4474- log("Removing non-directory file {} prior to mkdir()".format(path))
4475- os.unlink(realpath)
4476- else:
4477- os.makedirs(realpath, perms)
4478- os.chown(realpath, uid, gid)
4479-
4480-
4481-def write_file(path, content, owner='root', group='root', perms=0444):
4482- """Create or overwrite a file with the contents of a string"""
4483- log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
4484- uid = pwd.getpwnam(owner).pw_uid
4485- gid = grp.getgrnam(group).gr_gid
4486- with open(path, 'w') as target:
4487- os.fchown(target.fileno(), uid, gid)
4488- os.fchmod(target.fileno(), perms)
4489- target.write(content)
4490-
4491-
4492-def filter_installed_packages(packages):
4493- """Returns a list of packages that require installation"""
4494- apt_pkg.init()
4495- cache = apt_pkg.Cache()
4496- _pkgs = []
4497- for package in packages:
4498- try:
4499- p = cache[package]
4500- p.current_ver or _pkgs.append(package)
4501- except KeyError:
4502- log('Package {} has no installation candidate.'.format(package),
4503- level='WARNING')
4504- _pkgs.append(package)
4505- return _pkgs
4506-
4507-
4508-def apt_install(packages, options=None, fatal=False):
4509- """Install one or more packages"""
4510- options = options or []
4511- cmd = ['apt-get', '-y']
4512- cmd.extend(options)
4513- cmd.append('install')
4514- if isinstance(packages, basestring):
4515- cmd.append(packages)
4516- else:
4517- cmd.extend(packages)
4518- log("Installing {} with options: {}".format(packages,
4519- options))
4520- if fatal:
4521- subprocess.check_call(cmd)
4522- else:
4523- subprocess.call(cmd)
4524-
4525-
4526-def apt_update(fatal=False):
4527- """Update local apt cache"""
4528- cmd = ['apt-get', 'update']
4529- if fatal:
4530- subprocess.check_call(cmd)
4531- else:
4532- subprocess.call(cmd)
4533-
4534-
4535-def mount(device, mountpoint, options=None, persist=False):
4536- '''Mount a filesystem'''
4537- cmd_args = ['mount']
4538- if options is not None:
4539- cmd_args.extend(['-o', options])
4540- cmd_args.extend([device, mountpoint])
4541- try:
4542- subprocess.check_output(cmd_args)
4543- except subprocess.CalledProcessError, e:
4544- log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
4545- return False
4546- if persist:
4547- # TODO: update fstab
4548- pass
4549- return True
4550-
4551-
4552-def umount(mountpoint, persist=False):
4553- '''Unmount a filesystem'''
4554- cmd_args = ['umount', mountpoint]
4555- try:
4556- subprocess.check_output(cmd_args)
4557- except subprocess.CalledProcessError, e:
4558- log('Error unmounting {}\n{}'.format(mountpoint, e.output))
4559- return False
4560- if persist:
4561- # TODO: update fstab
4562- pass
4563- return True
4564-
4565-
4566-def mounts():
4567- '''List of all mounted volumes as [[mountpoint,device],[...]]'''
4568- with open('/proc/mounts') as f:
4569- # [['/mount/point','/dev/path'],[...]]
4570- system_mounts = [m[1::-1] for m in [l.strip().split()
4571- for l in f.readlines()]]
4572- return system_mounts
4573-
4574-
4575-def file_hash(path):
4576- ''' Generate a md5 hash of the contents of 'path' or None if not found '''
4577- if os.path.exists(path):
4578- h = hashlib.md5()
4579- with open(path, 'r') as source:
4580- h.update(source.read()) # IGNORE:E1101 - it does have update
4581- return h.hexdigest()
4582- else:
4583- return None
4584-
4585-
4586-def restart_on_change(restart_map):
4587- ''' Restart services based on configuration files changing
4588-
4589- This function is used a decorator, for example
4590-
4591- @restart_on_change({
4592- '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
4593- })
4594- def ceph_client_changed():
4595- ...
4596-
4597- In this example, the cinder-api and cinder-volume services
4598- would be restarted if /etc/ceph/ceph.conf is changed by the
4599- ceph_client_changed function.
4600- '''
4601- def wrap(f):
4602- def wrapped_f(*args):
4603- checksums = {}
4604- for path in restart_map:
4605- checksums[path] = file_hash(path)
4606- f(*args)
4607- restarts = []
4608- for path in restart_map:
4609- if checksums[path] != file_hash(path):
4610- restarts += restart_map[path]
4611- for service_name in list(OrderedDict.fromkeys(restarts)):
4612- service('restart', service_name)
4613- return wrapped_f
4614- return wrap
4615-
4616-
4617-def lsb_release():
4618- '''Return /etc/lsb-release in a dict'''
4619- d = {}
4620- with open('/etc/lsb-release', 'r') as lsb:
4621- for l in lsb:
4622- k, v = l.split('=')
4623- d[k.strip()] = v.strip()
4624- return d
4625
4626=== removed directory 'lib/charm-helpers/charmhelpers/fetch'
4627=== removed file 'lib/charm-helpers/charmhelpers/fetch/__init__.py'
4628--- lib/charm-helpers/charmhelpers/fetch/__init__.py 2013-07-18 06:08:49 +0000
4629+++ lib/charm-helpers/charmhelpers/fetch/__init__.py 1970-01-01 00:00:00 +0000
4630@@ -1,152 +0,0 @@
4631-import importlib
4632-from yaml import safe_load
4633-from charmhelpers.core.host import (
4634- apt_install,
4635- apt_update,
4636- filter_installed_packages,
4637- lsb_release
4638-)
4639-from urlparse import (
4640- urlparse,
4641- urlunparse,
4642-)
4643-import subprocess
4644-from charmhelpers.core.hookenv import (
4645- config,
4646- log,
4647-)
4648-
4649-CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
4650-deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
4651-"""
4652-PROPOSED_POCKET = """# Proposed
4653-deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
4654-"""
4655-
4656-
4657-def add_source(source, key=None):
4658- if ((source.startswith('ppa:') or
4659- source.startswith('http:'))):
4660- subprocess.check_call(['add-apt-repository', source])
4661- elif source.startswith('cloud:'):
4662- apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
4663- fatal=True)
4664- pocket = source.split(':')[-1]
4665- with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
4666- apt.write(CLOUD_ARCHIVE.format(pocket))
4667- elif source == 'proposed':
4668- release = lsb_release()['DISTRIB_CODENAME']
4669- with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
4670- apt.write(PROPOSED_POCKET.format(release))
4671- if key:
4672- subprocess.check_call(['apt-key', 'import', key])
4673-
4674-
4675-class SourceConfigError(Exception):
4676- pass
4677-
4678-
4679-def configure_sources(update=False,
4680- sources_var='install_sources',
4681- keys_var='install_keys'):
4682- """
4683- Configure multiple sources from charm configuration
4684-
4685- Example config:
4686- install_sources:
4687- - "ppa:foo"
4688- - "http://example.com/repo precise main"
4689- install_keys:
4690- - null
4691- - "a1b2c3d4"
4692-
4693- Note that 'null' (a.k.a. None) should not be quoted.
4694- """
4695- sources = safe_load(config(sources_var))
4696- keys = safe_load(config(keys_var))
4697- if isinstance(sources, basestring) and isinstance(keys, basestring):
4698- add_source(sources, keys)
4699- else:
4700- if not len(sources) == len(keys):
4701- msg = 'Install sources and keys lists are different lengths'
4702- raise SourceConfigError(msg)
4703- for src_num in range(len(sources)):
4704- add_source(sources[src_num], keys[src_num])
4705- if update:
4706- apt_update(fatal=True)
4707-
4708-# The order of this list is very important. Handlers should be listed in from
4709-# least- to most-specific URL matching.
4710-FETCH_HANDLERS = (
4711- 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
4712-)
4713-
4714-
4715-class UnhandledSource(Exception):
4716- pass
4717-
4718-
4719-def install_remote(source):
4720- """
4721- Install a file tree from a remote source
4722-
4723- The specified source should be a url of the form:
4724- scheme://[host]/path[#[option=value][&...]]
4725-
4726- Schemes supported are based on this modules submodules
4727- Options supported are submodule-specific"""
4728- # We ONLY check for True here because can_handle may return a string
4729- # explaining why it can't handle a given source.
4730- handlers = [h for h in plugins() if h.can_handle(source) is True]
4731- for handler in handlers:
4732- try:
4733- installed_to = handler.install(source)
4734- except UnhandledSource:
4735- pass
4736- if not installed_to:
4737- raise UnhandledSource("No handler found for source {}".format(source))
4738- return installed_to
4739-
4740-
4741-def install_from_config(config_var_name):
4742- charm_config = config()
4743- source = charm_config[config_var_name]
4744- return install_remote(source)
4745-
4746-
4747-class BaseFetchHandler(object):
4748- """Base class for FetchHandler implementations in fetch plugins"""
4749- def can_handle(self, source):
4750- """Returns True if the source can be handled. Otherwise returns
4751- a string explaining why it cannot"""
4752- return "Wrong source type"
4753-
4754- def install(self, source):
4755- """Try to download and unpack the source. Return the path to the
4756- unpacked files or raise UnhandledSource."""
4757- raise UnhandledSource("Wrong source type {}".format(source))
4758-
4759- def parse_url(self, url):
4760- return urlparse(url)
4761-
4762- def base_url(self, url):
4763- """Return url without querystring or fragment"""
4764- parts = list(self.parse_url(url))
4765- parts[4:] = ['' for i in parts[4:]]
4766- return urlunparse(parts)
4767-
4768-
4769-def plugins(fetch_handlers=None):
4770- if not fetch_handlers:
4771- fetch_handlers = FETCH_HANDLERS
4772- plugin_list = []
4773- for handler_name in fetch_handlers:
4774- package, classname = handler_name.rsplit('.', 1)
4775- try:
4776- handler_class = getattr(importlib.import_module(package), classname)
4777- plugin_list.append(handler_class())
4778- except (ImportError, AttributeError):
4779- # Skip missing plugins so that they can be ommitted from
4780- # installation if desired
4781- log("FetchHandler {} not found, skipping plugin".format(handler_name))
4782- return plugin_list
4783
4784=== removed file 'lib/charm-helpers/charmhelpers/fetch/archiveurl.py'
4785--- lib/charm-helpers/charmhelpers/fetch/archiveurl.py 2013-07-18 06:08:49 +0000
4786+++ lib/charm-helpers/charmhelpers/fetch/archiveurl.py 1970-01-01 00:00:00 +0000
4787@@ -1,48 +0,0 @@
4788-import os
4789-import urllib2
4790-from charmhelpers.fetch import (
4791- BaseFetchHandler,
4792- UnhandledSource
4793-)
4794-from charmhelpers.payload.archive import (
4795- get_archive_handler,
4796- extract,
4797-)
4798-from charmhelpers.core.host import mkdir
4799-
4800-
4801-class ArchiveUrlFetchHandler(BaseFetchHandler):
4802- """Handler for archives via generic URLs"""
4803- def can_handle(self, source):
4804- url_parts = self.parse_url(source)
4805- if url_parts.scheme not in ('http', 'https', 'ftp', 'file'):
4806- return "Wrong source type"
4807- if get_archive_handler(self.base_url(source)):
4808- return True
4809- return False
4810-
4811- def download(self, source, dest):
4812- # propogate all exceptions
4813- # URLError, OSError, etc
4814- response = urllib2.urlopen(source)
4815- try:
4816- with open(dest, 'w') as dest_file:
4817- dest_file.write(response.read())
4818- except Exception as e:
4819- if os.path.isfile(dest):
4820- os.unlink(dest)
4821- raise e
4822-
4823- def install(self, source):
4824- url_parts = self.parse_url(source)
4825- dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched')
4826- if not os.path.exists(dest_dir):
4827- mkdir(dest_dir, perms=0755)
4828- dld_file = os.path.join(dest_dir, os.path.basename(url_parts.path))
4829- try:
4830- self.download(source, dld_file)
4831- except urllib2.URLError as e:
4832- raise UnhandledSource(e.reason)
4833- except OSError as e:
4834- raise UnhandledSource(e.strerror)
4835- return extract(dld_file)
4836
4837=== removed directory 'lib/charm-helpers/charmhelpers/payload'
4838=== removed file 'lib/charm-helpers/charmhelpers/payload/__init__.py'
4839--- lib/charm-helpers/charmhelpers/payload/__init__.py 2013-07-18 06:08:49 +0000
4840+++ lib/charm-helpers/charmhelpers/payload/__init__.py 1970-01-01 00:00:00 +0000
4841@@ -1,1 +0,0 @@
4842-"Tools for working with files injected into a charm just before deployment."
4843
4844=== removed file 'lib/charm-helpers/charmhelpers/payload/archive.py'
4845--- lib/charm-helpers/charmhelpers/payload/archive.py 2013-07-18 06:08:49 +0000
4846+++ lib/charm-helpers/charmhelpers/payload/archive.py 1970-01-01 00:00:00 +0000
4847@@ -1,57 +0,0 @@
4848-import os
4849-import tarfile
4850-import zipfile
4851-from charmhelpers.core import (
4852- host,
4853- hookenv,
4854-)
4855-
4856-
4857-class ArchiveError(Exception):
4858- pass
4859-
4860-
4861-def get_archive_handler(archive_name):
4862- if os.path.isfile(archive_name):
4863- if tarfile.is_tarfile(archive_name):
4864- return extract_tarfile
4865- elif zipfile.is_zipfile(archive_name):
4866- return extract_zipfile
4867- else:
4868- # look at the file name
4869- for ext in ('.tar', '.tar.gz', '.tgz', 'tar.bz2', '.tbz2', '.tbz'):
4870- if archive_name.endswith(ext):
4871- return extract_tarfile
4872- for ext in ('.zip', '.jar'):
4873- if archive_name.endswith(ext):
4874- return extract_zipfile
4875-
4876-
4877-def archive_dest_default(archive_name):
4878- archive_file = os.path.basename(archive_name)
4879- return os.path.join(hookenv.charm_dir(), "archives", archive_file)
4880-
4881-
4882-def extract(archive_name, destpath=None):
4883- handler = get_archive_handler(archive_name)
4884- if handler:
4885- if not destpath:
4886- destpath = archive_dest_default(archive_name)
4887- if not os.path.isdir(destpath):
4888- host.mkdir(destpath)
4889- handler(archive_name, destpath)
4890- return destpath
4891- else:
4892- raise ArchiveError("No handler for archive")
4893-
4894-
4895-def extract_tarfile(archive_name, destpath):
4896- "Unpack a tar archive, optionally compressed"
4897- archive = tarfile.open(archive_name)
4898- archive.extractall(destpath)
4899-
4900-
4901-def extract_zipfile(archive_name, destpath):
4902- "Unpack a zip file"
4903- archive = zipfile.ZipFile(archive_name)
4904- archive.extractall(destpath)
4905
4906=== removed file 'lib/charm-helpers/charmhelpers/payload/execd.py'
4907--- lib/charm-helpers/charmhelpers/payload/execd.py 2013-07-18 06:08:49 +0000
4908+++ lib/charm-helpers/charmhelpers/payload/execd.py 1970-01-01 00:00:00 +0000
4909@@ -1,50 +0,0 @@
4910-#!/usr/bin/env python
4911-
4912-import os
4913-import sys
4914-import subprocess
4915-from charmhelpers.core import hookenv
4916-
4917-
4918-def default_execd_dir():
4919- return os.path.join(os.environ['CHARM_DIR'], 'exec.d')
4920-
4921-
4922-def execd_module_paths(execd_dir=None):
4923- """Generate a list of full paths to modules within execd_dir."""
4924- if not execd_dir:
4925- execd_dir = default_execd_dir()
4926-
4927- if not os.path.exists(execd_dir):
4928- return
4929-
4930- for subpath in os.listdir(execd_dir):
4931- module = os.path.join(execd_dir, subpath)
4932- if os.path.isdir(module):
4933- yield module
4934-
4935-
4936-def execd_submodule_paths(command, execd_dir=None):
4937- """Generate a list of full paths to the specified command within exec_dir.
4938- """
4939- for module_path in execd_module_paths(execd_dir):
4940- path = os.path.join(module_path, command)
4941- if os.access(path, os.X_OK) and os.path.isfile(path):
4942- yield path
4943-
4944-
4945-def execd_run(command, execd_dir=None, die_on_error=False, stderr=None):
4946- """Run command for each module within execd_dir which defines it."""
4947- for submodule_path in execd_submodule_paths(command, execd_dir):
4948- try:
4949- subprocess.check_call(submodule_path, shell=True, stderr=stderr)
4950- except subprocess.CalledProcessError as e:
4951- hookenv.log("Error ({}) running {}. Output: {}".format(
4952- e.returncode, e.cmd, e.output))
4953- if die_on_error:
4954- sys.exit(e.returncode)
4955-
4956-
4957-def execd_preinstall(execd_dir=None):
4958- """Run charm-pre-install for each module within execd_dir."""
4959- execd_run('charm-pre-install', execd_dir=execd_dir)
4960
4961=== removed directory 'lib/charm-helpers/debian'
4962=== removed file 'lib/charm-helpers/debian/compat'
4963--- lib/charm-helpers/debian/compat 2013-07-18 06:08:49 +0000
4964+++ lib/charm-helpers/debian/compat 1970-01-01 00:00:00 +0000
4965@@ -1,1 +0,0 @@
4966-7
4967
4968=== removed file 'lib/charm-helpers/debian/control'
4969--- lib/charm-helpers/debian/control 2013-07-18 06:08:49 +0000
4970+++ lib/charm-helpers/debian/control 1970-01-01 00:00:00 +0000
4971@@ -1,20 +0,0 @@
4972-Source: charmhelpers
4973-Maintainer: Matthew Wedgwood <matthew.wedgwood@ubuntu.com>
4974-Section: python
4975-Priority: optional
4976-Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7)
4977-Standards-Version: 3.9.1
4978-
4979-Package: python-charmhelpers
4980-Architecture: all
4981-Depends: ${misc:Depends}, ${python:Depends}
4982-Description: UNKNOWN
4983- ============
4984- CharmHelpers
4985- ============
4986- .
4987- CharmHelpers provides an opinionated set of tools for building Juju
4988- charms that work together. In addition to basic tasks like interact-
4989- ing with the charm environment and the machine it runs on, it also
4990- helps keep you build hooks and establish relations effortlessly.
4991- .
4992
4993=== removed file 'lib/charm-helpers/debian/rules'
4994--- lib/charm-helpers/debian/rules 2013-07-18 06:08:49 +0000
4995+++ lib/charm-helpers/debian/rules 1970-01-01 00:00:00 +0000
4996@@ -1,9 +0,0 @@
4997-#!/usr/bin/make -f
4998-
4999-# This file was automatically generated by stdeb 0.6.0+git at
5000-# Fri, 04 Jan 2013 15:14:11 -0600
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: