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.
Revision history for this message
Jose L. VG (josvaz) wrote :

This has been tested on a juju setup on GCE

Revision history for this message
Pen Gale (pengale) 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.

Revision history for this message
Jose L. VG (josvaz) wrote :

Anything I should be fixing here?

Revision history for this message
Pen Gale (pengale) 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.

Revision history for this message
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
Revision history for this message
Jose L. VG (josvaz) wrote :

No worries, thanks Brad!

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

Revision history for this message
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)
Revision history for this message
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

Got charmhelpers for fetch as apt_install is now there

29. By Jose L. VG

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

28. By Jose L. VG

Result of removing charmhelpers and re-syncing it

27. By Jose L. VG

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

26. By Jose L. VG

Flake8 suggested fixes, including unused imports

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'charm-helpers.yaml'
--- charm-helpers.yaml 1970-01-01 00:00:00 +0000
+++ charm-helpers.yaml 2016-07-29 14:56:08 +0000
@@ -0,0 +1,5 @@
1destination: lib/charmhelpers
2branch: lp:charm-helpers
3include:
4 - core
5 - fetch
06
=== modified file 'hooks/hooks.py'
--- hooks/hooks.py 2016-07-20 10:26:23 +0000
+++ hooks/hooks.py 2016-07-29 14:56:08 +0000
@@ -3,7 +3,6 @@
3# Copyright 2013 Canonical Ltd. All rights reserved.3# Copyright 2013 Canonical Ltd. All rights reserved.
4# Author: Brad Marshall <brad.marshall@canonical.com>4# Author: Brad Marshall <brad.marshall@canonical.com>
55
6import glob
7import os6import os
8import sys7import sys
9import json8import json
@@ -11,14 +10,13 @@
1110
12local_copy = os.path.join(11local_copy = os.path.join(
13 os.path.dirname(os.path.abspath(os.path.dirname(__file__))),12 os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
14 "lib", "charm-helpers")13 "lib")
15if os.path.exists(local_copy) and os.path.isdir(local_copy):14if os.path.exists(local_copy) and os.path.isdir(local_copy):
16 sys.path.insert(0, local_copy)15 sys.path.insert(0, local_copy)
1716
17from charmhelpers.fetch import apt_install
1818
19from charmhelpers.core.host import (19from charmhelpers.core.host import (
20 apt_install,
21 mkdir,
22 service,20 service,
23 service_start,21 service_start,
24 service_stop,22 service_stop,
@@ -29,9 +27,6 @@
29 Hooks,27 Hooks,
30 config,28 config,
31 open_port,29 open_port,
32 relation_get,
33 relation_id,
34 remote_unit,
35)30)
3631
37hook_dir = os.path.abspath(os.path.dirname(__file__))32hook_dir = os.path.abspath(os.path.dirname(__file__))
@@ -61,19 +56,19 @@
61def update_config():56def update_config():
62 from jinja2 import Environment, FileSystemLoader57 from jinja2 import Environment, FileSystemLoader
63 if network_config:58 if network_config:
64 ## Try originally supported JSON formatted config59 # Try originally supported JSON formatted config
65 try:60 try:
66 network = json.loads(network_config)61 network = json.loads(network_config)
67 ## else use YAML (current):62 # else use YAML (current):
68 except ValueError:63 except ValueError:
69 network = yaml.load(network_config)64 network = yaml.load(network_config)
70 else:65 else:
71 network = {}66 network = {}
72 if user_config:67 if user_config:
73 ## Try originally supported JSON formatted config68 # Try originally supported JSON formatted config
74 try:69 try:
75 user = json.loads(user_config)70 user = json.loads(user_config)
76 ## else use YAML (current):71 # else use YAML (current):
77 except ValueError:72 except ValueError:
78 user = yaml.load(user_config)73 user = yaml.load(user_config)
79 else:74 else:
@@ -94,7 +89,8 @@
94 'backlog_msg_only': backlog_msg_only,89 'backlog_msg_only': backlog_msg_only,
95 'backlog_always': backlog_always,90 'backlog_always': backlog_always,
96 }91 }
97 template = template_env.get_template('bip_conf.template').render(templ_vars)92 template = template_env.get_template('bip_conf.template').render(
93 templ_vars)
98 with open(bip_conf, 'w') as bip_conf_config:94 with open(bip_conf, 'w') as bip_conf_config:
99 bip_conf_config.write(str(template))95 bip_conf_config.write(str(template))
10096
10197
=== removed directory 'lib/charm-helpers'
=== removed file 'lib/charm-helpers/LICENSE.txt'
--- lib/charm-helpers/LICENSE.txt 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/LICENSE.txt 1970-01-01 00:00:00 +0000
@@ -1,661 +0,0 @@
1 GNU AFFERO GENERAL PUBLIC LICENSE
2 Version 3, 19 November 2007
3
4 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5 Everyone is permitted to copy and distribute verbatim copies
6 of this license document, but changing it is not allowed.
7
8 Preamble
9
10 The GNU Affero General Public License is a free, copyleft license for
11software and other kinds of works, specifically designed to ensure
12cooperation with the community in the case of network server software.
13
14 The licenses for most software and other practical works are designed
15to take away your freedom to share and change the works. By contrast,
16our General Public Licenses are intended to guarantee your freedom to
17share and change all versions of a program--to make sure it remains free
18software for all its users.
19
20 When we speak of free software, we are referring to freedom, not
21price. Our General Public Licenses are designed to make sure that you
22have the freedom to distribute copies of free software (and charge for
23them if you wish), that you receive source code or can get it if you
24want it, that you can change the software or use pieces of it in new
25free programs, and that you know you can do these things.
26
27 Developers that use our General Public Licenses protect your rights
28with two steps: (1) assert copyright on the software, and (2) offer
29you this License which gives you legal permission to copy, distribute
30and/or modify the software.
31
32 A secondary benefit of defending all users' freedom is that
33improvements made in alternate versions of the program, if they
34receive widespread use, become available for other developers to
35incorporate. Many developers of free software are heartened and
36encouraged by the resulting cooperation. However, in the case of
37software used on network servers, this result may fail to come about.
38The GNU General Public License permits making a modified version and
39letting the public access it on a server without ever releasing its
40source code to the public.
41
42 The GNU Affero General Public License is designed specifically to
43ensure that, in such cases, the modified source code becomes available
44to the community. It requires the operator of a network server to
45provide the source code of the modified version running there to the
46users of that server. Therefore, public use of a modified version, on
47a publicly accessible server, gives the public access to the source
48code of the modified version.
49
50 An older license, called the Affero General Public License and
51published by Affero, was designed to accomplish similar goals. This is
52a different license, not a version of the Affero GPL, but Affero has
53released a new version of the Affero GPL which permits relicensing under
54this license.
55
56 The precise terms and conditions for copying, distribution and
57modification follow.
58
59 TERMS AND CONDITIONS
60
61 0. Definitions.
62
63 "This License" refers to version 3 of the GNU Affero General Public License.
64
65 "Copyright" also means copyright-like laws that apply to other kinds of
66works, such as semiconductor masks.
67
68 "The Program" refers to any copyrightable work licensed under this
69License. Each licensee is addressed as "you". "Licensees" and
70"recipients" may be individuals or organizations.
71
72 To "modify" a work means to copy from or adapt all or part of the work
73in a fashion requiring copyright permission, other than the making of an
74exact copy. The resulting work is called a "modified version" of the
75earlier work or a work "based on" the earlier work.
76
77 A "covered work" means either the unmodified Program or a work based
78on the Program.
79
80 To "propagate" a work means to do anything with it that, without
81permission, would make you directly or secondarily liable for
82infringement under applicable copyright law, except executing it on a
83computer or modifying a private copy. Propagation includes copying,
84distribution (with or without modification), making available to the
85public, and in some countries other activities as well.
86
87 To "convey" a work means any kind of propagation that enables other
88parties to make or receive copies. Mere interaction with a user through
89a computer network, with no transfer of a copy, is not conveying.
90
91 An interactive user interface displays "Appropriate Legal Notices"
92to the extent that it includes a convenient and prominently visible
93feature that (1) displays an appropriate copyright notice, and (2)
94tells the user that there is no warranty for the work (except to the
95extent that warranties are provided), that licensees may convey the
96work under this License, and how to view a copy of this License. If
97the interface presents a list of user commands or options, such as a
98menu, a prominent item in the list meets this criterion.
99
100 1. Source Code.
101
102 The "source code" for a work means the preferred form of the work
103for making modifications to it. "Object code" means any non-source
104form of a work.
105
106 A "Standard Interface" means an interface that either is an official
107standard defined by a recognized standards body, or, in the case of
108interfaces specified for a particular programming language, one that
109is widely used among developers working in that language.
110
111 The "System Libraries" of an executable work include anything, other
112than the work as a whole, that (a) is included in the normal form of
113packaging a Major Component, but which is not part of that Major
114Component, and (b) serves only to enable use of the work with that
115Major Component, or to implement a Standard Interface for which an
116implementation is available to the public in source code form. A
117"Major Component", in this context, means a major essential component
118(kernel, window system, and so on) of the specific operating system
119(if any) on which the executable work runs, or a compiler used to
120produce the work, or an object code interpreter used to run it.
121
122 The "Corresponding Source" for a work in object code form means all
123the source code needed to generate, install, and (for an executable
124work) run the object code and to modify the work, including scripts to
125control those activities. However, it does not include the work's
126System Libraries, or general-purpose tools or generally available free
127programs which are used unmodified in performing those activities but
128which are not part of the work. For example, Corresponding Source
129includes interface definition files associated with source files for
130the work, and the source code for shared libraries and dynamically
131linked subprograms that the work is specifically designed to require,
132such as by intimate data communication or control flow between those
133subprograms and other parts of the work.
134
135 The Corresponding Source need not include anything that users
136can regenerate automatically from other parts of the Corresponding
137Source.
138
139 The Corresponding Source for a work in source code form is that
140same work.
141
142 2. Basic Permissions.
143
144 All rights granted under this License are granted for the term of
145copyright on the Program, and are irrevocable provided the stated
146conditions are met. This License explicitly affirms your unlimited
147permission to run the unmodified Program. The output from running a
148covered work is covered by this License only if the output, given its
149content, constitutes a covered work. This License acknowledges your
150rights of fair use or other equivalent, as provided by copyright law.
151
152 You may make, run and propagate covered works that you do not
153convey, without conditions so long as your license otherwise remains
154in force. You may convey covered works to others for the sole purpose
155of having them make modifications exclusively for you, or provide you
156with facilities for running those works, provided that you comply with
157the terms of this License in conveying all material for which you do
158not control copyright. Those thus making or running the covered works
159for you must do so exclusively on your behalf, under your direction
160and control, on terms that prohibit them from making any copies of
161your copyrighted material outside their relationship with you.
162
163 Conveying under any other circumstances is permitted solely under
164the conditions stated below. Sublicensing is not allowed; section 10
165makes it unnecessary.
166
167 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168
169 No covered work shall be deemed part of an effective technological
170measure under any applicable law fulfilling obligations under article
17111 of the WIPO copyright treaty adopted on 20 December 1996, or
172similar laws prohibiting or restricting circumvention of such
173measures.
174
175 When you convey a covered work, you waive any legal power to forbid
176circumvention of technological measures to the extent such circumvention
177is effected by exercising rights under this License with respect to
178the covered work, and you disclaim any intention to limit operation or
179modification of the work as a means of enforcing, against the work's
180users, your or third parties' legal rights to forbid circumvention of
181technological measures.
182
183 4. Conveying Verbatim Copies.
184
185 You may convey verbatim copies of the Program's source code as you
186receive it, in any medium, provided that you conspicuously and
187appropriately publish on each copy an appropriate copyright notice;
188keep intact all notices stating that this License and any
189non-permissive terms added in accord with section 7 apply to the code;
190keep intact all notices of the absence of any warranty; and give all
191recipients a copy of this License along with the Program.
192
193 You may charge any price or no price for each copy that you convey,
194and you may offer support or warranty protection for a fee.
195
196 5. Conveying Modified Source Versions.
197
198 You may convey a work based on the Program, or the modifications to
199produce it from the Program, in the form of source code under the
200terms of section 4, provided that you also meet all of these conditions:
201
202 a) The work must carry prominent notices stating that you modified
203 it, and giving a relevant date.
204
205 b) The work must carry prominent notices stating that it is
206 released under this License and any conditions added under section
207 7. This requirement modifies the requirement in section 4 to
208 "keep intact all notices".
209
210 c) You must license the entire work, as a whole, under this
211 License to anyone who comes into possession of a copy. This
212 License will therefore apply, along with any applicable section 7
213 additional terms, to the whole of the work, and all its parts,
214 regardless of how they are packaged. This License gives no
215 permission to license the work in any other way, but it does not
216 invalidate such permission if you have separately received it.
217
218 d) If the work has interactive user interfaces, each must display
219 Appropriate Legal Notices; however, if the Program has interactive
220 interfaces that do not display Appropriate Legal Notices, your
221 work need not make them do so.
222
223 A compilation of a covered work with other separate and independent
224works, which are not by their nature extensions of the covered work,
225and which are not combined with it such as to form a larger program,
226in or on a volume of a storage or distribution medium, is called an
227"aggregate" if the compilation and its resulting copyright are not
228used to limit the access or legal rights of the compilation's users
229beyond what the individual works permit. Inclusion of a covered work
230in an aggregate does not cause this License to apply to the other
231parts of the aggregate.
232
233 6. Conveying Non-Source Forms.
234
235 You may convey a covered work in object code form under the terms
236of sections 4 and 5, provided that you also convey the
237machine-readable Corresponding Source under the terms of this License,
238in one of these ways:
239
240 a) Convey the object code in, or embodied in, a physical product
241 (including a physical distribution medium), accompanied by the
242 Corresponding Source fixed on a durable physical medium
243 customarily used for software interchange.
244
245 b) Convey the object code in, or embodied in, a physical product
246 (including a physical distribution medium), accompanied by a
247 written offer, valid for at least three years and valid for as
248 long as you offer spare parts or customer support for that product
249 model, to give anyone who possesses the object code either (1) a
250 copy of the Corresponding Source for all the software in the
251 product that is covered by this License, on a durable physical
252 medium customarily used for software interchange, for a price no
253 more than your reasonable cost of physically performing this
254 conveying of source, or (2) access to copy the
255 Corresponding Source from a network server at no charge.
256
257 c) Convey individual copies of the object code with a copy of the
258 written offer to provide the Corresponding Source. This
259 alternative is allowed only occasionally and noncommercially, and
260 only if you received the object code with such an offer, in accord
261 with subsection 6b.
262
263 d) Convey the object code by offering access from a designated
264 place (gratis or for a charge), and offer equivalent access to the
265 Corresponding Source in the same way through the same place at no
266 further charge. You need not require recipients to copy the
267 Corresponding Source along with the object code. If the place to
268 copy the object code is a network server, the Corresponding Source
269 may be on a different server (operated by you or a third party)
270 that supports equivalent copying facilities, provided you maintain
271 clear directions next to the object code saying where to find the
272 Corresponding Source. Regardless of what server hosts the
273 Corresponding Source, you remain obligated to ensure that it is
274 available for as long as needed to satisfy these requirements.
275
276 e) Convey the object code using peer-to-peer transmission, provided
277 you inform other peers where the object code and Corresponding
278 Source of the work are being offered to the general public at no
279 charge under subsection 6d.
280
281 A separable portion of the object code, whose source code is excluded
282from the Corresponding Source as a System Library, need not be
283included in conveying the object code work.
284
285 A "User Product" is either (1) a "consumer product", which means any
286tangible personal property which is normally used for personal, family,
287or household purposes, or (2) anything designed or sold for incorporation
288into a dwelling. In determining whether a product is a consumer product,
289doubtful cases shall be resolved in favor of coverage. For a particular
290product received by a particular user, "normally used" refers to a
291typical or common use of that class of product, regardless of the status
292of the particular user or of the way in which the particular user
293actually uses, or expects or is expected to use, the product. A product
294is a consumer product regardless of whether the product has substantial
295commercial, industrial or non-consumer uses, unless such uses represent
296the only significant mode of use of the product.
297
298 "Installation Information" for a User Product means any methods,
299procedures, authorization keys, or other information required to install
300and execute modified versions of a covered work in that User Product from
301a modified version of its Corresponding Source. The information must
302suffice to ensure that the continued functioning of the modified object
303code is in no case prevented or interfered with solely because
304modification has been made.
305
306 If you convey an object code work under this section in, or with, or
307specifically for use in, a User Product, and the conveying occurs as
308part of a transaction in which the right of possession and use of the
309User Product is transferred to the recipient in perpetuity or for a
310fixed term (regardless of how the transaction is characterized), the
311Corresponding Source conveyed under this section must be accompanied
312by the Installation Information. But this requirement does not apply
313if neither you nor any third party retains the ability to install
314modified object code on the User Product (for example, the work has
315been installed in ROM).
316
317 The requirement to provide Installation Information does not include a
318requirement to continue to provide support service, warranty, or updates
319for a work that has been modified or installed by the recipient, or for
320the User Product in which it has been modified or installed. Access to a
321network may be denied when the modification itself materially and
322adversely affects the operation of the network or violates the rules and
323protocols for communication across the network.
324
325 Corresponding Source conveyed, and Installation Information provided,
326in accord with this section must be in a format that is publicly
327documented (and with an implementation available to the public in
328source code form), and must require no special password or key for
329unpacking, reading or copying.
330
331 7. Additional Terms.
332
333 "Additional permissions" are terms that supplement the terms of this
334License by making exceptions from one or more of its conditions.
335Additional permissions that are applicable to the entire Program shall
336be treated as though they were included in this License, to the extent
337that they are valid under applicable law. If additional permissions
338apply only to part of the Program, that part may be used separately
339under those permissions, but the entire Program remains governed by
340this License without regard to the additional permissions.
341
342 When you convey a copy of a covered work, you may at your option
343remove any additional permissions from that copy, or from any part of
344it. (Additional permissions may be written to require their own
345removal in certain cases when you modify the work.) You may place
346additional permissions on material, added by you to a covered work,
347for which you have or can give appropriate copyright permission.
348
349 Notwithstanding any other provision of this License, for material you
350add to a covered work, you may (if authorized by the copyright holders of
351that material) supplement the terms of this License with terms:
352
353 a) Disclaiming warranty or limiting liability differently from the
354 terms of sections 15 and 16 of this License; or
355
356 b) Requiring preservation of specified reasonable legal notices or
357 author attributions in that material or in the Appropriate Legal
358 Notices displayed by works containing it; or
359
360 c) Prohibiting misrepresentation of the origin of that material, or
361 requiring that modified versions of such material be marked in
362 reasonable ways as different from the original version; or
363
364 d) Limiting the use for publicity purposes of names of licensors or
365 authors of the material; or
366
367 e) Declining to grant rights under trademark law for use of some
368 trade names, trademarks, or service marks; or
369
370 f) Requiring indemnification of licensors and authors of that
371 material by anyone who conveys the material (or modified versions of
372 it) with contractual assumptions of liability to the recipient, for
373 any liability that these contractual assumptions directly impose on
374 those licensors and authors.
375
376 All other non-permissive additional terms are considered "further
377restrictions" within the meaning of section 10. If the Program as you
378received it, or any part of it, contains a notice stating that it is
379governed by this License along with a term that is a further
380restriction, you may remove that term. If a license document contains
381a further restriction but permits relicensing or conveying under this
382License, you may add to a covered work material governed by the terms
383of that license document, provided that the further restriction does
384not survive such relicensing or conveying.
385
386 If you add terms to a covered work in accord with this section, you
387must place, in the relevant source files, a statement of the
388additional terms that apply to those files, or a notice indicating
389where to find the applicable terms.
390
391 Additional terms, permissive or non-permissive, may be stated in the
392form of a separately written license, or stated as exceptions;
393the above requirements apply either way.
394
395 8. Termination.
396
397 You may not propagate or modify a covered work except as expressly
398provided under this License. Any attempt otherwise to propagate or
399modify it is void, and will automatically terminate your rights under
400this License (including any patent licenses granted under the third
401paragraph of section 11).
402
403 However, if you cease all violation of this License, then your
404license from a particular copyright holder is reinstated (a)
405provisionally, unless and until the copyright holder explicitly and
406finally terminates your license, and (b) permanently, if the copyright
407holder fails to notify you of the violation by some reasonable means
408prior to 60 days after the cessation.
409
410 Moreover, your license from a particular copyright holder is
411reinstated permanently if the copyright holder notifies you of the
412violation by some reasonable means, this is the first time you have
413received notice of violation of this License (for any work) from that
414copyright holder, and you cure the violation prior to 30 days after
415your receipt of the notice.
416
417 Termination of your rights under this section does not terminate the
418licenses of parties who have received copies or rights from you under
419this License. If your rights have been terminated and not permanently
420reinstated, you do not qualify to receive new licenses for the same
421material under section 10.
422
423 9. Acceptance Not Required for Having Copies.
424
425 You are not required to accept this License in order to receive or
426run a copy of the Program. Ancillary propagation of a covered work
427occurring solely as a consequence of using peer-to-peer transmission
428to receive a copy likewise does not require acceptance. However,
429nothing other than this License grants you permission to propagate or
430modify any covered work. These actions infringe copyright if you do
431not accept this License. Therefore, by modifying or propagating a
432covered work, you indicate your acceptance of this License to do so.
433
434 10. Automatic Licensing of Downstream Recipients.
435
436 Each time you convey a covered work, the recipient automatically
437receives a license from the original licensors, to run, modify and
438propagate that work, subject to this License. You are not responsible
439for enforcing compliance by third parties with this License.
440
441 An "entity transaction" is a transaction transferring control of an
442organization, or substantially all assets of one, or subdividing an
443organization, or merging organizations. If propagation of a covered
444work results from an entity transaction, each party to that
445transaction who receives a copy of the work also receives whatever
446licenses to the work the party's predecessor in interest had or could
447give under the previous paragraph, plus a right to possession of the
448Corresponding Source of the work from the predecessor in interest, if
449the predecessor has it or can get it with reasonable efforts.
450
451 You may not impose any further restrictions on the exercise of the
452rights granted or affirmed under this License. For example, you may
453not impose a license fee, royalty, or other charge for exercise of
454rights granted under this License, and you may not initiate litigation
455(including a cross-claim or counterclaim in a lawsuit) alleging that
456any patent claim is infringed by making, using, selling, offering for
457sale, or importing the Program or any portion of it.
458
459 11. Patents.
460
461 A "contributor" is a copyright holder who authorizes use under this
462License of the Program or a work on which the Program is based. The
463work thus licensed is called the contributor's "contributor version".
464
465 A contributor's "essential patent claims" are all patent claims
466owned or controlled by the contributor, whether already acquired or
467hereafter acquired, that would be infringed by some manner, permitted
468by this License, of making, using, or selling its contributor version,
469but do not include claims that would be infringed only as a
470consequence of further modification of the contributor version. For
471purposes of this definition, "control" includes the right to grant
472patent sublicenses in a manner consistent with the requirements of
473this License.
474
475 Each contributor grants you a non-exclusive, worldwide, royalty-free
476patent license under the contributor's essential patent claims, to
477make, use, sell, offer for sale, import and otherwise run, modify and
478propagate the contents of its contributor version.
479
480 In the following three paragraphs, a "patent license" is any express
481agreement or commitment, however denominated, not to enforce a patent
482(such as an express permission to practice a patent or covenant not to
483sue for patent infringement). To "grant" such a patent license to a
484party means to make such an agreement or commitment not to enforce a
485patent against the party.
486
487 If you convey a covered work, knowingly relying on a patent license,
488and the Corresponding Source of the work is not available for anyone
489to copy, free of charge and under the terms of this License, through a
490publicly available network server or other readily accessible means,
491then you must either (1) cause the Corresponding Source to be so
492available, or (2) arrange to deprive yourself of the benefit of the
493patent license for this particular work, or (3) arrange, in a manner
494consistent with the requirements of this License, to extend the patent
495license to downstream recipients. "Knowingly relying" means you have
496actual knowledge that, but for the patent license, your conveying the
497covered work in a country, or your recipient's use of the covered work
498in a country, would infringe one or more identifiable patents in that
499country that you have reason to believe are valid.
500
501 If, pursuant to or in connection with a single transaction or
502arrangement, you convey, or propagate by procuring conveyance of, a
503covered work, and grant a patent license to some of the parties
504receiving the covered work authorizing them to use, propagate, modify
505or convey a specific copy of the covered work, then the patent license
506you grant is automatically extended to all recipients of the covered
507work and works based on it.
508
509 A patent license is "discriminatory" if it does not include within
510the scope of its coverage, prohibits the exercise of, or is
511conditioned on the non-exercise of one or more of the rights that are
512specifically granted under this License. You may not convey a covered
513work if you are a party to an arrangement with a third party that is
514in the business of distributing software, under which you make payment
515to the third party based on the extent of your activity of conveying
516the work, and under which the third party grants, to any of the
517parties who would receive the covered work from you, a discriminatory
518patent license (a) in connection with copies of the covered work
519conveyed by you (or copies made from those copies), or (b) primarily
520for and in connection with specific products or compilations that
521contain the covered work, unless you entered into that arrangement,
522or that patent license was granted, prior to 28 March 2007.
523
524 Nothing in this License shall be construed as excluding or limiting
525any implied license or other defenses to infringement that may
526otherwise be available to you under applicable patent law.
527
528 12. No Surrender of Others' Freedom.
529
530 If conditions are imposed on you (whether by court order, agreement or
531otherwise) that contradict the conditions of this License, they do not
532excuse you from the conditions of this License. If you cannot convey a
533covered work so as to satisfy simultaneously your obligations under this
534License and any other pertinent obligations, then as a consequence you may
535not convey it at all. For example, if you agree to terms that obligate you
536to collect a royalty for further conveying from those to whom you convey
537the Program, the only way you could satisfy both those terms and this
538License would be to refrain entirely from conveying the Program.
539
540 13. Remote Network Interaction; Use with the GNU General Public License.
541
542 Notwithstanding any other provision of this License, if you modify the
543Program, your modified version must prominently offer all users
544interacting with it remotely through a computer network (if your version
545supports such interaction) an opportunity to receive the Corresponding
546Source of your version by providing access to the Corresponding Source
547from a network server at no charge, through some standard or customary
548means of facilitating copying of software. This Corresponding Source
549shall include the Corresponding Source for any work covered by version 3
550of the GNU General Public License that is incorporated pursuant to the
551following paragraph.
552
553 Notwithstanding any other provision of this License, you have
554permission to link or combine any covered work with a work licensed
555under version 3 of the GNU General Public License into a single
556combined work, and to convey the resulting work. The terms of this
557License will continue to apply to the part which is the covered work,
558but the work with which it is combined will remain governed by version
5593 of the GNU General Public License.
560
561 14. Revised Versions of this License.
562
563 The Free Software Foundation may publish revised and/or new versions of
564the GNU Affero General Public License from time to time. Such new versions
565will be similar in spirit to the present version, but may differ in detail to
566address new problems or concerns.
567
568 Each version is given a distinguishing version number. If the
569Program specifies that a certain numbered version of the GNU Affero General
570Public License "or any later version" applies to it, you have the
571option of following the terms and conditions either of that numbered
572version or of any later version published by the Free Software
573Foundation. If the Program does not specify a version number of the
574GNU Affero General Public License, you may choose any version ever published
575by the Free Software Foundation.
576
577 If the Program specifies that a proxy can decide which future
578versions of the GNU Affero General Public License can be used, that proxy's
579public statement of acceptance of a version permanently authorizes you
580to choose that version for the Program.
581
582 Later license versions may give you additional or different
583permissions. However, no additional obligations are imposed on any
584author or copyright holder as a result of your choosing to follow a
585later version.
586
587 15. Disclaimer of Warranty.
588
589 THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597
598 16. Limitation of Liability.
599
600 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608SUCH DAMAGES.
609
610 17. Interpretation of Sections 15 and 16.
611
612 If the disclaimer of warranty and limitation of liability provided
613above cannot be given local legal effect according to their terms,
614reviewing courts shall apply local law that most closely approximates
615an absolute waiver of all civil liability in connection with the
616Program, unless a warranty or assumption of liability accompanies a
617copy of the Program in return for a fee.
618
619 END OF TERMS AND CONDITIONS
620
621 How to Apply These Terms to Your New Programs
622
623 If you develop a new program, and you want it to be of the greatest
624possible use to the public, the best way to achieve this is to make it
625free software which everyone can redistribute and change under these terms.
626
627 To do so, attach the following notices to the program. It is safest
628to attach them to the start of each source file to most effectively
629state the exclusion of warranty; and each file should have at least
630the "copyright" line and a pointer to where the full notice is found.
631
632 <one line to give the program's name and a brief idea of what it does.>
633 Copyright (C) <year> <name of author>
634
635 This program is free software: you can redistribute it and/or modify
636 it under the terms of the GNU Affero General Public License as published by
637 the Free Software Foundation, either version 3 of the License, or
638 (at your option) any later version.
639
640 This program is distributed in the hope that it will be useful,
641 but WITHOUT ANY WARRANTY; without even the implied warranty of
642 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 GNU Affero General Public License for more details.
644
645 You should have received a copy of the GNU Affero General Public License
646 along with this program. If not, see <http://www.gnu.org/licenses/>.
647
648Also add information on how to contact you by electronic and paper mail.
649
650 If your software can interact with users remotely through a computer
651network, you should also make sure that it provides a way for users to
652get its source. For example, if your program is a web application, its
653interface could display a "Source" link that leads users to an archive
654of the code. There are many ways you could offer source, and different
655solutions will be better for different programs; see section 13 for the
656specific requirements.
657
658 You should also get your employer (if you work as a programmer) or school,
659if any, to sign a "copyright disclaimer" for the program, if necessary.
660For more information on this, and how to apply and follow the GNU AGPL, see
661<http://www.gnu.org/licenses/>.
6620
=== removed file 'lib/charm-helpers/MANIFEST.in'
--- lib/charm-helpers/MANIFEST.in 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/MANIFEST.in 1970-01-01 00:00:00 +0000
@@ -1,6 +0,0 @@
1include *.txt
2include Makefile
3include VERSION
4include MANIFEST.in
5include scripts/*
6recursive-include debian *
70
=== removed file 'lib/charm-helpers/Makefile'
--- lib/charm-helpers/Makefile 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/Makefile 1970-01-01 00:00:00 +0000
@@ -1,42 +0,0 @@
1PROJECT=charmhelpers
2PYTHON := /usr/bin/env python
3SUITE=unstable
4TESTS=tests/
5
6all:
7 @echo "make source - Create source package"
8 @echo "make sdeb - Create debian source package"
9 @echo "make deb - Create debian package"
10 @echo "make clean"
11 @echo "make userinstall - Install locally"
12
13sdeb: source
14 scripts/build source
15
16deb: source
17 scripts/build
18
19source: setup.py
20 scripts/update-revno
21 python setup.py sdist
22
23clean:
24 python setup.py clean
25 rm -rf build/ MANIFEST
26 find . -name '*.pyc' -delete
27 rm -rf dist/*
28 dh_clean
29
30userinstall:
31 scripts/update-revno
32 python setup.py install --user
33
34test:
35 @echo Starting tests...
36 @$(PYTHON) /usr/bin/nosetests --nologcapture tests/
37
38lint:
39 @echo Checking for Python syntax...
40 @flake8 --ignore=E123,E501 $(PROJECT) $(TESTS) && echo OK
41
42build: test lint
430
=== removed file 'lib/charm-helpers/README.test'
--- lib/charm-helpers/README.test 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/README.test 1970-01-01 00:00:00 +0000
@@ -1,7 +0,0 @@
1Required Packages for Running Tests
2-----------------------------------
3python-shelltoolbox
4python-tempita
5python-nose
6python-mock
7python-testtools
80
=== removed file 'lib/charm-helpers/README.txt'
--- lib/charm-helpers/README.txt 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/README.txt 1970-01-01 00:00:00 +0000
@@ -1,8 +0,0 @@
1============
2CharmHelpers
3============
4
5CharmHelpers provides an opinionated set of tools for building Juju
6charms that work together. In addition to basic tasks like interact-
7ing with the charm environment and the machine it runs on, it also
8helps keep you build hooks and establish relations effortlessly.
90
=== removed file 'lib/charm-helpers/REVISION'
--- lib/charm-helpers/REVISION 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/REVISION 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
154
20
=== removed file 'lib/charm-helpers/VERSION'
--- lib/charm-helpers/VERSION 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/VERSION 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
10.1.2
20
=== removed directory 'lib/charm-helpers/bin'
=== removed file 'lib/charm-helpers/bin/README'
--- lib/charm-helpers/bin/README 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/bin/README 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1This directory contains executables for accessing charmhelpers functionality
20
=== removed directory 'lib/charm-helpers/bin/contrib'
=== removed directory 'lib/charm-helpers/bin/contrib/charmsupport'
=== removed file 'lib/charm-helpers/bin/contrib/charmsupport/charmsupport'
--- lib/charm-helpers/bin/contrib/charmsupport/charmsupport 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/bin/contrib/charmsupport/charmsupport 1970-01-01 00:00:00 +0000
@@ -1,31 +0,0 @@
1#!/usr/bin/env python
2
3import argparse
4from charmhelpers.contrib.charmsupport import execd
5
6
7def run_execd(args):
8 execd.execd_run(args.module, args.dir, die_on_error=True)
9
10
11def parse_args():
12 parser = argparse.ArgumentParser(description='Perform common charm tasks')
13 subparsers = parser.add_subparsers(help='Commands')
14
15 execd_parser = subparsers.add_parser('execd',
16 help='Execute a directory of commands')
17 execd_parser.add_argument('--module', default='charm-pre-install',
18 help='module to run (default: charm-pre-install)')
19 execd_parser.add_argument('--dir',
20 help="Override the exec.d directory path")
21 execd_parser.set_defaults(func=run_execd)
22
23 return parser.parse_args()
24
25
26def main():
27 arguments = parse_args()
28 arguments.func(arguments)
29
30if __name__ == '__main__':
31 exit(main())
320
=== removed directory 'lib/charm-helpers/bin/contrib/saltstack'
=== removed file 'lib/charm-helpers/bin/contrib/saltstack/salt-call'
--- lib/charm-helpers/bin/contrib/saltstack/salt-call 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/bin/contrib/saltstack/salt-call 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1#!/usr/bin/env python
2'''
3Directly call a salt command in the modules, does not require a running salt
4minion to run.
5'''
6
7from salt.scripts import salt_call
8
9
10if __name__ == '__main__':
11 salt_call()
120
=== removed directory 'lib/charm-helpers/charmhelpers'
=== removed file 'lib/charm-helpers/charmhelpers/__init__.py'
=== removed directory 'lib/charm-helpers/charmhelpers/contrib'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/__init__.py'
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/charmhelpers'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmhelpers/IMPORT'
--- lib/charm-helpers/charmhelpers/contrib/charmhelpers/IMPORT 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/charmhelpers/IMPORT 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1Source lp:charm-tools/trunk
2
3charm-tools/helpers/python/charmhelpers/__init__.py -> charmhelpers/charmhelpers/contrib/charmhelpers/__init__.py
4charm-tools/helpers/python/charmhelpers/tests/test_charmhelpers.py -> charmhelpers/tests/contrib/charmhelpers/test_charmhelpers.py
50
=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmhelpers/__init__.py'
--- lib/charm-helpers/charmhelpers/contrib/charmhelpers/__init__.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/charmhelpers/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,183 +0,0 @@
1# Copyright 2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4import warnings
5warnings.warn("contrib.charmhelpers is deprecated", DeprecationWarning)
6
7"""Helper functions for writing Juju charms in Python."""
8
9__metaclass__ = type
10__all__ = [
11 #'get_config', # core.hookenv.config()
12 #'log', # core.hookenv.log()
13 #'log_entry', # core.hookenv.log()
14 #'log_exit', # core.hookenv.log()
15 #'relation_get', # core.hookenv.relation_get()
16 #'relation_set', # core.hookenv.relation_set()
17 #'relation_ids', # core.hookenv.relation_ids()
18 #'relation_list', # core.hookenv.relation_units()
19 #'config_get', # core.hookenv.config()
20 #'unit_get', # core.hookenv.unit_get()
21 #'open_port', # core.hookenv.open_port()
22 #'close_port', # core.hookenv.close_port()
23 #'service_control', # core.host.service()
24 'unit_info', # client-side, NOT IMPLEMENTED
25 'wait_for_machine', # client-side, NOT IMPLEMENTED
26 'wait_for_page_contents', # client-side, NOT IMPLEMENTED
27 'wait_for_relation', # client-side, NOT IMPLEMENTED
28 'wait_for_unit', # client-side, NOT IMPLEMENTED
29 ]
30
31import operator
32from shelltoolbox import (
33 command,
34)
35import tempfile
36import time
37import urllib2
38import yaml
39
40SLEEP_AMOUNT = 0.1
41# We create a juju_status Command here because it makes testing much,
42# much easier.
43juju_status = lambda: command('juju')('status')
44
45# re-implemented as charmhelpers.fetch.configure_sources()
46#def configure_source(update=False):
47# source = config_get('source')
48# if ((source.startswith('ppa:') or
49# source.startswith('cloud:') or
50# source.startswith('http:'))):
51# run('add-apt-repository', source)
52# if source.startswith("http:"):
53# run('apt-key', 'import', config_get('key'))
54# if update:
55# run('apt-get', 'update')
56
57# DEPRECATED: client-side only
58def make_charm_config_file(charm_config):
59 charm_config_file = tempfile.NamedTemporaryFile()
60 charm_config_file.write(yaml.dump(charm_config))
61 charm_config_file.flush()
62 # The NamedTemporaryFile instance is returned instead of just the name
63 # because we want to take advantage of garbage collection-triggered
64 # deletion of the temp file when it goes out of scope in the caller.
65 return charm_config_file
66
67
68# DEPRECATED: client-side only
69def unit_info(service_name, item_name, data=None, unit=None):
70 if data is None:
71 data = yaml.safe_load(juju_status())
72 service = data['services'].get(service_name)
73 if service is None:
74 # XXX 2012-02-08 gmb:
75 # This allows us to cope with the race condition that we
76 # have between deploying a service and having it come up in
77 # `juju status`. We could probably do with cleaning it up so
78 # that it fails a bit more noisily after a while.
79 return ''
80 units = service['units']
81 if unit is not None:
82 item = units[unit][item_name]
83 else:
84 # It might seem odd to sort the units here, but we do it to
85 # ensure that when no unit is specified, the first unit for the
86 # service (or at least the one with the lowest number) is the
87 # one whose data gets returned.
88 sorted_unit_names = sorted(units.keys())
89 item = units[sorted_unit_names[0]][item_name]
90 return item
91
92
93# DEPRECATED: client-side only
94def get_machine_data():
95 return yaml.safe_load(juju_status())['machines']
96
97
98# DEPRECATED: client-side only
99def wait_for_machine(num_machines=1, timeout=300):
100 """Wait `timeout` seconds for `num_machines` machines to come up.
101
102 This wait_for... function can be called by other wait_for functions
103 whose timeouts might be too short in situations where only a bare
104 Juju setup has been bootstrapped.
105
106 :return: A tuple of (num_machines, time_taken). This is used for
107 testing.
108 """
109 # You may think this is a hack, and you'd be right. The easiest way
110 # to tell what environment we're working in (LXC vs EC2) is to check
111 # the dns-name of the first machine. If it's localhost we're in LXC
112 # and we can just return here.
113 if get_machine_data()[0]['dns-name'] == 'localhost':
114 return 1, 0
115 start_time = time.time()
116 while True:
117 # Drop the first machine, since it's the Zookeeper and that's
118 # not a machine that we need to wait for. This will only work
119 # for EC2 environments, which is why we return early above if
120 # we're in LXC.
121 machine_data = get_machine_data()
122 non_zookeeper_machines = [
123 machine_data[key] for key in machine_data.keys()[1:]]
124 if len(non_zookeeper_machines) >= num_machines:
125 all_machines_running = True
126 for machine in non_zookeeper_machines:
127 if machine.get('instance-state') != 'running':
128 all_machines_running = False
129 break
130 if all_machines_running:
131 break
132 if time.time() - start_time >= timeout:
133 raise RuntimeError('timeout waiting for service to start')
134 time.sleep(SLEEP_AMOUNT)
135 return num_machines, time.time() - start_time
136
137
138# DEPRECATED: client-side only
139def wait_for_unit(service_name, timeout=480):
140 """Wait `timeout` seconds for a given service name to come up."""
141 wait_for_machine(num_machines=1)
142 start_time = time.time()
143 while True:
144 state = unit_info(service_name, 'agent-state')
145 if 'error' in state or state == 'started':
146 break
147 if time.time() - start_time >= timeout:
148 raise RuntimeError('timeout waiting for service to start')
149 time.sleep(SLEEP_AMOUNT)
150 if state != 'started':
151 raise RuntimeError('unit did not start, agent-state: ' + state)
152
153
154# DEPRECATED: client-side only
155def wait_for_relation(service_name, relation_name, timeout=120):
156 """Wait `timeout` seconds for a given relation to come up."""
157 start_time = time.time()
158 while True:
159 relation = unit_info(service_name, 'relations').get(relation_name)
160 if relation is not None and relation['state'] == 'up':
161 break
162 if time.time() - start_time >= timeout:
163 raise RuntimeError('timeout waiting for relation to be up')
164 time.sleep(SLEEP_AMOUNT)
165
166
167# DEPRECATED: client-side only
168def wait_for_page_contents(url, contents, timeout=120, validate=None):
169 if validate is None:
170 validate = operator.contains
171 start_time = time.time()
172 while True:
173 try:
174 stream = urllib2.urlopen(url)
175 except (urllib2.HTTPError, urllib2.URLError):
176 pass
177 else:
178 page = stream.read()
179 if validate(page, contents):
180 return page
181 if time.time() - start_time >= timeout:
182 raise RuntimeError('timeout waiting for contents of ' + url)
183 time.sleep(SLEEP_AMOUNT)
1840
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/charmsupport'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmsupport/IMPORT'
--- lib/charm-helpers/charmhelpers/contrib/charmsupport/IMPORT 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/charmsupport/IMPORT 1970-01-01 00:00:00 +0000
@@ -1,14 +0,0 @@
1Source: lp:charmsupport/trunk
2
3charmsupport/charmsupport/execd.py -> charm-helpers/charmhelpers/contrib/charmsupport/execd.py
4charmsupport/charmsupport/hookenv.py -> charm-helpers/charmhelpers/contrib/charmsupport/hookenv.py
5charmsupport/charmsupport/host.py -> charm-helpers/charmhelpers/contrib/charmsupport/host.py
6charmsupport/charmsupport/nrpe.py -> charm-helpers/charmhelpers/contrib/charmsupport/nrpe.py
7charmsupport/charmsupport/volumes.py -> charm-helpers/charmhelpers/contrib/charmsupport/volumes.py
8
9charmsupport/tests/test_execd.py -> charm-helpers/tests/contrib/charmsupport/test_execd.py
10charmsupport/tests/test_hookenv.py -> charm-helpers/tests/contrib/charmsupport/test_hookenv.py
11charmsupport/tests/test_host.py -> charm-helpers/tests/contrib/charmsupport/test_host.py
12charmsupport/tests/test_nrpe.py -> charm-helpers/tests/contrib/charmsupport/test_nrpe.py
13
14charmsupport/bin/charmsupport -> charm-helpers/bin/contrib/charmsupport/charmsupport
150
=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmsupport/__init__.py'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmsupport/nrpe.py'
--- lib/charm-helpers/charmhelpers/contrib/charmsupport/nrpe.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/charmsupport/nrpe.py 1970-01-01 00:00:00 +0000
@@ -1,217 +0,0 @@
1"""Compatibility with the nrpe-external-master charm"""
2# Copyright 2012 Canonical Ltd.
3#
4# Authors:
5# Matthew Wedgwood <matthew.wedgwood@canonical.com>
6
7import subprocess
8import pwd
9import grp
10import os
11import re
12import shlex
13import yaml
14
15from charmhelpers.core.hookenv import (
16 config,
17 local_unit,
18 log,
19 relation_ids,
20 relation_set,
21 )
22from charmhelpers.core.host import service
23
24# This module adds compatibility with the nrpe-external-master and plain nrpe
25# subordinate charms. To use it in your charm:
26#
27# 1. Update metadata.yaml
28#
29# provides:
30# (...)
31# nrpe-external-master:
32# interface: nrpe-external-master
33# scope: container
34#
35# and/or
36#
37# provides:
38# (...)
39# local-monitors:
40# interface: local-monitors
41# scope: container
42
43#
44# 2. Add the following to config.yaml
45#
46# nagios_context:
47# default: "juju"
48# type: string
49# description: |
50# Used by the nrpe subordinate charms.
51# A string that will be prepended to instance name to set the host name
52# in nagios. So for instance the hostname would be something like:
53# juju-myservice-0
54# If you're running multiple environments with the same services in them
55# this allows you to differentiate between them.
56#
57# 3. Add custom checks (Nagios plugins) to files/nrpe-external-master
58#
59# 4. Update your hooks.py with something like this:
60#
61# from charmsupport.nrpe import NRPE
62# (...)
63# def update_nrpe_config():
64# nrpe_compat = NRPE()
65# nrpe_compat.add_check(
66# shortname = "myservice",
67# description = "Check MyService",
68# check_cmd = "check_http -w 2 -c 10 http://localhost"
69# )
70# nrpe_compat.add_check(
71# "myservice_other",
72# "Check for widget failures",
73# check_cmd = "/srv/myapp/scripts/widget_check"
74# )
75# nrpe_compat.write()
76#
77# def config_changed():
78# (...)
79# update_nrpe_config()
80#
81# def nrpe_external_master_relation_changed():
82# update_nrpe_config()
83#
84# def local_monitors_relation_changed():
85# update_nrpe_config()
86#
87# 5. ln -s hooks.py nrpe-external-master-relation-changed
88# ln -s hooks.py local-monitors-relation-changed
89
90
91class CheckException(Exception):
92 pass
93
94
95class Check(object):
96 shortname_re = '[A-Za-z0-9-_]+$'
97 service_template = ("""
98#---------------------------------------------------
99# This file is Juju managed
100#---------------------------------------------------
101define service {{
102 use active-service
103 host_name {nagios_hostname}
104 service_description {nagios_hostname}[{shortname}] """
105 """{description}
106 check_command check_nrpe!{command}
107 servicegroups {nagios_servicegroup}
108}}
109""")
110
111 def __init__(self, shortname, description, check_cmd):
112 super(Check, self).__init__()
113 # XXX: could be better to calculate this from the service name
114 if not re.match(self.shortname_re, shortname):
115 raise CheckException("shortname must match {}".format(
116 Check.shortname_re))
117 self.shortname = shortname
118 self.command = "check_{}".format(shortname)
119 # Note: a set of invalid characters is defined by the
120 # Nagios server config
121 # The default is: illegal_object_name_chars=`~!$%^&*"|'<>?,()=
122 self.description = description
123 self.check_cmd = self._locate_cmd(check_cmd)
124
125 def _locate_cmd(self, check_cmd):
126 search_path = (
127 '/',
128 os.path.join(os.environ['CHARM_DIR'],
129 'files/nrpe-external-master'),
130 '/usr/lib/nagios/plugins',
131 )
132 parts = shlex.split(check_cmd)
133 for path in search_path:
134 if os.path.exists(os.path.join(path, parts[0])):
135 command = os.path.join(path, parts[0])
136 if len(parts) > 1:
137 command += " " + " ".join(parts[1:])
138 return command
139 log('Check command not found: {}'.format(parts[0]))
140 return ''
141
142 def write(self, nagios_context, hostname):
143 nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
144 self.command)
145 with open(nrpe_check_file, 'w') as nrpe_check_config:
146 nrpe_check_config.write("# check {}\n".format(self.shortname))
147 nrpe_check_config.write("command[{}]={}\n".format(
148 self.command, self.check_cmd))
149
150 if not os.path.exists(NRPE.nagios_exportdir):
151 log('Not writing service config as {} is not accessible'.format(
152 NRPE.nagios_exportdir))
153 else:
154 self.write_service_config(nagios_context, hostname)
155
156 def write_service_config(self, nagios_context, hostname):
157 for f in os.listdir(NRPE.nagios_exportdir):
158 if re.search('.*{}.cfg'.format(self.command), f):
159 os.remove(os.path.join(NRPE.nagios_exportdir, f))
160
161 templ_vars = {
162 'nagios_hostname': hostname,
163 'nagios_servicegroup': nagios_context,
164 'description': self.description,
165 'shortname': self.shortname,
166 'command': self.command,
167 }
168 nrpe_service_text = Check.service_template.format(**templ_vars)
169 nrpe_service_file = '{}/service__{}_{}.cfg'.format(
170 NRPE.nagios_exportdir, hostname, self.command)
171 with open(nrpe_service_file, 'w') as nrpe_service_config:
172 nrpe_service_config.write(str(nrpe_service_text))
173
174 def run(self):
175 subprocess.call(self.check_cmd)
176
177
178class NRPE(object):
179 nagios_logdir = '/var/log/nagios'
180 nagios_exportdir = '/var/lib/nagios/export'
181 nrpe_confdir = '/etc/nagios/nrpe.d'
182
183 def __init__(self):
184 super(NRPE, self).__init__()
185 self.config = config()
186 self.nagios_context = self.config['nagios_context']
187 self.unit_name = local_unit().replace('/', '-')
188 self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
189 self.checks = []
190
191 def add_check(self, *args, **kwargs):
192 self.checks.append(Check(*args, **kwargs))
193
194 def write(self):
195 try:
196 nagios_uid = pwd.getpwnam('nagios').pw_uid
197 nagios_gid = grp.getgrnam('nagios').gr_gid
198 except:
199 log("Nagios user not set up, nrpe checks not updated")
200 return
201
202 if not os.path.exists(NRPE.nagios_logdir):
203 os.mkdir(NRPE.nagios_logdir)
204 os.chown(NRPE.nagios_logdir, nagios_uid, nagios_gid)
205
206 nrpe_monitors = {}
207 monitors = {"monitors": {"remote": {"nrpe": nrpe_monitors}}}
208 for nrpecheck in self.checks:
209 nrpecheck.write(self.nagios_context, self.hostname)
210 nrpe_monitors[nrpecheck.shortname] = {
211 "command": nrpecheck.command,
212 }
213
214 service('restart', 'nagios-nrpe-server')
215
216 for rid in relation_ids("local-monitors"):
217 relation_set(relation_id=rid, monitors=yaml.dump(monitors))
2180
=== removed file 'lib/charm-helpers/charmhelpers/contrib/charmsupport/volumes.py'
--- lib/charm-helpers/charmhelpers/contrib/charmsupport/volumes.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/charmsupport/volumes.py 1970-01-01 00:00:00 +0000
@@ -1,156 +0,0 @@
1'''
2Functions for managing volumes in juju units. One volume is supported per unit.
3Subordinates may have their own storage, provided it is on its own partition.
4
5Configuration stanzas:
6 volume-ephemeral:
7 type: boolean
8 default: true
9 description: >
10 If false, a volume is mounted as sepecified in "volume-map"
11 If true, ephemeral storage will be used, meaning that log data
12 will only exist as long as the machine. YOU HAVE BEEN WARNED.
13 volume-map:
14 type: string
15 default: {}
16 description: >
17 YAML map of units to device names, e.g:
18 "{ rsyslog/0: /dev/vdb, rsyslog/1: /dev/vdb }"
19 Service units will raise a configure-error if volume-ephemeral
20 is 'true' and no volume-map value is set. Use 'juju set' to set a
21 value and 'juju resolved' to complete configuration.
22
23Usage:
24 from charmsupport.volumes import configure_volume, VolumeConfigurationError
25 from charmsupport.hookenv import log, ERROR
26 def post_mount_hook():
27 stop_service('myservice')
28 def post_mount_hook():
29 start_service('myservice')
30
31 if __name__ == '__main__':
32 try:
33 configure_volume(before_change=pre_mount_hook,
34 after_change=post_mount_hook)
35 except VolumeConfigurationError:
36 log('Storage could not be configured', ERROR)
37'''
38
39# XXX: Known limitations
40# - fstab is neither consulted nor updated
41
42import os
43from charmhelpers.core import hookenv
44from charmhelpers.core import host
45import yaml
46
47
48MOUNT_BASE = '/srv/juju/volumes'
49
50
51class VolumeConfigurationError(Exception):
52 '''Volume configuration data is missing or invalid'''
53 pass
54
55
56def get_config():
57 '''Gather and sanity-check volume configuration data'''
58 volume_config = {}
59 config = hookenv.config()
60
61 errors = False
62
63 if config.get('volume-ephemeral') in (True, 'True', 'true', 'Yes', 'yes'):
64 volume_config['ephemeral'] = True
65 else:
66 volume_config['ephemeral'] = False
67
68 try:
69 volume_map = yaml.safe_load(config.get('volume-map', '{}'))
70 except yaml.YAMLError as e:
71 hookenv.log("Error parsing YAML volume-map: {}".format(e),
72 hookenv.ERROR)
73 errors = True
74 if volume_map is None:
75 # probably an empty string
76 volume_map = {}
77 elif not isinstance(volume_map, dict):
78 hookenv.log("Volume-map should be a dictionary, not {}".format(
79 type(volume_map)))
80 errors = True
81
82 volume_config['device'] = volume_map.get(os.environ['JUJU_UNIT_NAME'])
83 if volume_config['device'] and volume_config['ephemeral']:
84 # asked for ephemeral storage but also defined a volume ID
85 hookenv.log('A volume is defined for this unit, but ephemeral '
86 'storage was requested', hookenv.ERROR)
87 errors = True
88 elif not volume_config['device'] and not volume_config['ephemeral']:
89 # asked for permanent storage but did not define volume ID
90 hookenv.log('Ephemeral storage was requested, but there is no volume '
91 'defined for this unit.', hookenv.ERROR)
92 errors = True
93
94 unit_mount_name = hookenv.local_unit().replace('/', '-')
95 volume_config['mountpoint'] = os.path.join(MOUNT_BASE, unit_mount_name)
96
97 if errors:
98 return None
99 return volume_config
100
101
102def mount_volume(config):
103 if os.path.exists(config['mountpoint']):
104 if not os.path.isdir(config['mountpoint']):
105 hookenv.log('Not a directory: {}'.format(config['mountpoint']))
106 raise VolumeConfigurationError()
107 else:
108 host.mkdir(config['mountpoint'])
109 if os.path.ismount(config['mountpoint']):
110 unmount_volume(config)
111 if not host.mount(config['device'], config['mountpoint'], persist=True):
112 raise VolumeConfigurationError()
113
114
115def unmount_volume(config):
116 if os.path.ismount(config['mountpoint']):
117 if not host.umount(config['mountpoint'], persist=True):
118 raise VolumeConfigurationError()
119
120
121def managed_mounts():
122 '''List of all mounted managed volumes'''
123 return filter(lambda mount: mount[0].startswith(MOUNT_BASE), host.mounts())
124
125
126def configure_volume(before_change=lambda: None, after_change=lambda: None):
127 '''Set up storage (or don't) according to the charm's volume configuration.
128 Returns the mount point or "ephemeral". before_change and after_change
129 are optional functions to be called if the volume configuration changes.
130 '''
131
132 config = get_config()
133 if not config:
134 hookenv.log('Failed to read volume configuration', hookenv.CRITICAL)
135 raise VolumeConfigurationError()
136
137 if config['ephemeral']:
138 if os.path.ismount(config['mountpoint']):
139 before_change()
140 unmount_volume(config)
141 after_change()
142 return 'ephemeral'
143 else:
144 # persistent storage
145 if os.path.ismount(config['mountpoint']):
146 mounts = dict(managed_mounts())
147 if mounts.get(config['mountpoint']) != config['device']:
148 before_change()
149 unmount_volume(config)
150 mount_volume(config)
151 after_change()
152 else:
153 before_change()
154 mount_volume(config)
155 after_change()
156 return config['mountpoint']
1570
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/hahelpers'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/hahelpers/__init__.py'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/hahelpers/apache.py'
--- lib/charm-helpers/charmhelpers/contrib/hahelpers/apache.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/hahelpers/apache.py 1970-01-01 00:00:00 +0000
@@ -1,58 +0,0 @@
1#
2# Copyright 2012 Canonical Ltd.
3#
4# This file is sourced from lp:openstack-charm-helpers
5#
6# Authors:
7# James Page <james.page@ubuntu.com>
8# Adam Gandelman <adamg@ubuntu.com>
9#
10
11import subprocess
12
13from charmhelpers.core.hookenv import (
14 config as config_get,
15 relation_get,
16 relation_ids,
17 related_units as relation_list,
18 log,
19 INFO,
20)
21
22
23def get_cert():
24 cert = config_get('ssl_cert')
25 key = config_get('ssl_key')
26 if not (cert and key):
27 log("Inspecting identity-service relations for SSL certificate.",
28 level=INFO)
29 cert = key = None
30 for r_id in relation_ids('identity-service'):
31 for unit in relation_list(r_id):
32 if not cert:
33 cert = relation_get('ssl_cert',
34 rid=r_id, unit=unit)
35 if not key:
36 key = relation_get('ssl_key',
37 rid=r_id, unit=unit)
38 return (cert, key)
39
40
41def get_ca_cert():
42 ca_cert = None
43 log("Inspecting identity-service relations for CA SSL certificate.",
44 level=INFO)
45 for r_id in relation_ids('identity-service'):
46 for unit in relation_list(r_id):
47 if not ca_cert:
48 ca_cert = relation_get('ca_cert',
49 rid=r_id, unit=unit)
50 return ca_cert
51
52
53def install_ca_cert(ca_cert):
54 if ca_cert:
55 with open('/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt',
56 'w') as crt:
57 crt.write(ca_cert)
58 subprocess.check_call(['update-ca-certificates', '--fresh'])
590
=== removed file 'lib/charm-helpers/charmhelpers/contrib/hahelpers/ceph.py'
--- lib/charm-helpers/charmhelpers/contrib/hahelpers/ceph.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/hahelpers/ceph.py 1970-01-01 00:00:00 +0000
@@ -1,278 +0,0 @@
1#
2# Copyright 2012 Canonical Ltd.
3#
4# This file is sourced from lp:openstack-charm-helpers
5#
6# Authors:
7# James Page <james.page@ubuntu.com>
8# Adam Gandelman <adamg@ubuntu.com>
9#
10
11import commands
12import os
13import shutil
14
15from subprocess import (
16 check_call,
17 check_output,
18 CalledProcessError
19)
20
21from charmhelpers.core.hookenv import (
22 relation_get,
23 relation_ids,
24 related_units,
25 log,
26 INFO,
27)
28
29from charmhelpers.core.host import (
30 apt_install,
31 mount,
32 mounts,
33 service_start,
34 service_stop,
35 umount,
36)
37
38KEYRING = '/etc/ceph/ceph.client.%s.keyring'
39KEYFILE = '/etc/ceph/ceph.client.%s.key'
40
41CEPH_CONF = """[global]
42 auth supported = %(auth)s
43 keyring = %(keyring)s
44 mon host = %(mon_hosts)s
45"""
46
47
48def running(service):
49 # this local util can be dropped as soon the following branch lands
50 # in lp:charm-helpers
51 # https://code.launchpad.net/~gandelman-a/charm-helpers/service_running/
52 try:
53 output = check_output(['service', service, 'status'])
54 except CalledProcessError:
55 return False
56 else:
57 if ("start/running" in output or "is running" in output):
58 return True
59 else:
60 return False
61
62
63def install():
64 ceph_dir = "/etc/ceph"
65 if not os.path.isdir(ceph_dir):
66 os.mkdir(ceph_dir)
67 apt_install('ceph-common', fatal=True)
68
69
70def rbd_exists(service, pool, rbd_img):
71 (rc, out) = commands.getstatusoutput('rbd list --id %s --pool %s' %
72 (service, pool))
73 return rbd_img in out
74
75
76def create_rbd_image(service, pool, image, sizemb):
77 cmd = [
78 'rbd',
79 'create',
80 image,
81 '--size',
82 str(sizemb),
83 '--id',
84 service,
85 '--pool',
86 pool
87 ]
88 check_call(cmd)
89
90
91def pool_exists(service, name):
92 (rc, out) = commands.getstatusoutput("rados --id %s lspools" % service)
93 return name in out
94
95
96def create_pool(service, name):
97 cmd = [
98 'rados',
99 '--id',
100 service,
101 'mkpool',
102 name
103 ]
104 check_call(cmd)
105
106
107def keyfile_path(service):
108 return KEYFILE % service
109
110
111def keyring_path(service):
112 return KEYRING % service
113
114
115def create_keyring(service, key):
116 keyring = keyring_path(service)
117 if os.path.exists(keyring):
118 log('ceph: Keyring exists at %s.' % keyring, level=INFO)
119 cmd = [
120 'ceph-authtool',
121 keyring,
122 '--create-keyring',
123 '--name=client.%s' % service,
124 '--add-key=%s' % key
125 ]
126 check_call(cmd)
127 log('ceph: Created new ring at %s.' % keyring, level=INFO)
128
129
130def create_key_file(service, key):
131 # create a file containing the key
132 keyfile = keyfile_path(service)
133 if os.path.exists(keyfile):
134 log('ceph: Keyfile exists at %s.' % keyfile, level=INFO)
135 fd = open(keyfile, 'w')
136 fd.write(key)
137 fd.close()
138 log('ceph: Created new keyfile at %s.' % keyfile, level=INFO)
139
140
141def get_ceph_nodes():
142 hosts = []
143 for r_id in relation_ids('ceph'):
144 for unit in related_units(r_id):
145 hosts.append(relation_get('private-address', unit=unit, rid=r_id))
146 return hosts
147
148
149def configure(service, key, auth):
150 create_keyring(service, key)
151 create_key_file(service, key)
152 hosts = get_ceph_nodes()
153 mon_hosts = ",".join(map(str, hosts))
154 keyring = keyring_path(service)
155 with open('/etc/ceph/ceph.conf', 'w') as ceph_conf:
156 ceph_conf.write(CEPH_CONF % locals())
157 modprobe_kernel_module('rbd')
158
159
160def image_mapped(image_name):
161 (rc, out) = commands.getstatusoutput('rbd showmapped')
162 return image_name in out
163
164
165def map_block_storage(service, pool, image):
166 cmd = [
167 'rbd',
168 'map',
169 '%s/%s' % (pool, image),
170 '--user',
171 service,
172 '--secret',
173 keyfile_path(service),
174 ]
175 check_call(cmd)
176
177
178def filesystem_mounted(fs):
179 return fs in [f for m, f in mounts()]
180
181
182def make_filesystem(blk_device, fstype='ext4'):
183 log('ceph: Formatting block device %s as filesystem %s.' %
184 (blk_device, fstype), level=INFO)
185 cmd = ['mkfs', '-t', fstype, blk_device]
186 check_call(cmd)
187
188
189def place_data_on_ceph(service, blk_device, data_src_dst, fstype='ext4'):
190 # mount block device into /mnt
191 mount(blk_device, '/mnt')
192
193 # copy data to /mnt
194 try:
195 copy_files(data_src_dst, '/mnt')
196 except:
197 pass
198
199 # umount block device
200 umount('/mnt')
201
202 _dir = os.stat(data_src_dst)
203 uid = _dir.st_uid
204 gid = _dir.st_gid
205
206 # re-mount where the data should originally be
207 mount(blk_device, data_src_dst, persist=True)
208
209 # ensure original ownership of new mount.
210 cmd = ['chown', '-R', '%s:%s' % (uid, gid), data_src_dst]
211 check_call(cmd)
212
213
214# TODO: re-use
215def modprobe_kernel_module(module):
216 log('ceph: Loading kernel module', level=INFO)
217 cmd = ['modprobe', module]
218 check_call(cmd)
219 cmd = 'echo %s >> /etc/modules' % module
220 check_call(cmd, shell=True)
221
222
223def copy_files(src, dst, symlinks=False, ignore=None):
224 for item in os.listdir(src):
225 s = os.path.join(src, item)
226 d = os.path.join(dst, item)
227 if os.path.isdir(s):
228 shutil.copytree(s, d, symlinks, ignore)
229 else:
230 shutil.copy2(s, d)
231
232
233def ensure_ceph_storage(service, pool, rbd_img, sizemb, mount_point,
234 blk_device, fstype, system_services=[]):
235 """
236 To be called from the current cluster leader.
237 Ensures given pool and RBD image exists, is mapped to a block device,
238 and the device is formatted and mounted at the given mount_point.
239
240 If formatting a device for the first time, data existing at mount_point
241 will be migrated to the RBD device before being remounted.
242
243 All services listed in system_services will be stopped prior to data
244 migration and restarted when complete.
245 """
246 # Ensure pool, RBD image, RBD mappings are in place.
247 if not pool_exists(service, pool):
248 log('ceph: Creating new pool %s.' % pool, level=INFO)
249 create_pool(service, pool)
250
251 if not rbd_exists(service, pool, rbd_img):
252 log('ceph: Creating RBD image (%s).' % rbd_img, level=INFO)
253 create_rbd_image(service, pool, rbd_img, sizemb)
254
255 if not image_mapped(rbd_img):
256 log('ceph: Mapping RBD Image as a Block Device.', level=INFO)
257 map_block_storage(service, pool, rbd_img)
258
259 # make file system
260 # TODO: What happens if for whatever reason this is run again and
261 # the data is already in the rbd device and/or is mounted??
262 # When it is mounted already, it will fail to make the fs
263 # XXX: This is really sketchy! Need to at least add an fstab entry
264 # otherwise this hook will blow away existing data if its executed
265 # after a reboot.
266 if not filesystem_mounted(mount_point):
267 make_filesystem(blk_device, fstype)
268
269 for svc in system_services:
270 if running(svc):
271 log('Stopping services %s prior to migrating data.' % svc,
272 level=INFO)
273 service_stop(svc)
274
275 place_data_on_ceph(service, blk_device, mount_point, fstype)
276
277 for svc in system_services:
278 service_start(svc)
2790
=== removed file 'lib/charm-helpers/charmhelpers/contrib/hahelpers/cluster.py'
--- lib/charm-helpers/charmhelpers/contrib/hahelpers/cluster.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/hahelpers/cluster.py 1970-01-01 00:00:00 +0000
@@ -1,180 +0,0 @@
1#
2# Copyright 2012 Canonical Ltd.
3#
4# Authors:
5# James Page <james.page@ubuntu.com>
6# Adam Gandelman <adamg@ubuntu.com>
7#
8
9import subprocess
10import os
11
12from socket import gethostname as get_unit_hostname
13
14from charmhelpers.core.hookenv import (
15 log,
16 relation_ids,
17 related_units as relation_list,
18 relation_get,
19 config as config_get,
20 INFO,
21 ERROR,
22)
23
24
25class HAIncompleteConfig(Exception):
26 pass
27
28
29def is_clustered():
30 for r_id in (relation_ids('ha') or []):
31 for unit in (relation_list(r_id) or []):
32 clustered = relation_get('clustered',
33 rid=r_id,
34 unit=unit)
35 if clustered:
36 return True
37 return False
38
39
40def is_leader(resource):
41 cmd = [
42 "crm", "resource",
43 "show", resource
44 ]
45 try:
46 status = subprocess.check_output(cmd)
47 except subprocess.CalledProcessError:
48 return False
49 else:
50 if get_unit_hostname() in status:
51 return True
52 else:
53 return False
54
55
56def peer_units():
57 peers = []
58 for r_id in (relation_ids('cluster') or []):
59 for unit in (relation_list(r_id) or []):
60 peers.append(unit)
61 return peers
62
63
64def oldest_peer(peers):
65 local_unit_no = int(os.getenv('JUJU_UNIT_NAME').split('/')[1])
66 for peer in peers:
67 remote_unit_no = int(peer.split('/')[1])
68 if remote_unit_no < local_unit_no:
69 return False
70 return True
71
72
73def eligible_leader(resource):
74 if is_clustered():
75 if not is_leader(resource):
76 log('Deferring action to CRM leader.', level=INFO)
77 return False
78 else:
79 peers = peer_units()
80 if peers and not oldest_peer(peers):
81 log('Deferring action to oldest service unit.', level=INFO)
82 return False
83 return True
84
85
86def https():
87 '''
88 Determines whether enough data has been provided in configuration
89 or relation data to configure HTTPS
90 .
91 returns: boolean
92 '''
93 if config_get('use-https') == "yes":
94 return True
95 if config_get('ssl_cert') and config_get('ssl_key'):
96 return True
97 for r_id in relation_ids('identity-service'):
98 for unit in relation_list(r_id):
99 if None not in [
100 relation_get('https_keystone', rid=r_id, unit=unit),
101 relation_get('ssl_cert', rid=r_id, unit=unit),
102 relation_get('ssl_key', rid=r_id, unit=unit),
103 relation_get('ca_cert', rid=r_id, unit=unit),
104 ]:
105 return True
106 return False
107
108
109def determine_api_port(public_port):
110 '''
111 Determine correct API server listening port based on
112 existence of HTTPS reverse proxy and/or haproxy.
113
114 public_port: int: standard public port for given service
115
116 returns: int: the correct listening port for the API service
117 '''
118 i = 0
119 if len(peer_units()) > 0 or is_clustered():
120 i += 1
121 if https():
122 i += 1
123 return public_port - (i * 10)
124
125
126def determine_haproxy_port(public_port):
127 '''
128 Description: Determine correct proxy listening port based on public IP +
129 existence of HTTPS reverse proxy.
130
131 public_port: int: standard public port for given service
132
133 returns: int: the correct listening port for the HAProxy service
134 '''
135 i = 0
136 if https():
137 i += 1
138 return public_port - (i * 10)
139
140
141def get_hacluster_config():
142 '''
143 Obtains all relevant configuration from charm configuration required
144 for initiating a relation to hacluster:
145
146 ha-bindiface, ha-mcastport, vip, vip_iface, vip_cidr
147
148 returns: dict: A dict containing settings keyed by setting name.
149 raises: HAIncompleteConfig if settings are missing.
150 '''
151 settings = ['ha-bindiface', 'ha-mcastport', 'vip', 'vip_iface', 'vip_cidr']
152 conf = {}
153 for setting in settings:
154 conf[setting] = config_get(setting)
155 missing = []
156 [missing.append(s) for s, v in conf.iteritems() if v is None]
157 if missing:
158 log('Insufficient config data to configure hacluster.', level=ERROR)
159 raise HAIncompleteConfig
160 return conf
161
162
163def canonical_url(configs, vip_setting='vip'):
164 '''
165 Returns the correct HTTP URL to this host given the state of HTTPS
166 configuration and hacluster.
167
168 :configs : OSTemplateRenderer: A config tempating object to inspect for
169 a complete https context.
170 :vip_setting: str: Setting in charm config that specifies
171 VIP address.
172 '''
173 scheme = 'http'
174 if 'https' in configs.complete_contexts():
175 scheme = 'https'
176 if is_clustered():
177 addr = config_get(vip_setting)
178 else:
179 addr = get_unit_hostname()
180 return '%s://%s' % (scheme, addr)
1810
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/jujugui'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/jujugui/IMPORT'
--- lib/charm-helpers/charmhelpers/contrib/jujugui/IMPORT 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/jujugui/IMPORT 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1Source: lp:charms/juju-gui
2
3juju-gui/hooks/utils.py -> charm-helpers/charmhelpers/contrib/jujugui/utils.py
4juju-gui/tests/test_utils.py -> charm-helpers/tests/contrib/jujugui/test_utils.py
50
=== removed file 'lib/charm-helpers/charmhelpers/contrib/jujugui/__init__.py'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/jujugui/utils.py'
--- lib/charm-helpers/charmhelpers/contrib/jujugui/utils.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/jujugui/utils.py 1970-01-01 00:00:00 +0000
@@ -1,602 +0,0 @@
1"""Juju GUI charm utilities."""
2
3__all__ = [
4 'AGENT',
5 'APACHE',
6 'API_PORT',
7 'CURRENT_DIR',
8 'HAPROXY',
9 'IMPROV',
10 'JUJU_DIR',
11 'JUJU_GUI_DIR',
12 'JUJU_GUI_SITE',
13 'JUJU_PEM',
14 'WEB_PORT',
15 'bzr_checkout',
16 'chain',
17 'cmd_log',
18 'fetch_api',
19 'fetch_gui',
20 'find_missing_packages',
21 'first_path_in_dir',
22 'get_api_address',
23 'get_npm_cache_archive_url',
24 'get_release_file_url',
25 'get_staging_dependencies',
26 'get_zookeeper_address',
27 'legacy_juju',
28 'log_hook',
29 'merge',
30 'parse_source',
31 'prime_npm_cache',
32 'render_to_file',
33 'save_or_create_certificates',
34 'setup_apache',
35 'setup_gui',
36 'start_agent',
37 'start_gui',
38 'start_improv',
39 'write_apache_config',
40]
41
42from contextlib import contextmanager
43import errno
44import json
45import os
46import logging
47import shutil
48from subprocess import CalledProcessError
49import tempfile
50from urlparse import urlparse
51
52import apt
53import tempita
54
55from launchpadlib.launchpad import Launchpad
56from shelltoolbox import (
57 Serializer,
58 apt_get_install,
59 command,
60 environ,
61 install_extra_repositories,
62 run,
63 script_name,
64 search_file,
65 su,
66)
67from charmhelpers.core.host import (
68 service_start,
69)
70from charmhelpers.core.hookenv import (
71 log,
72 config,
73 unit_get,
74)
75
76
77AGENT = 'juju-api-agent'
78APACHE = 'apache2'
79IMPROV = 'juju-api-improv'
80HAPROXY = 'haproxy'
81
82API_PORT = 8080
83WEB_PORT = 8000
84
85CURRENT_DIR = os.getcwd()
86JUJU_DIR = os.path.join(CURRENT_DIR, 'juju')
87JUJU_GUI_DIR = os.path.join(CURRENT_DIR, 'juju-gui')
88JUJU_GUI_SITE = '/etc/apache2/sites-available/juju-gui'
89JUJU_GUI_PORTS = '/etc/apache2/ports.conf'
90JUJU_PEM = 'juju.includes-private-key.pem'
91BUILD_REPOSITORIES = ('ppa:chris-lea/node.js-legacy',)
92DEB_BUILD_DEPENDENCIES = (
93 'bzr', 'imagemagick', 'make', 'nodejs', 'npm',
94)
95DEB_STAGE_DEPENDENCIES = (
96 'zookeeper',
97)
98
99
100# Store the configuration from on invocation to the next.
101config_json = Serializer('/tmp/config.json')
102# Bazaar checkout command.
103bzr_checkout = command('bzr', 'co', '--lightweight')
104# Whether or not the charm is deployed using juju-core.
105# If juju-core has been used to deploy the charm, an agent.conf file must
106# be present in the charm parent directory.
107legacy_juju = lambda: not os.path.exists(
108 os.path.join(CURRENT_DIR, '..', 'agent.conf'))
109
110
111def _get_build_dependencies():
112 """Install deb dependencies for building."""
113 log('Installing build dependencies.')
114 cmd_log(install_extra_repositories(*BUILD_REPOSITORIES))
115 cmd_log(apt_get_install(*DEB_BUILD_DEPENDENCIES))
116
117
118def get_api_address(unit_dir):
119 """Return the Juju API address stored in the uniter agent.conf file."""
120 import yaml # python-yaml is only installed if juju-core is used.
121 # XXX 2013-03-27 frankban bug=1161443:
122 # currently the uniter agent.conf file does not include the API
123 # address. For now retrieve it from the machine agent file.
124 base_dir = os.path.abspath(os.path.join(unit_dir, '..'))
125 for dirname in os.listdir(base_dir):
126 if dirname.startswith('machine-'):
127 agent_conf = os.path.join(base_dir, dirname, 'agent.conf')
128 break
129 else:
130 raise IOError('Juju agent configuration file not found.')
131 contents = yaml.load(open(agent_conf))
132 return contents['apiinfo']['addrs'][0]
133
134
135def get_staging_dependencies():
136 """Install deb dependencies for the stage (improv) environment."""
137 log('Installing stage dependencies.')
138 cmd_log(apt_get_install(*DEB_STAGE_DEPENDENCIES))
139
140
141def first_path_in_dir(directory):
142 """Return the full path of the first file/dir in *directory*."""
143 return os.path.join(directory, os.listdir(directory)[0])
144
145
146def _get_by_attr(collection, attr, value):
147 """Return the first item in collection having attr == value.
148
149 Return None if the item is not found.
150 """
151 for item in collection:
152 if getattr(item, attr) == value:
153 return item
154
155
156def get_release_file_url(project, series_name, release_version):
157 """Return the URL of the release file hosted in Launchpad.
158
159 The returned URL points to a release file for the given project, series
160 name and release version.
161 The argument *project* is a project object as returned by launchpadlib.
162 The arguments *series_name* and *release_version* are strings. If
163 *release_version* is None, the URL of the latest release will be returned.
164 """
165 series = _get_by_attr(project.series, 'name', series_name)
166 if series is None:
167 raise ValueError('%r: series not found' % series_name)
168 # Releases are returned by Launchpad in reverse date order.
169 releases = list(series.releases)
170 if not releases:
171 raise ValueError('%r: series does not contain releases' % series_name)
172 if release_version is not None:
173 release = _get_by_attr(releases, 'version', release_version)
174 if release is None:
175 raise ValueError('%r: release not found' % release_version)
176 releases = [release]
177 for release in releases:
178 for file_ in release.files:
179 if str(file_).endswith('.tgz'):
180 return file_.file_link
181 raise ValueError('%r: file not found' % release_version)
182
183
184def get_zookeeper_address(agent_file_path):
185 """Retrieve the Zookeeper address contained in the given *agent_file_path*.
186
187 The *agent_file_path* is a path to a file containing a line similar to the
188 following::
189
190 env JUJU_ZOOKEEPER="address"
191 """
192 line = search_file('JUJU_ZOOKEEPER', agent_file_path).strip()
193 return line.split('=')[1].strip('"')
194
195
196@contextmanager
197def log_hook():
198 """Log when a hook starts and stops its execution.
199
200 Also log to stdout possible CalledProcessError exceptions raised executing
201 the hook.
202 """
203 script = script_name()
204 log(">>> Entering {}".format(script))
205 try:
206 yield
207 except CalledProcessError as err:
208 log('Exception caught:')
209 log(err.output)
210 raise
211 finally:
212 log("<<< Exiting {}".format(script))
213
214
215def parse_source(source):
216 """Parse the ``juju-gui-source`` option.
217
218 Return a tuple of two elements representing info on how to deploy Juju GUI.
219 Examples:
220 - ('stable', None): latest stable release;
221 - ('stable', '0.1.0'): stable release v0.1.0;
222 - ('trunk', None): latest trunk release;
223 - ('trunk', '0.1.0+build.1'): trunk release v0.1.0 bzr revision 1;
224 - ('branch', 'lp:juju-gui'): release is made from a branch;
225 - ('url', 'http://example.com/gui'): release from a downloaded file.
226 """
227 if source.startswith('url:'):
228 source = source[4:]
229 # Support file paths, including relative paths.
230 if urlparse(source).scheme == '':
231 if not source.startswith('/'):
232 source = os.path.join(os.path.abspath(CURRENT_DIR), source)
233 source = "file://%s" % source
234 return 'url', source
235 if source in ('stable', 'trunk'):
236 return source, None
237 if source.startswith('lp:') or source.startswith('http://'):
238 return 'branch', source
239 if 'build' in source:
240 return 'trunk', source
241 return 'stable', source
242
243
244def render_to_file(template_name, context, destination):
245 """Render the given *template_name* into *destination* using *context*.
246
247 The tempita template language is used to render contents
248 (see http://pythonpaste.org/tempita/).
249 The argument *template_name* is the name or path of the template file:
250 it may be either a path relative to ``../config`` or an absolute path.
251 The argument *destination* is a file path.
252 The argument *context* is a dict-like object.
253 """
254 template_path = os.path.abspath(template_name)
255 template = tempita.Template.from_filename(template_path)
256 with open(destination, 'w') as stream:
257 stream.write(template.substitute(context))
258
259
260results_log = None
261
262
263def _setupLogging():
264 global results_log
265 if results_log is not None:
266 return
267 cfg = config()
268 logging.basicConfig(
269 filename=cfg['command-log-file'],
270 level=logging.INFO,
271 format="%(asctime)s: %(name)s@%(levelname)s %(message)s")
272 results_log = logging.getLogger('juju-gui')
273
274
275def cmd_log(results):
276 global results_log
277 if not results:
278 return
279 if results_log is None:
280 _setupLogging()
281 # Since 'results' may be multi-line output, start it on a separate line
282 # from the logger timestamp, etc.
283 results_log.info('\n' + results)
284
285
286def start_improv(staging_env, ssl_cert_path,
287 config_path='/etc/init/juju-api-improv.conf'):
288 """Start a simulated juju environment using ``improv.py``."""
289 log('Setting up staging start up script.')
290 context = {
291 'juju_dir': JUJU_DIR,
292 'keys': ssl_cert_path,
293 'port': API_PORT,
294 'staging_env': staging_env,
295 }
296 render_to_file('config/juju-api-improv.conf.template', context, config_path)
297 log('Starting the staging backend.')
298 with su('root'):
299 service_start(IMPROV)
300
301
302def start_agent(
303 ssl_cert_path, config_path='/etc/init/juju-api-agent.conf',
304 read_only=False):
305 """Start the Juju agent and connect to the current environment."""
306 # Retrieve the Zookeeper address from the start up script.
307 unit_dir = os.path.realpath(os.path.join(CURRENT_DIR, '..'))
308 agent_file = '/etc/init/juju-{0}.conf'.format(os.path.basename(unit_dir))
309 zookeeper = get_zookeeper_address(agent_file)
310 log('Setting up API agent start up script.')
311 context = {
312 'juju_dir': JUJU_DIR,
313 'keys': ssl_cert_path,
314 'port': API_PORT,
315 'zookeeper': zookeeper,
316 'read_only': read_only
317 }
318 render_to_file('config/juju-api-agent.conf.template', context, config_path)
319 log('Starting API agent.')
320 with su('root'):
321 service_start(AGENT)
322
323
324def start_gui(
325 console_enabled, login_help, readonly, in_staging, ssl_cert_path,
326 charmworld_url, serve_tests, haproxy_path='/etc/haproxy/haproxy.cfg',
327 config_js_path=None, secure=True, sandbox=False):
328 """Set up and start the Juju GUI server."""
329 with su('root'):
330 run('chown', '-R', 'ubuntu:', JUJU_GUI_DIR)
331 # XXX 2013-02-05 frankban bug=1116320:
332 # External insecure resources are still loaded when testing in the
333 # debug environment. For now, switch to the production environment if
334 # the charm is configured to serve tests.
335 if in_staging and not serve_tests:
336 build_dirname = 'build-debug'
337 else:
338 build_dirname = 'build-prod'
339 build_dir = os.path.join(JUJU_GUI_DIR, build_dirname)
340 log('Generating the Juju GUI configuration file.')
341 is_legacy_juju = legacy_juju()
342 user, password = None, None
343 if (is_legacy_juju and in_staging) or sandbox:
344 user, password = 'admin', 'admin'
345 else:
346 user, password = None, None
347
348 api_backend = 'python' if is_legacy_juju else 'go'
349 if secure:
350 protocol = 'wss'
351 else:
352 log('Running in insecure mode! Port 80 will serve unencrypted.')
353 protocol = 'ws'
354
355 context = {
356 'raw_protocol': protocol,
357 'address': unit_get('public-address'),
358 'console_enabled': json.dumps(console_enabled),
359 'login_help': json.dumps(login_help),
360 'password': json.dumps(password),
361 'api_backend': json.dumps(api_backend),
362 'readonly': json.dumps(readonly),
363 'user': json.dumps(user),
364 'protocol': json.dumps(protocol),
365 'sandbox': json.dumps(sandbox),
366 'charmworld_url': json.dumps(charmworld_url),
367 }
368 if config_js_path is None:
369 config_js_path = os.path.join(
370 build_dir, 'juju-ui', 'assets', 'config.js')
371 render_to_file('config/config.js.template', context, config_js_path)
372
373 write_apache_config(build_dir, serve_tests)
374
375 log('Generating haproxy configuration file.')
376 if is_legacy_juju:
377 # The PyJuju API agent is listening on localhost.
378 api_address = '127.0.0.1:{0}'.format(API_PORT)
379 else:
380 # Retrieve the juju-core API server address.
381 api_address = get_api_address(os.path.join(CURRENT_DIR, '..'))
382 context = {
383 'api_address': api_address,
384 'api_pem': JUJU_PEM,
385 'legacy_juju': is_legacy_juju,
386 'ssl_cert_path': ssl_cert_path,
387 # In PyJuju environments, use the same certificate for both HTTPS and
388 # WebSocket connections. In juju-core the system already has the proper
389 # certificate installed.
390 'web_pem': JUJU_PEM,
391 'web_port': WEB_PORT,
392 'secure': secure
393 }
394 render_to_file('config/haproxy.cfg.template', context, haproxy_path)
395 log('Starting Juju GUI.')
396
397
398def write_apache_config(build_dir, serve_tests=False):
399 log('Generating the apache site configuration file.')
400 context = {
401 'port': WEB_PORT,
402 'serve_tests': serve_tests,
403 'server_root': build_dir,
404 'tests_root': os.path.join(JUJU_GUI_DIR, 'test', ''),
405 }
406 render_to_file('config/apache-ports.template', context, JUJU_GUI_PORTS)
407 render_to_file('config/apache-site.template', context, JUJU_GUI_SITE)
408
409
410def get_npm_cache_archive_url(Launchpad=Launchpad):
411 """Figure out the URL of the most recent NPM cache archive on Launchpad."""
412 launchpad = Launchpad.login_anonymously('Juju GUI charm', 'production')
413 project = launchpad.projects['juju-gui']
414 # Find the URL of the most recently created NPM cache archive.
415 npm_cache_url = get_release_file_url(project, 'npm-cache', None)
416 return npm_cache_url
417
418
419def prime_npm_cache(npm_cache_url):
420 """Download NPM cache archive and prime the NPM cache with it."""
421 # Download the cache archive and then uncompress it into the NPM cache.
422 npm_cache_archive = os.path.join(CURRENT_DIR, 'npm-cache.tgz')
423 cmd_log(run('curl', '-L', '-o', npm_cache_archive, npm_cache_url))
424 npm_cache_dir = os.path.expanduser('~/.npm')
425 # The NPM cache directory probably does not exist, so make it if not.
426 try:
427 os.mkdir(npm_cache_dir)
428 except OSError, e:
429 # If the directory already exists then ignore the error.
430 if e.errno != errno.EEXIST: # File exists.
431 raise
432 uncompress = command('tar', '-x', '-z', '-C', npm_cache_dir, '-f')
433 cmd_log(uncompress(npm_cache_archive))
434
435
436def fetch_gui(juju_gui_source, logpath):
437 """Retrieve the Juju GUI release/branch."""
438 # Retrieve a Juju GUI release.
439 origin, version_or_branch = parse_source(juju_gui_source)
440 if origin == 'branch':
441 # Make sure we have the dependencies necessary for us to actually make
442 # a build.
443 _get_build_dependencies()
444 # Create a release starting from a branch.
445 juju_gui_source_dir = os.path.join(CURRENT_DIR, 'juju-gui-source')
446 log('Retrieving Juju GUI source checkout from %s.' % version_or_branch)
447 cmd_log(run('rm', '-rf', juju_gui_source_dir))
448 cmd_log(bzr_checkout(version_or_branch, juju_gui_source_dir))
449 log('Preparing a Juju GUI release.')
450 logdir = os.path.dirname(logpath)
451 fd, name = tempfile.mkstemp(prefix='make-distfile-', dir=logdir)
452 log('Output from "make distfile" sent to %s' % name)
453 with environ(NO_BZR='1'):
454 run('make', '-C', juju_gui_source_dir, 'distfile',
455 stdout=fd, stderr=fd)
456 release_tarball = first_path_in_dir(
457 os.path.join(juju_gui_source_dir, 'releases'))
458 else:
459 log('Retrieving Juju GUI release.')
460 if origin == 'url':
461 file_url = version_or_branch
462 else:
463 # Retrieve a release from Launchpad.
464 launchpad = Launchpad.login_anonymously(
465 'Juju GUI charm', 'production')
466 project = launchpad.projects['juju-gui']
467 file_url = get_release_file_url(project, origin, version_or_branch)
468 log('Downloading release file from %s.' % file_url)
469 release_tarball = os.path.join(CURRENT_DIR, 'release.tgz')
470 cmd_log(run('curl', '-L', '-o', release_tarball, file_url))
471 return release_tarball
472
473
474def fetch_api(juju_api_branch):
475 """Retrieve the Juju branch."""
476 # Retrieve Juju API source checkout.
477 log('Retrieving Juju API source checkout.')
478 cmd_log(run('rm', '-rf', JUJU_DIR))
479 cmd_log(bzr_checkout(juju_api_branch, JUJU_DIR))
480
481
482def setup_gui(release_tarball):
483 """Set up Juju GUI."""
484 # Uncompress the release tarball.
485 log('Installing Juju GUI.')
486 release_dir = os.path.join(CURRENT_DIR, 'release')
487 cmd_log(run('rm', '-rf', release_dir))
488 os.mkdir(release_dir)
489 uncompress = command('tar', '-x', '-z', '-C', release_dir, '-f')
490 cmd_log(uncompress(release_tarball))
491 # Link the Juju GUI dir to the contents of the release tarball.
492 cmd_log(run('ln', '-sf', first_path_in_dir(release_dir), JUJU_GUI_DIR))
493
494
495def setup_apache():
496 """Set up apache."""
497 log('Setting up apache.')
498 if not os.path.exists(JUJU_GUI_SITE):
499 cmd_log(run('touch', JUJU_GUI_SITE))
500 cmd_log(run('chown', 'ubuntu:', JUJU_GUI_SITE))
501 cmd_log(
502 run('ln', '-s', JUJU_GUI_SITE,
503 '/etc/apache2/sites-enabled/juju-gui'))
504
505 if not os.path.exists(JUJU_GUI_PORTS):
506 cmd_log(run('touch', JUJU_GUI_PORTS))
507 cmd_log(run('chown', 'ubuntu:', JUJU_GUI_PORTS))
508
509 with su('root'):
510 run('a2dissite', 'default')
511 run('a2ensite', 'juju-gui')
512
513
514def save_or_create_certificates(
515 ssl_cert_path, ssl_cert_contents, ssl_key_contents):
516 """Generate the SSL certificates.
517
518 If both *ssl_cert_contents* and *ssl_key_contents* are provided, use them
519 as certificates; otherwise, generate them.
520
521 Also create a pem file, suitable for use in the haproxy configuration,
522 concatenating the key and the certificate files.
523 """
524 crt_path = os.path.join(ssl_cert_path, 'juju.crt')
525 key_path = os.path.join(ssl_cert_path, 'juju.key')
526 if not os.path.exists(ssl_cert_path):
527 os.makedirs(ssl_cert_path)
528 if ssl_cert_contents and ssl_key_contents:
529 # Save the provided certificates.
530 with open(crt_path, 'w') as cert_file:
531 cert_file.write(ssl_cert_contents)
532 with open(key_path, 'w') as key_file:
533 key_file.write(ssl_key_contents)
534 else:
535 # Generate certificates.
536 # See http://superuser.com/questions/226192/openssl-without-prompt
537 cmd_log(run(
538 'openssl', 'req', '-new', '-newkey', 'rsa:4096',
539 '-days', '365', '-nodes', '-x509', '-subj',
540 # These are arbitrary test values for the certificate.
541 '/C=GB/ST=Juju/L=GUI/O=Ubuntu/CN=juju.ubuntu.com',
542 '-keyout', key_path, '-out', crt_path))
543 # Generate the pem file.
544 pem_path = os.path.join(ssl_cert_path, JUJU_PEM)
545 if os.path.exists(pem_path):
546 os.remove(pem_path)
547 with open(pem_path, 'w') as pem_file:
548 shutil.copyfileobj(open(key_path), pem_file)
549 shutil.copyfileobj(open(crt_path), pem_file)
550
551
552def find_missing_packages(*packages):
553 """Given a list of packages, return the packages which are not installed.
554 """
555 cache = apt.Cache()
556 missing = set()
557 for pkg_name in packages:
558 try:
559 pkg = cache[pkg_name]
560 except KeyError:
561 missing.add(pkg_name)
562 continue
563 if pkg.is_installed:
564 continue
565 missing.add(pkg_name)
566 return missing
567
568
569## Backend support decorators
570
571def chain(name):
572 """Helper method to compose a set of mixin objects into a callable.
573
574 Each method is called in the context of its mixin instance, and its
575 argument is the Backend instance.
576 """
577 # Chain method calls through all implementing mixins.
578 def method(self):
579 for mixin in self.mixins:
580 a_callable = getattr(type(mixin), name, None)
581 if a_callable:
582 a_callable(mixin, self)
583
584 method.__name__ = name
585 return method
586
587
588def merge(name):
589 """Helper to merge a property from a set of strategy objects
590 into a unified set.
591 """
592 # Return merged property from every providing mixin as a set.
593 @property
594 def method(self):
595 result = set()
596 for mixin in self.mixins:
597 segment = getattr(type(mixin), name, None)
598 if segment and isinstance(segment, (list, tuple, set)):
599 result |= set(segment)
600
601 return result
602 return method
6030
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/network'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/network/__init__.py'
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/network/ovs'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/network/ovs/__init__.py'
--- lib/charm-helpers/charmhelpers/contrib/network/ovs/__init__.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/network/ovs/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,72 +0,0 @@
1''' Helpers for interacting with OpenvSwitch '''
2import subprocess
3import os
4from charmhelpers.core.hookenv import (
5 log, WARNING
6)
7from charmhelpers.core.host import (
8 service
9)
10
11
12def add_bridge(name):
13 ''' Add the named bridge to openvswitch '''
14 log('Creating bridge {}'.format(name))
15 subprocess.check_call(["ovs-vsctl", "--", "--may-exist", "add-br", name])
16
17
18def del_bridge(name):
19 ''' Delete the named bridge from openvswitch '''
20 log('Deleting bridge {}'.format(name))
21 subprocess.check_call(["ovs-vsctl", "--", "--if-exists", "del-br", name])
22
23
24def add_bridge_port(name, port):
25 ''' Add a port to the named openvswitch bridge '''
26 log('Adding port {} to bridge {}'.format(port, name))
27 subprocess.check_call(["ovs-vsctl", "--", "--may-exist", "add-port",
28 name, port])
29 subprocess.check_call(["ip", "link", "set", port, "up"])
30
31
32def del_bridge_port(name, port):
33 ''' Delete a port from the named openvswitch bridge '''
34 log('Deleting port {} from bridge {}'.format(port, name))
35 subprocess.check_call(["ovs-vsctl", "--", "--if-exists", "del-port",
36 name, port])
37 subprocess.check_call(["ip", "link", "set", port, "down"])
38
39
40def set_manager(manager):
41 ''' Set the controller for the local openvswitch '''
42 log('Setting manager for local ovs to {}'.format(manager))
43 subprocess.check_call(['ovs-vsctl', 'set-manager',
44 'ssl:{}'.format(manager)])
45
46
47CERT_PATH = '/etc/openvswitch/ovsclient-cert.pem'
48
49
50def get_certificate():
51 ''' Read openvswitch certificate from disk '''
52 if os.path.exists(CERT_PATH):
53 log('Reading ovs certificate from {}'.format(CERT_PATH))
54 with open(CERT_PATH, 'r') as cert:
55 full_cert = cert.read()
56 begin_marker = "-----BEGIN CERTIFICATE-----"
57 end_marker = "-----END CERTIFICATE-----"
58 begin_index = full_cert.find(begin_marker)
59 end_index = full_cert.rfind(end_marker)
60 if end_index == -1 or begin_index == -1:
61 raise RuntimeError("Certificate does not contain valid begin"
62 " and end markers.")
63 full_cert = full_cert[begin_index:(end_index + len(end_marker))]
64 return full_cert
65 else:
66 log('Certificate not found', level=WARNING)
67 return None
68
69
70def full_restart():
71 ''' Full restart and reload of openvswitch '''
72 service('force-reload-kmod', 'openvswitch-switch')
730
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/openstack'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/__init__.py'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/context.py'
--- lib/charm-helpers/charmhelpers/contrib/openstack/context.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/openstack/context.py 1970-01-01 00:00:00 +0000
@@ -1,271 +0,0 @@
1import os
2
3from base64 import b64decode
4
5from subprocess import (
6 check_call
7)
8
9from charmhelpers.core.hookenv import (
10 config,
11 local_unit,
12 log,
13 relation_get,
14 relation_ids,
15 related_units,
16 unit_get,
17)
18
19from charmhelpers.contrib.hahelpers.cluster import (
20 determine_api_port,
21 determine_haproxy_port,
22 https,
23 is_clustered,
24 peer_units,
25)
26
27from charmhelpers.contrib.hahelpers.apache import (
28 get_cert,
29 get_ca_cert,
30)
31
32CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
33
34
35class OSContextError(Exception):
36 pass
37
38
39def context_complete(ctxt):
40 _missing = []
41 for k, v in ctxt.iteritems():
42 if v is None or v == '':
43 _missing.append(k)
44 if _missing:
45 log('Missing required data: %s' % ' '.join(_missing), level='INFO')
46 return False
47 return True
48
49
50class OSContextGenerator(object):
51 interfaces = []
52
53 def __call__(self):
54 raise NotImplementedError
55
56
57class SharedDBContext(OSContextGenerator):
58 interfaces = ['shared-db']
59
60 def __call__(self):
61 log('Generating template context for shared-db')
62 conf = config()
63 try:
64 database = conf['database']
65 username = conf['database-user']
66 except KeyError as e:
67 log('Could not generate shared_db context. '
68 'Missing required charm config options: %s.' % e)
69 raise OSContextError
70 ctxt = {}
71 for rid in relation_ids('shared-db'):
72 for unit in related_units(rid):
73 ctxt = {
74 'database_host': relation_get('db_host', rid=rid,
75 unit=unit),
76 'database': database,
77 'database_user': username,
78 'database_password': relation_get('password', rid=rid,
79 unit=unit)
80 }
81 if not context_complete(ctxt):
82 return {}
83 return ctxt
84
85
86class IdentityServiceContext(OSContextGenerator):
87 interfaces = ['identity-service']
88
89 def __call__(self):
90 log('Generating template context for identity-service')
91 ctxt = {}
92
93 for rid in relation_ids('identity-service'):
94 for unit in related_units(rid):
95 ctxt = {
96 'service_port': relation_get('service_port', rid=rid,
97 unit=unit),
98 'service_host': relation_get('service_host', rid=rid,
99 unit=unit),
100 'auth_host': relation_get('auth_host', rid=rid, unit=unit),
101 'auth_port': relation_get('auth_port', rid=rid, unit=unit),
102 'admin_tenant_name': relation_get('service_tenant',
103 rid=rid, unit=unit),
104 'admin_user': relation_get('service_username', rid=rid,
105 unit=unit),
106 'admin_password': relation_get('service_password', rid=rid,
107 unit=unit),
108 # XXX: Hard-coded http.
109 'service_protocol': 'http',
110 'auth_protocol': 'http',
111 }
112 if not context_complete(ctxt):
113 return {}
114 return ctxt
115
116
117class AMQPContext(OSContextGenerator):
118 interfaces = ['amqp']
119
120 def __call__(self):
121 log('Generating template context for amqp')
122 conf = config()
123 try:
124 username = conf['rabbit-user']
125 vhost = conf['rabbit-vhost']
126 except KeyError as e:
127 log('Could not generate shared_db context. '
128 'Missing required charm config options: %s.' % e)
129 raise OSContextError
130
131 ctxt = {}
132 for rid in relation_ids('amqp'):
133 for unit in related_units(rid):
134 if relation_get('clustered', rid=rid, unit=unit):
135 rabbitmq_host = relation_get('vip', rid=rid, unit=unit)
136 else:
137 rabbitmq_host = relation_get('private-address',
138 rid=rid, unit=unit)
139 ctxt = {
140 'rabbitmq_host': rabbitmq_host,
141 'rabbitmq_user': username,
142 'rabbitmq_password': relation_get('password', rid=rid,
143 unit=unit),
144 'rabbitmq_virtual_host': vhost,
145 }
146 if not context_complete(ctxt):
147 return {}
148 return ctxt
149
150
151class CephContext(OSContextGenerator):
152 interfaces = ['ceph']
153
154 def __call__(self):
155 '''This generates context for /etc/ceph/ceph.conf templates'''
156 log('Generating tmeplate context for ceph')
157 mon_hosts = []
158 auth = None
159 for rid in relation_ids('ceph'):
160 for unit in related_units(rid):
161 mon_hosts.append(relation_get('private-address', rid=rid,
162 unit=unit))
163 auth = relation_get('auth', rid=rid, unit=unit)
164
165 ctxt = {
166 'mon_hosts': ' '.join(mon_hosts),
167 'auth': auth,
168 }
169 if not context_complete(ctxt):
170 return {}
171 return ctxt
172
173
174class HAProxyContext(OSContextGenerator):
175 interfaces = ['cluster']
176
177 def __call__(self):
178 '''
179 Builds half a context for the haproxy template, which describes
180 all peers to be included in the cluster. Each charm needs to include
181 its own context generator that describes the port mapping.
182 '''
183 if not relation_ids('cluster'):
184 return {}
185
186 cluster_hosts = {}
187 l_unit = local_unit().replace('/', '-')
188 cluster_hosts[l_unit] = unit_get('private-address')
189
190 for rid in relation_ids('cluster'):
191 for unit in related_units(rid):
192 _unit = unit.replace('/', '-')
193 addr = relation_get('private-address', rid=rid, unit=unit)
194 cluster_hosts[_unit] = addr
195
196 ctxt = {
197 'units': cluster_hosts,
198 }
199 if len(cluster_hosts.keys()) > 1:
200 # Enable haproxy when we have enough peers.
201 log('Ensuring haproxy enabled in /etc/default/haproxy.')
202 with open('/etc/default/haproxy', 'w') as out:
203 out.write('ENABLED=1\n')
204 return ctxt
205 log('HAProxy context is incomplete, this unit has no peers.')
206 return {}
207
208
209class ApacheSSLContext(OSContextGenerator):
210 """
211 Generates a context for an apache vhost configuration that configures
212 HTTPS reverse proxying for one or many endpoints. Generated context
213 looks something like:
214 {
215 'namespace': 'cinder',
216 'private_address': 'iscsi.mycinderhost.com',
217 'endpoints': [(8776, 8766), (8777, 8767)]
218 }
219
220 The endpoints list consists of a tuples mapping external ports
221 to internal ports.
222 """
223 interfaces = ['https']
224
225 # charms should inherit this context and set external ports
226 # and service namespace accordingly.
227 external_ports = []
228 service_namespace = None
229
230 def enable_modules(self):
231 cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http']
232 check_call(cmd)
233
234 def configure_cert(self):
235 if not os.path.isdir('/etc/apache2/ssl'):
236 os.mkdir('/etc/apache2/ssl')
237 ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace)
238 if not os.path.isdir(ssl_dir):
239 os.mkdir(ssl_dir)
240 cert, key = get_cert()
241 with open(os.path.join(ssl_dir, 'cert'), 'w') as cert_out:
242 cert_out.write(b64decode(cert))
243 with open(os.path.join(ssl_dir, 'key'), 'w') as key_out:
244 key_out.write(b64decode(key))
245 ca_cert = get_ca_cert()
246 if ca_cert:
247 with open(CA_CERT_PATH, 'w') as ca_out:
248 ca_out.write(b64decode(ca_cert))
249
250 def __call__(self):
251 if isinstance(self.external_ports, basestring):
252 self.external_ports = [self.external_ports]
253 if (not self.external_ports or not https()):
254 return {}
255
256 self.configure_cert()
257 self.enable_modules()
258
259 ctxt = {
260 'namespace': self.service_namespace,
261 'private_address': unit_get('private-address'),
262 'endpoints': []
263 }
264 for ext_port in self.external_ports:
265 if peer_units() or is_clustered():
266 int_port = determine_haproxy_port(ext_port)
267 else:
268 int_port = determine_api_port(ext_port)
269 portmap = (int(ext_port), int(int_port))
270 ctxt['endpoints'].append(portmap)
271 return ctxt
2720
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/openstack/templates'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/templates/__init__.py'
--- lib/charm-helpers/charmhelpers/contrib/openstack/templates/__init__.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/openstack/templates/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,2 +0,0 @@
1# dummy __init__.py to fool syncer into thinking this is a syncable python
2# module
30
=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/templates/ceph.conf'
--- lib/charm-helpers/charmhelpers/contrib/openstack/templates/ceph.conf 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/openstack/templates/ceph.conf 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1###############################################################################
2# [ WARNING ]
3# cinder configuration file maintained by Juju
4# local changes may be overwritten.
5###############################################################################
6{% if auth %}
7[global]
8 auth_supported = {{ auth }}
9 keyring = /etc/ceph/$cluster.$name.keyring
10 mon host = {{ mon_hosts }}
11{% endif %}
120
=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/templates/haproxy.cfg'
--- lib/charm-helpers/charmhelpers/contrib/openstack/templates/haproxy.cfg 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/openstack/templates/haproxy.cfg 1970-01-01 00:00:00 +0000
@@ -1,37 +0,0 @@
1global
2 log 127.0.0.1 local0
3 log 127.0.0.1 local1 notice
4 maxconn 20000
5 user haproxy
6 group haproxy
7 spread-checks 0
8
9defaults
10 log global
11 mode http
12 option httplog
13 option dontlognull
14 retries 3
15 timeout queue 1000
16 timeout connect 1000
17 timeout client 30000
18 timeout server 30000
19
20listen stats :8888
21 mode http
22 stats enable
23 stats hide-version
24 stats realm Haproxy\ Statistics
25 stats uri /
26 stats auth admin:password
27
28{% if units %}
29{% for service, ports in service_ports.iteritems() -%}
30listen {{ service }} 0.0.0.0:{{ ports[0] }}
31 balance roundrobin
32 option tcplog
33 {% for unit, address in units.iteritems() -%}
34 server {{ unit }} {{ address }}:{{ ports[1] }} check
35 {% endfor %}
36{% endfor %}
37{% endif %}
380
=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/templates/openstack_https_frontend'
--- lib/charm-helpers/charmhelpers/contrib/openstack/templates/openstack_https_frontend 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/openstack/templates/openstack_https_frontend 1970-01-01 00:00:00 +0000
@@ -1,23 +0,0 @@
1{% if endpoints %}
2{% for ext, int in endpoints %}
3Listen {{ ext }}
4NameVirtualHost *:{{ ext }}
5<VirtualHost *:{{ ext }}>
6 ServerName {{ private_address }}
7 SSLEngine on
8 SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert
9 SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key
10 ProxyPass / http://localhost:{{ int }}/
11 ProxyPassReverse / http://localhost:{{ int }}/
12 ProxyPreserveHost on
13</VirtualHost>
14<Proxy *>
15 Order deny,allow
16 Allow from all
17</Proxy>
18<Location />
19 Order allow,deny
20 Allow from all
21</Location>
22{% endfor %}
23{% endif %}
240
=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/templating.py'
--- lib/charm-helpers/charmhelpers/contrib/openstack/templating.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/openstack/templating.py 1970-01-01 00:00:00 +0000
@@ -1,261 +0,0 @@
1import os
2
3from charmhelpers.core.host import apt_install
4
5from charmhelpers.core.hookenv import (
6 log,
7 ERROR,
8 INFO
9)
10
11from charmhelpers.contrib.openstack.utils import OPENSTACK_CODENAMES
12
13try:
14 from jinja2 import FileSystemLoader, ChoiceLoader, Environment
15except ImportError:
16 # python-jinja2 may not be installed yet, or we're running unittests.
17 FileSystemLoader = ChoiceLoader = Environment = None
18
19
20class OSConfigException(Exception):
21 pass
22
23
24def get_loader(templates_dir, os_release):
25 """
26 Create a jinja2.ChoiceLoader containing template dirs up to
27 and including os_release. If directory template directory
28 is missing at templates_dir, it will be omitted from the loader.
29 templates_dir is added to the bottom of the search list as a base
30 loading dir.
31
32 A charm may also ship a templates dir with this module
33 and it will be appended to the bottom of the search list, eg:
34 hooks/charmhelpers/contrib/openstack/templates.
35
36 :param templates_dir: str: Base template directory containing release
37 sub-directories.
38 :param os_release : str: OpenStack release codename to construct template
39 loader.
40
41 :returns : jinja2.ChoiceLoader constructed with a list of
42 jinja2.FilesystemLoaders, ordered in descending
43 order by OpenStack release.
44 """
45 tmpl_dirs = [(rel, os.path.join(templates_dir, rel))
46 for rel in OPENSTACK_CODENAMES.itervalues()]
47
48 if not os.path.isdir(templates_dir):
49 log('Templates directory not found @ %s.' % templates_dir,
50 level=ERROR)
51 raise OSConfigException
52
53 # the bottom contains tempaltes_dir and possibly a common templates dir
54 # shipped with the helper.
55 loaders = [FileSystemLoader(templates_dir)]
56 helper_templates = os.path.join(os.path.dirname(__file__), 'templates')
57 if os.path.isdir(helper_templates):
58 loaders.append(FileSystemLoader(helper_templates))
59
60 for rel, tmpl_dir in tmpl_dirs:
61 if os.path.isdir(tmpl_dir):
62 loaders.insert(0, FileSystemLoader(tmpl_dir))
63 if rel == os_release:
64 break
65 log('Creating choice loader with dirs: %s' %
66 [l.searchpath for l in loaders], level=INFO)
67 return ChoiceLoader(loaders)
68
69
70class OSConfigTemplate(object):
71 """
72 Associates a config file template with a list of context generators.
73 Responsible for constructing a template context based on those generators.
74 """
75 def __init__(self, config_file, contexts):
76 self.config_file = config_file
77
78 if hasattr(contexts, '__call__'):
79 self.contexts = [contexts]
80 else:
81 self.contexts = contexts
82
83 self._complete_contexts = []
84
85 def context(self):
86 ctxt = {}
87 for context in self.contexts:
88 _ctxt = context()
89 if _ctxt:
90 ctxt.update(_ctxt)
91 # track interfaces for every complete context.
92 [self._complete_contexts.append(interface)
93 for interface in context.interfaces
94 if interface not in self._complete_contexts]
95 return ctxt
96
97 def complete_contexts(self):
98 '''
99 Return a list of interfaces that have atisfied contexts.
100 '''
101 if self._complete_contexts:
102 return self._complete_contexts
103 self.context()
104 return self._complete_contexts
105
106
107class OSConfigRenderer(object):
108 """
109 This class provides a common templating system to be used by OpenStack
110 charms. It is intended to help charms share common code and templates,
111 and ease the burden of managing config templates across multiple OpenStack
112 releases.
113
114 Basic usage:
115 # import some common context generates from charmhelpers
116 from charmhelpers.contrib.openstack import context
117
118 # Create a renderer object for a specific OS release.
119 configs = OSConfigRenderer(templates_dir='/tmp/templates',
120 openstack_release='folsom')
121 # register some config files with context generators.
122 configs.register(config_file='/etc/nova/nova.conf',
123 contexts=[context.SharedDBContext(),
124 context.AMQPContext()])
125 configs.register(config_file='/etc/nova/api-paste.ini',
126 contexts=[context.IdentityServiceContext()])
127 configs.register(config_file='/etc/haproxy/haproxy.conf',
128 contexts=[context.HAProxyContext()])
129 # write out a single config
130 configs.write('/etc/nova/nova.conf')
131 # write out all registered configs
132 configs.write_all()
133
134 Details:
135
136 OpenStack Releases and template loading
137 ---------------------------------------
138 When the object is instantiated, it is associated with a specific OS
139 release. This dictates how the template loader will be constructed.
140
141 The constructed loader attempts to load the template from several places
142 in the following order:
143 - from the most recent OS release-specific template dir (if one exists)
144 - the base templates_dir
145 - a template directory shipped in the charm with this helper file.
146
147
148 For the example above, '/tmp/templates' contains the following structure:
149 /tmp/templates/nova.conf
150 /tmp/templates/api-paste.ini
151 /tmp/templates/grizzly/api-paste.ini
152 /tmp/templates/havana/api-paste.ini
153
154 Since it was registered with the grizzly release, it first seraches
155 the grizzly directory for nova.conf, then the templates dir.
156
157 When writing api-paste.ini, it will find the template in the grizzly
158 directory.
159
160 If the object were created with folsom, it would fall back to the
161 base templates dir for its api-paste.ini template.
162
163 This system should help manage changes in config files through
164 openstack releases, allowing charms to fall back to the most recently
165 updated config template for a given release
166
167 The haproxy.conf, since it is not shipped in the templates dir, will
168 be loaded from the module directory's template directory, eg
169 $CHARM/hooks/charmhelpers/contrib/openstack/templates. This allows
170 us to ship common templates (haproxy, apache) with the helpers.
171
172 Context generators
173 ---------------------------------------
174 Context generators are used to generate template contexts during hook
175 execution. Doing so may require inspecting service relations, charm
176 config, etc. When registered, a config file is associated with a list
177 of generators. When a template is rendered and written, all context
178 generates are called in a chain to generate the context dictionary
179 passed to the jinja2 template. See context.py for more info.
180 """
181 def __init__(self, templates_dir, openstack_release):
182 if not os.path.isdir(templates_dir):
183 log('Could not locate templates dir %s' % templates_dir,
184 level=ERROR)
185 raise OSConfigException
186
187 self.templates_dir = templates_dir
188 self.openstack_release = openstack_release
189 self.templates = {}
190 self._tmpl_env = None
191
192 if None in [Environment, ChoiceLoader, FileSystemLoader]:
193 # if this code is running, the object is created pre-install hook.
194 # jinja2 shouldn't get touched until the module is reloaded on next
195 # hook execution, with proper jinja2 bits successfully imported.
196 apt_install('python-jinja2')
197
198 def register(self, config_file, contexts):
199 """
200 Register a config file with a list of context generators to be called
201 during rendering.
202 """
203 self.templates[config_file] = OSConfigTemplate(config_file=config_file,
204 contexts=contexts)
205 log('Registered config file: %s' % config_file, level=INFO)
206
207 def _get_tmpl_env(self):
208 if not self._tmpl_env:
209 loader = get_loader(self.templates_dir, self.openstack_release)
210 self._tmpl_env = Environment(loader=loader)
211
212 def _get_template(self, template):
213 self._get_tmpl_env()
214 template = self._tmpl_env.get_template(template)
215 log('Loaded template from %s' % template.filename, level=INFO)
216 return template
217
218 def render(self, config_file):
219 if config_file not in self.templates:
220 log('Config not registered: %s' % config_file, level=ERROR)
221 raise OSConfigException
222 ctxt = self.templates[config_file].context()
223 _tmpl = os.path.basename(config_file)
224 log('Rendering from template: %s' % _tmpl, level=INFO)
225 template = self._get_template(_tmpl)
226 return template.render(ctxt)
227
228 def write(self, config_file):
229 """
230 Write a single config file, raises if config file is not registered.
231 """
232 if config_file not in self.templates:
233 log('Config not registered: %s' % config_file, level=ERROR)
234 raise OSConfigException
235 with open(config_file, 'wb') as out:
236 out.write(self.render(config_file))
237 log('Wrote template %s.' % config_file, level=INFO)
238
239 def write_all(self):
240 """
241 Write out all registered config files.
242 """
243 [self.write(k) for k in self.templates.iterkeys()]
244
245 def set_release(self, openstack_release):
246 """
247 Resets the template environment and generates a new template loader
248 based on a the new openstack release.
249 """
250 self._tmpl_env = None
251 self.openstack_release = openstack_release
252 self._get_tmpl_env()
253
254 def complete_contexts(self):
255 '''
256 Returns a list of context interfaces that yield a complete context.
257 '''
258 interfaces = []
259 [interfaces.extend(i.complete_contexts())
260 for i in self.templates.itervalues()]
261 return interfaces
2620
=== removed file 'lib/charm-helpers/charmhelpers/contrib/openstack/utils.py'
--- lib/charm-helpers/charmhelpers/contrib/openstack/utils.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/openstack/utils.py 1970-01-01 00:00:00 +0000
@@ -1,271 +0,0 @@
1#!/usr/bin/python
2
3# Common python helper functions used for OpenStack charms.
4
5from collections import OrderedDict
6
7import apt_pkg as apt
8import subprocess
9import os
10import sys
11
12from charmhelpers.core.hookenv import (
13 config,
14 log as juju_log,
15)
16
17from charmhelpers.core.host import (
18 lsb_release,
19 apt_install
20)
21
22CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
23CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
24
25UBUNTU_OPENSTACK_RELEASE = OrderedDict([
26 ('oneiric', 'diablo'),
27 ('precise', 'essex'),
28 ('quantal', 'folsom'),
29 ('raring', 'grizzly'),
30 ('saucy', 'havana'),
31])
32
33
34OPENSTACK_CODENAMES = OrderedDict([
35 ('2011.2', 'diablo'),
36 ('2012.1', 'essex'),
37 ('2012.2', 'folsom'),
38 ('2013.1', 'grizzly'),
39 ('2013.2', 'havana'),
40 ('2014.1', 'icehouse'),
41])
42
43# The ugly duckling
44SWIFT_CODENAMES = {
45 '1.4.3': 'diablo',
46 '1.4.8': 'essex',
47 '1.7.4': 'folsom',
48 '1.7.6': 'grizzly',
49 '1.7.7': 'grizzly',
50 '1.8.0': 'grizzly',
51 '1.9.0': 'havana',
52 '1.9.1': 'havana',
53}
54
55
56def error_out(msg):
57 juju_log("FATAL ERROR: %s" % msg, level='ERROR')
58 sys.exit(1)
59
60
61def get_os_codename_install_source(src):
62 '''Derive OpenStack release codename from a given installation source.'''
63 ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
64 rel = ''
65 if src == 'distro':
66 try:
67 rel = UBUNTU_OPENSTACK_RELEASE[ubuntu_rel]
68 except KeyError:
69 e = 'Could not derive openstack release for '\
70 'this Ubuntu release: %s' % ubuntu_rel
71 error_out(e)
72 return rel
73
74 if src.startswith('cloud:'):
75 ca_rel = src.split(':')[1]
76 ca_rel = ca_rel.split('%s-' % ubuntu_rel)[1].split('/')[0]
77 return ca_rel
78
79 # Best guess match based on deb string provided
80 if src.startswith('deb') or src.startswith('ppa'):
81 for k, v in OPENSTACK_CODENAMES.iteritems():
82 if v in src:
83 return v
84
85
86def get_os_version_install_source(src):
87 codename = get_os_codename_install_source(src)
88 return get_os_version_codename(codename)
89
90
91def get_os_codename_version(vers):
92 '''Determine OpenStack codename from version number.'''
93 try:
94 return OPENSTACK_CODENAMES[vers]
95 except KeyError:
96 e = 'Could not determine OpenStack codename for version %s' % vers
97 error_out(e)
98
99
100def get_os_version_codename(codename):
101 '''Determine OpenStack version number from codename.'''
102 for k, v in OPENSTACK_CODENAMES.iteritems():
103 if v == codename:
104 return k
105 e = 'Could not derive OpenStack version for '\
106 'codename: %s' % codename
107 error_out(e)
108
109
110def get_os_codename_package(package, fatal=True):
111 '''Derive OpenStack release codename from an installed package.'''
112 apt.init()
113 cache = apt.Cache()
114
115 try:
116 pkg = cache[package]
117 except:
118 if not fatal:
119 return None
120 # the package is unknown to the current apt cache.
121 e = 'Could not determine version of package with no installation '\
122 'candidate: %s' % package
123 error_out(e)
124
125 if not pkg.current_ver:
126 if not fatal:
127 return None
128 # package is known, but no version is currently installed.
129 e = 'Could not determine version of uninstalled package: %s' % package
130 error_out(e)
131
132 vers = apt.UpstreamVersion(pkg.current_ver.ver_str)
133
134 try:
135 if 'swift' in pkg.name:
136 vers = vers[:5]
137 return SWIFT_CODENAMES[vers]
138 else:
139 vers = vers[:6]
140 return OPENSTACK_CODENAMES[vers]
141 except KeyError:
142 e = 'Could not determine OpenStack codename for version %s' % vers
143 error_out(e)
144
145
146def get_os_version_package(pkg, fatal=True):
147 '''Derive OpenStack version number from an installed package.'''
148 codename = get_os_codename_package(pkg, fatal=fatal)
149
150 if not codename:
151 return None
152
153 if 'swift' in pkg:
154 vers_map = SWIFT_CODENAMES
155 else:
156 vers_map = OPENSTACK_CODENAMES
157
158 for version, cname in vers_map.iteritems():
159 if cname == codename:
160 return version
161 #e = "Could not determine OpenStack version for package: %s" % pkg
162 #error_out(e)
163
164
165def import_key(keyid):
166 cmd = "apt-key adv --keyserver keyserver.ubuntu.com " \
167 "--recv-keys %s" % keyid
168 try:
169 subprocess.check_call(cmd.split(' '))
170 except subprocess.CalledProcessError:
171 error_out("Error importing repo key %s" % keyid)
172
173
174def configure_installation_source(rel):
175 '''Configure apt installation source.'''
176 if rel == 'distro':
177 return
178 elif rel[:4] == "ppa:":
179 src = rel
180 subprocess.check_call(["add-apt-repository", "-y", src])
181 elif rel[:3] == "deb":
182 l = len(rel.split('|'))
183 if l == 2:
184 src, key = rel.split('|')
185 juju_log("Importing PPA key from keyserver for %s" % src)
186 import_key(key)
187 elif l == 1:
188 src = rel
189 with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
190 f.write(src)
191 elif rel[:6] == 'cloud:':
192 ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
193 rel = rel.split(':')[1]
194 u_rel = rel.split('-')[0]
195 ca_rel = rel.split('-')[1]
196
197 if u_rel != ubuntu_rel:
198 e = 'Cannot install from Cloud Archive pocket %s on this Ubuntu '\
199 'version (%s)' % (ca_rel, ubuntu_rel)
200 error_out(e)
201
202 if 'staging' in ca_rel:
203 # staging is just a regular PPA.
204 os_rel = ca_rel.split('/')[0]
205 ppa = 'ppa:ubuntu-cloud-archive/%s-staging' % os_rel
206 cmd = 'add-apt-repository -y %s' % ppa
207 subprocess.check_call(cmd.split(' '))
208 return
209
210 # map charm config options to actual archive pockets.
211 pockets = {
212 'folsom': 'precise-updates/folsom',
213 'folsom/updates': 'precise-updates/folsom',
214 'folsom/proposed': 'precise-proposed/folsom',
215 'grizzly': 'precise-updates/grizzly',
216 'grizzly/updates': 'precise-updates/grizzly',
217 'grizzly/proposed': 'precise-proposed/grizzly',
218 'havana': 'precise-updates/havana',
219 'havana/updates': 'precise-updates/havana',
220 'havana/proposed': 'precise-proposed/havana',
221 }
222
223 try:
224 pocket = pockets[ca_rel]
225 except KeyError:
226 e = 'Invalid Cloud Archive release specified: %s' % rel
227 error_out(e)
228
229 src = "deb %s %s main" % (CLOUD_ARCHIVE_URL, pocket)
230 apt_install('ubuntu-cloud-keyring', fatal=True)
231
232 with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as f:
233 f.write(src)
234 else:
235 error_out("Invalid openstack-release specified: %s" % rel)
236
237
238def save_script_rc(script_path="scripts/scriptrc", **env_vars):
239 """
240 Write an rc file in the charm-delivered directory containing
241 exported environment variables provided by env_vars. Any charm scripts run
242 outside the juju hook environment can source this scriptrc to obtain
243 updated config information necessary to perform health checks or
244 service changes.
245 """
246 unit_name = os.getenv('JUJU_UNIT_NAME').replace('/', '-')
247 juju_rc_path = "/var/lib/juju/units/%s/charm/%s" % (unit_name, script_path)
248 with open(juju_rc_path, 'wb') as rc_script:
249 rc_script.write(
250 "#!/bin/bash\n")
251 [rc_script.write('export %s=%s\n' % (u, p))
252 for u, p in env_vars.iteritems() if u != "script_path"]
253
254
255def openstack_upgrade_available(package):
256 """
257 Determines if an OpenStack upgrade is available from installation
258 source, based on version of installed package.
259
260 :param package: str: Name of installed package.
261
262 :returns: bool: : Returns True if configured installation source offers
263 a newer version of package.
264
265 """
266
267 src = config('openstack-origin')
268 cur_vers = get_os_version_package(package)
269 available_vers = get_os_version_install_source(src)
270 apt.init()
271 return apt.version_compare(available_vers, cur_vers) == 1
2720
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/saltstack'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/saltstack/__init__.py'
--- lib/charm-helpers/charmhelpers/contrib/saltstack/__init__.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/saltstack/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,126 +0,0 @@
1"""Charm Helpers saltstack - declare the state of your machines.
2
3This helper enables you to declare your machine state, rather than
4program it procedurally (and have to test each change to your procedures).
5Your install hook can be as simple as:
6
7{{{
8from charmhelpers.contrib.saltstack import (
9 install_salt_support,
10 update_machine_state,
11)
12
13
14def install():
15 install_salt_support()
16 update_machine_state('machine_states/dependencies.yaml')
17 update_machine_state('machine_states/installed.yaml')
18}}}
19
20and won't need to change (nor will its tests) when you change the machine
21state.
22
23It's using a python package called salt-minion which allows various formats for
24specifying resources, such as:
25
26{{{
27/srv/{{ basedir }}:
28 file.directory:
29 - group: ubunet
30 - user: ubunet
31 - require:
32 - user: ubunet
33 - recurse:
34 - user
35 - group
36
37ubunet:
38 group.present:
39 - gid: 1500
40 user.present:
41 - uid: 1500
42 - gid: 1500
43 - createhome: False
44 - require:
45 - group: ubunet
46}}}
47
48The docs for all the different state definitions are at:
49 http://docs.saltstack.com/ref/states/all/
50
51
52TODO:
53 * Add test helpers which will ensure that machine state definitions
54 are functionally (but not necessarily logically) correct (ie. getting
55 salt to parse all state defs.
56 * Add a link to a public bootstrap charm example / blogpost.
57 * Find a way to obviate the need to use the grains['charm_dir'] syntax
58 in templates.
59"""
60# Copyright 2013 Canonical Ltd.
61#
62# Authors:
63# Charm Helpers Developers <juju@lists.ubuntu.com>
64import os
65import subprocess
66import yaml
67
68import charmhelpers.core.host
69import charmhelpers.core.hookenv
70
71
72charm_dir = os.environ.get('CHARM_DIR', '')
73salt_grains_path = '/etc/salt/grains'
74
75
76def install_salt_support(from_ppa=True):
77 """Installs the salt-minion helper for machine state.
78
79 By default the salt-minion package is installed from
80 the saltstack PPA. If from_ppa is False you must ensure
81 that the salt-minion package is available in the apt cache.
82 """
83 if from_ppa:
84 subprocess.check_call([
85 '/usr/bin/add-apt-repository',
86 '--yes',
87 'ppa:saltstack/salt',
88 ])
89 subprocess.check_call(['/usr/bin/apt-get', 'update'])
90 # We install salt-common as salt-minion would run the salt-minion
91 # daemon.
92 charmhelpers.core.host.apt_install('salt-common')
93
94
95def update_machine_state(state_path):
96 """Update the machine state using the provided state declaration."""
97 juju_config_2_grains()
98 subprocess.check_call([
99 'salt-call',
100 '--local',
101 'state.template',
102 state_path,
103 ])
104
105
106def juju_config_2_grains():
107 """Insert the juju config as salt grains for use in state templates.
108
109 This includes any current relation-get data, and the charm
110 directory.
111 """
112 config = charmhelpers.core.hookenv.config()
113
114 # Add the charm_dir which we will need to refer to charm
115 # file resources etc.
116 config['charm_dir'] = charm_dir
117 config['local_unit'] = charmhelpers.core.hookenv.local_unit()
118 config.update(charmhelpers.core.hookenv.relation_get())
119
120 # Don't use non-standard tags for unicode which will not
121 # work when salt uses yaml.load_safe.
122 yaml.add_representer(unicode, lambda dumper,
123 value: dumper.represent_scalar(
124 u'tag:yaml.org,2002:str', value))
125 with open(salt_grains_path, "w+") as fp:
126 fp.write(config.yaml())
1270
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/storage'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/storage/__init__.py'
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/storage/linux'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/storage/linux/__init__.py'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/storage/linux/loopback.py'
--- lib/charm-helpers/charmhelpers/contrib/storage/linux/loopback.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/storage/linux/loopback.py 1970-01-01 00:00:00 +0000
@@ -1,59 +0,0 @@
1
2import os
3import re
4
5from subprocess import (
6 check_call,
7 check_output,
8)
9
10
11##################################################
12# loopback device helpers.
13##################################################
14def loopback_devices():
15 '''
16 Parse through 'losetup -a' output to determine currently mapped
17 loopback devices. Output is expected to look like:
18
19 /dev/loop0: [0807]:961814 (/tmp/my.img)
20
21 :returns: dict: a dict mapping {loopback_dev: backing_file}
22 '''
23 loopbacks = {}
24 cmd = ['losetup', '-a']
25 devs = [d.strip().split(' ') for d in
26 check_output(cmd).splitlines() if d != '']
27 for dev, _, f in devs:
28 loopbacks[dev.replace(':', '')] = re.search('\((\S+)\)', f).groups()[0]
29 return loopbacks
30
31
32def create_loopback(file_path):
33 '''
34 Create a loopback device for a given backing file.
35
36 :returns: str: Full path to new loopback device (eg, /dev/loop0)
37 '''
38 cmd = ['losetup', '--find', file_path]
39 return check_output(cmd).strip()
40
41
42def ensure_loopback_device(path, size):
43 '''
44 Ensure a loopback device exists for a given backing file path and size.
45 If it a loopback device is not mapped to file, a new one will be created.
46
47 TODO: Confirm size of found loopback device.
48
49 :returns: str: Full path to the ensured loopback device (eg, /dev/loop0)
50 '''
51 for d, f in loopback_devices().iteritems():
52 if f == path:
53 return d
54
55 if not os.path.exists(path):
56 cmd = ['truncate', '--size', size, path]
57 check_call(cmd)
58
59 return create_loopback(path)
600
=== removed file 'lib/charm-helpers/charmhelpers/contrib/storage/linux/lvm.py'
--- lib/charm-helpers/charmhelpers/contrib/storage/linux/lvm.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/storage/linux/lvm.py 1970-01-01 00:00:00 +0000
@@ -1,88 +0,0 @@
1from subprocess import (
2 CalledProcessError,
3 check_call,
4 check_output,
5 Popen,
6 PIPE,
7)
8
9
10##################################################
11# LVM helpers.
12##################################################
13def deactivate_lvm_volume_group(block_device):
14 '''
15 Deactivate any volume gruop associated with an LVM physical volume.
16
17 :param block_device: str: Full path to LVM physical volume
18 '''
19 vg = list_lvm_volume_group(block_device)
20 if vg:
21 cmd = ['vgchange', '-an', vg]
22 check_call(cmd)
23
24
25def is_lvm_physical_volume(block_device):
26 '''
27 Determine whether a block device is initialized as an LVM PV.
28
29 :param block_device: str: Full path of block device to inspect.
30
31 :returns: boolean: True if block device is a PV, False if not.
32 '''
33 try:
34 check_output(['pvdisplay', block_device])
35 return True
36 except CalledProcessError:
37 return False
38
39
40def remove_lvm_physical_volume(block_device):
41 '''
42 Remove LVM PV signatures from a given block device.
43
44 :param block_device: str: Full path of block device to scrub.
45 '''
46 p = Popen(['pvremove', '-ff', block_device],
47 stdin=PIPE)
48 p.communicate(input='y\n')
49
50
51def list_lvm_volume_group(block_device):
52 '''
53 List LVM volume group associated with a given block device.
54
55 Assumes block device is a valid LVM PV.
56
57 :param block_device: str: Full path of block device to inspect.
58
59 :returns: str: Name of volume group associated with block device or None
60 '''
61 vg = None
62 pvd = check_output(['pvdisplay', block_device]).splitlines()
63 for l in pvd:
64 if l.strip().startswith('VG Name'):
65 vg = ' '.join(l.split()).split(' ').pop()
66 return vg
67
68
69def create_lvm_physical_volume(block_device):
70 '''
71 Initialize a block device as an LVM physical volume.
72
73 :param block_device: str: Full path of block device to initialize.
74
75 '''
76 check_call(['pvcreate', block_device])
77
78
79def create_lvm_volume_group(volume_group, block_device):
80 '''
81 Create an LVM volume group backed by a given block device.
82
83 Assumes block device has already been initialized as an LVM PV.
84
85 :param volume_group: str: Name of volume group to create.
86 :block_device: str: Full path of PV-initialized block device.
87 '''
88 check_call(['vgcreate', volume_group, block_device])
890
=== removed file 'lib/charm-helpers/charmhelpers/contrib/storage/linux/utils.py'
--- lib/charm-helpers/charmhelpers/contrib/storage/linux/utils.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/storage/linux/utils.py 1970-01-01 00:00:00 +0000
@@ -1,25 +0,0 @@
1from os import stat
2from stat import S_ISBLK
3
4from subprocess import (
5 check_call
6)
7
8
9def is_block_device(path):
10 '''
11 Confirm device at path is a valid block device node.
12
13 :returns: boolean: True if path is a block device, False if not.
14 '''
15 return S_ISBLK(stat(path).st_mode)
16
17
18def zap_disk(block_device):
19 '''
20 Clear a block device of partition table. Relies on sgdisk, which is
21 installed as pat of the 'gdisk' package in Ubuntu.
22
23 :param block_device: str: Full path of block device to clean.
24 '''
25 check_call(['sgdisk', '--zap-all', block_device])
260
=== removed directory 'lib/charm-helpers/charmhelpers/contrib/templating'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/templating/__init__.py'
=== removed file 'lib/charm-helpers/charmhelpers/contrib/templating/pyformat.py'
--- lib/charm-helpers/charmhelpers/contrib/templating/pyformat.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/contrib/templating/pyformat.py 1970-01-01 00:00:00 +0000
@@ -1,13 +0,0 @@
1'''
2Templating using standard Python str.format() method.
3'''
4
5from charmhelpers.core import hookenv
6
7
8def render(template, extra={}, **kwargs):
9 """Return the template rendered using Python's str.format()."""
10 context = hookenv.execution_environment()
11 context.update(extra)
12 context.update(kwargs)
13 return template.format(**context)
140
=== removed directory 'lib/charm-helpers/charmhelpers/core'
=== removed file 'lib/charm-helpers/charmhelpers/core/__init__.py'
=== removed file 'lib/charm-helpers/charmhelpers/core/hookenv.py'
--- lib/charm-helpers/charmhelpers/core/hookenv.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/core/hookenv.py 1970-01-01 00:00:00 +0000
@@ -1,340 +0,0 @@
1"Interactions with the Juju environment"
2# Copyright 2013 Canonical Ltd.
3#
4# Authors:
5# Charm Helpers Developers <juju@lists.ubuntu.com>
6
7import os
8import json
9import yaml
10import subprocess
11import UserDict
12
13CRITICAL = "CRITICAL"
14ERROR = "ERROR"
15WARNING = "WARNING"
16INFO = "INFO"
17DEBUG = "DEBUG"
18MARKER = object()
19
20cache = {}
21
22
23def cached(func):
24 ''' Cache return values for multiple executions of func + args
25
26 For example:
27
28 @cached
29 def unit_get(attribute):
30 pass
31
32 unit_get('test')
33
34 will cache the result of unit_get + 'test' for future calls.
35 '''
36 def wrapper(*args, **kwargs):
37 global cache
38 key = str((func, args, kwargs))
39 try:
40 return cache[key]
41 except KeyError:
42 res = func(*args, **kwargs)
43 cache[key] = res
44 return res
45 return wrapper
46
47
48def flush(key):
49 ''' Flushes any entries from function cache where the
50 key is found in the function+args '''
51 flush_list = []
52 for item in cache:
53 if key in item:
54 flush_list.append(item)
55 for item in flush_list:
56 del cache[item]
57
58
59def log(message, level=None):
60 "Write a message to the juju log"
61 command = ['juju-log']
62 if level:
63 command += ['-l', level]
64 command += [message]
65 subprocess.call(command)
66
67
68class Serializable(UserDict.IterableUserDict):
69 "Wrapper, an object that can be serialized to yaml or json"
70
71 def __init__(self, obj):
72 # wrap the object
73 UserDict.IterableUserDict.__init__(self)
74 self.data = obj
75
76 def __getattr__(self, attr):
77 # See if this object has attribute.
78 if attr in ("json", "yaml", "data"):
79 return self.__dict__[attr]
80 # Check for attribute in wrapped object.
81 got = getattr(self.data, attr, MARKER)
82 if got is not MARKER:
83 return got
84 # Proxy to the wrapped object via dict interface.
85 try:
86 return self.data[attr]
87 except KeyError:
88 raise AttributeError(attr)
89
90 def __getstate__(self):
91 # Pickle as a standard dictionary.
92 return self.data
93
94 def __setstate__(self, state):
95 # Unpickle into our wrapper.
96 self.data = state
97
98 def json(self):
99 "Serialize the object to json"
100 return json.dumps(self.data)
101
102 def yaml(self):
103 "Serialize the object to yaml"
104 return yaml.dump(self.data)
105
106
107def execution_environment():
108 """A convenient bundling of the current execution context"""
109 context = {}
110 context['conf'] = config()
111 if relation_id():
112 context['reltype'] = relation_type()
113 context['relid'] = relation_id()
114 context['rel'] = relation_get()
115 context['unit'] = local_unit()
116 context['rels'] = relations()
117 context['env'] = os.environ
118 return context
119
120
121def in_relation_hook():
122 "Determine whether we're running in a relation hook"
123 return 'JUJU_RELATION' in os.environ
124
125
126def relation_type():
127 "The scope for the current relation hook"
128 return os.environ.get('JUJU_RELATION', None)
129
130
131def relation_id():
132 "The relation ID for the current relation hook"
133 return os.environ.get('JUJU_RELATION_ID', None)
134
135
136def local_unit():
137 "Local unit ID"
138 return os.environ['JUJU_UNIT_NAME']
139
140
141def remote_unit():
142 "The remote unit for the current relation hook"
143 return os.environ['JUJU_REMOTE_UNIT']
144
145
146def service_name():
147 "The name service group this unit belongs to"
148 return local_unit().split('/')[0]
149
150
151@cached
152def config(scope=None):
153 "Juju charm configuration"
154 config_cmd_line = ['config-get']
155 if scope is not None:
156 config_cmd_line.append(scope)
157 config_cmd_line.append('--format=json')
158 try:
159 return json.loads(subprocess.check_output(config_cmd_line))
160 except ValueError:
161 return None
162
163
164@cached
165def relation_get(attribute=None, unit=None, rid=None):
166 _args = ['relation-get', '--format=json']
167 if rid:
168 _args.append('-r')
169 _args.append(rid)
170 _args.append(attribute or '-')
171 if unit:
172 _args.append(unit)
173 try:
174 return json.loads(subprocess.check_output(_args))
175 except ValueError:
176 return None
177
178
179def relation_set(relation_id=None, relation_settings={}, **kwargs):
180 relation_cmd_line = ['relation-set']
181 if relation_id is not None:
182 relation_cmd_line.extend(('-r', relation_id))
183 for k, v in (relation_settings.items() + kwargs.items()):
184 if v is None:
185 relation_cmd_line.append('{}='.format(k))
186 else:
187 relation_cmd_line.append('{}={}'.format(k, v))
188 subprocess.check_call(relation_cmd_line)
189 # Flush cache of any relation-gets for local unit
190 flush(local_unit())
191
192
193@cached
194def relation_ids(reltype=None):
195 "A list of relation_ids"
196 reltype = reltype or relation_type()
197 relid_cmd_line = ['relation-ids', '--format=json']
198 if reltype is not None:
199 relid_cmd_line.append(reltype)
200 return json.loads(subprocess.check_output(relid_cmd_line)) or []
201 return []
202
203
204@cached
205def related_units(relid=None):
206 "A list of related units"
207 relid = relid or relation_id()
208 units_cmd_line = ['relation-list', '--format=json']
209 if relid is not None:
210 units_cmd_line.extend(('-r', relid))
211 return json.loads(subprocess.check_output(units_cmd_line)) or []
212
213
214@cached
215def relation_for_unit(unit=None, rid=None):
216 "Get the json represenation of a unit's relation"
217 unit = unit or remote_unit()
218 relation = relation_get(unit=unit, rid=rid)
219 for key in relation:
220 if key.endswith('-list'):
221 relation[key] = relation[key].split()
222 relation['__unit__'] = unit
223 return relation
224
225
226@cached
227def relations_for_id(relid=None):
228 "Get relations of a specific relation ID"
229 relation_data = []
230 relid = relid or relation_ids()
231 for unit in related_units(relid):
232 unit_data = relation_for_unit(unit, relid)
233 unit_data['__relid__'] = relid
234 relation_data.append(unit_data)
235 return relation_data
236
237
238@cached
239def relations_of_type(reltype=None):
240 "Get relations of a specific type"
241 relation_data = []
242 reltype = reltype or relation_type()
243 for relid in relation_ids(reltype):
244 for relation in relations_for_id(relid):
245 relation['__relid__'] = relid
246 relation_data.append(relation)
247 return relation_data
248
249
250@cached
251def relation_types():
252 "Get a list of relation types supported by this charm"
253 charmdir = os.environ.get('CHARM_DIR', '')
254 mdf = open(os.path.join(charmdir, 'metadata.yaml'))
255 md = yaml.safe_load(mdf)
256 rel_types = []
257 for key in ('provides', 'requires', 'peers'):
258 section = md.get(key)
259 if section:
260 rel_types.extend(section.keys())
261 mdf.close()
262 return rel_types
263
264
265@cached
266def relations():
267 rels = {}
268 for reltype in relation_types():
269 relids = {}
270 for relid in relation_ids(reltype):
271 units = {local_unit(): relation_get(unit=local_unit(), rid=relid)}
272 for unit in related_units(relid):
273 reldata = relation_get(unit=unit, rid=relid)
274 units[unit] = reldata
275 relids[relid] = units
276 rels[reltype] = relids
277 return rels
278
279
280def open_port(port, protocol="TCP"):
281 "Open a service network port"
282 _args = ['open-port']
283 _args.append('{}/{}'.format(port, protocol))
284 subprocess.check_call(_args)
285
286
287def close_port(port, protocol="TCP"):
288 "Close a service network port"
289 _args = ['close-port']
290 _args.append('{}/{}'.format(port, protocol))
291 subprocess.check_call(_args)
292
293
294@cached
295def unit_get(attribute):
296 _args = ['unit-get', '--format=json', attribute]
297 try:
298 return json.loads(subprocess.check_output(_args))
299 except ValueError:
300 return None
301
302
303def unit_private_ip():
304 return unit_get('private-address')
305
306
307class UnregisteredHookError(Exception):
308 pass
309
310
311class Hooks(object):
312 def __init__(self):
313 super(Hooks, self).__init__()
314 self._hooks = {}
315
316 def register(self, name, function):
317 self._hooks[name] = function
318
319 def execute(self, args):
320 hook_name = os.path.basename(args[0])
321 if hook_name in self._hooks:
322 self._hooks[hook_name]()
323 else:
324 raise UnregisteredHookError(hook_name)
325
326 def hook(self, *hook_names):
327 def wrapper(decorated):
328 for hook_name in hook_names:
329 self.register(hook_name, decorated)
330 else:
331 self.register(decorated.__name__, decorated)
332 if '_' in decorated.__name__:
333 self.register(
334 decorated.__name__.replace('_', '-'), decorated)
335 return decorated
336 return wrapper
337
338
339def charm_dir():
340 return os.environ.get('CHARM_DIR')
3410
=== removed file 'lib/charm-helpers/charmhelpers/core/host.py'
--- lib/charm-helpers/charmhelpers/core/host.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/core/host.py 1970-01-01 00:00:00 +0000
@@ -1,272 +0,0 @@
1"""Tools for working with the host system"""
2# Copyright 2012 Canonical Ltd.
3#
4# Authors:
5# Nick Moffitt <nick.moffitt@canonical.com>
6# Matthew Wedgwood <matthew.wedgwood@canonical.com>
7
8import apt_pkg
9import os
10import pwd
11import grp
12import subprocess
13import hashlib
14
15from collections import OrderedDict
16
17from hookenv import log, execution_environment
18
19
20def service_start(service_name):
21 service('start', service_name)
22
23
24def service_stop(service_name):
25 service('stop', service_name)
26
27
28def service_restart(service_name):
29 service('restart', service_name)
30
31
32def service_reload(service_name, restart_on_failure=False):
33 if not service('reload', service_name) and restart_on_failure:
34 service('restart', service_name)
35
36
37def service(action, service_name):
38 cmd = ['service', service_name, action]
39 return subprocess.call(cmd) == 0
40
41
42def service_running(service):
43 try:
44 output = subprocess.check_output(['service', service, 'status'])
45 except subprocess.CalledProcessError:
46 return False
47 else:
48 if ("start/running" in output or "is running" in output):
49 return True
50 else:
51 return False
52
53
54def adduser(username, password=None, shell='/bin/bash', system_user=False):
55 """Add a user"""
56 try:
57 user_info = pwd.getpwnam(username)
58 log('user {0} already exists!'.format(username))
59 except KeyError:
60 log('creating user {0}'.format(username))
61 cmd = ['useradd']
62 if system_user or password is None:
63 cmd.append('--system')
64 else:
65 cmd.extend([
66 '--create-home',
67 '--shell', shell,
68 '--password', password,
69 ])
70 cmd.append(username)
71 subprocess.check_call(cmd)
72 user_info = pwd.getpwnam(username)
73 return user_info
74
75
76def add_user_to_group(username, group):
77 """Add a user to a group"""
78 cmd = [
79 'gpasswd', '-a',
80 username,
81 group
82 ]
83 log("Adding user {} to group {}".format(username, group))
84 subprocess.check_call(cmd)
85
86
87def rsync(from_path, to_path, flags='-r', options=None):
88 """Replicate the contents of a path"""
89 context = execution_environment()
90 options = options or ['--delete', '--executability']
91 cmd = ['/usr/bin/rsync', flags]
92 cmd.extend(options)
93 cmd.append(from_path.format(**context))
94 cmd.append(to_path.format(**context))
95 log(" ".join(cmd))
96 return subprocess.check_output(cmd).strip()
97
98
99def symlink(source, destination):
100 """Create a symbolic link"""
101 context = execution_environment()
102 log("Symlinking {} as {}".format(source, destination))
103 cmd = [
104 'ln',
105 '-sf',
106 source.format(**context),
107 destination.format(**context)
108 ]
109 subprocess.check_call(cmd)
110
111
112def mkdir(path, owner='root', group='root', perms=0555, force=False):
113 """Create a directory"""
114 context = execution_environment()
115 log("Making dir {} {}:{} {:o}".format(path, owner, group,
116 perms))
117 uid = pwd.getpwnam(owner.format(**context)).pw_uid
118 gid = grp.getgrnam(group.format(**context)).gr_gid
119 realpath = os.path.abspath(path)
120 if os.path.exists(realpath):
121 if force and not os.path.isdir(realpath):
122 log("Removing non-directory file {} prior to mkdir()".format(path))
123 os.unlink(realpath)
124 else:
125 os.makedirs(realpath, perms)
126 os.chown(realpath, uid, gid)
127
128
129def write_file(path, content, owner='root', group='root', perms=0444):
130 """Create or overwrite a file with the contents of a string"""
131 log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
132 uid = pwd.getpwnam(owner).pw_uid
133 gid = grp.getgrnam(group).gr_gid
134 with open(path, 'w') as target:
135 os.fchown(target.fileno(), uid, gid)
136 os.fchmod(target.fileno(), perms)
137 target.write(content)
138
139
140def filter_installed_packages(packages):
141 """Returns a list of packages that require installation"""
142 apt_pkg.init()
143 cache = apt_pkg.Cache()
144 _pkgs = []
145 for package in packages:
146 try:
147 p = cache[package]
148 p.current_ver or _pkgs.append(package)
149 except KeyError:
150 log('Package {} has no installation candidate.'.format(package),
151 level='WARNING')
152 _pkgs.append(package)
153 return _pkgs
154
155
156def apt_install(packages, options=None, fatal=False):
157 """Install one or more packages"""
158 options = options or []
159 cmd = ['apt-get', '-y']
160 cmd.extend(options)
161 cmd.append('install')
162 if isinstance(packages, basestring):
163 cmd.append(packages)
164 else:
165 cmd.extend(packages)
166 log("Installing {} with options: {}".format(packages,
167 options))
168 if fatal:
169 subprocess.check_call(cmd)
170 else:
171 subprocess.call(cmd)
172
173
174def apt_update(fatal=False):
175 """Update local apt cache"""
176 cmd = ['apt-get', 'update']
177 if fatal:
178 subprocess.check_call(cmd)
179 else:
180 subprocess.call(cmd)
181
182
183def mount(device, mountpoint, options=None, persist=False):
184 '''Mount a filesystem'''
185 cmd_args = ['mount']
186 if options is not None:
187 cmd_args.extend(['-o', options])
188 cmd_args.extend([device, mountpoint])
189 try:
190 subprocess.check_output(cmd_args)
191 except subprocess.CalledProcessError, e:
192 log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
193 return False
194 if persist:
195 # TODO: update fstab
196 pass
197 return True
198
199
200def umount(mountpoint, persist=False):
201 '''Unmount a filesystem'''
202 cmd_args = ['umount', mountpoint]
203 try:
204 subprocess.check_output(cmd_args)
205 except subprocess.CalledProcessError, e:
206 log('Error unmounting {}\n{}'.format(mountpoint, e.output))
207 return False
208 if persist:
209 # TODO: update fstab
210 pass
211 return True
212
213
214def mounts():
215 '''List of all mounted volumes as [[mountpoint,device],[...]]'''
216 with open('/proc/mounts') as f:
217 # [['/mount/point','/dev/path'],[...]]
218 system_mounts = [m[1::-1] for m in [l.strip().split()
219 for l in f.readlines()]]
220 return system_mounts
221
222
223def file_hash(path):
224 ''' Generate a md5 hash of the contents of 'path' or None if not found '''
225 if os.path.exists(path):
226 h = hashlib.md5()
227 with open(path, 'r') as source:
228 h.update(source.read()) # IGNORE:E1101 - it does have update
229 return h.hexdigest()
230 else:
231 return None
232
233
234def restart_on_change(restart_map):
235 ''' Restart services based on configuration files changing
236
237 This function is used a decorator, for example
238
239 @restart_on_change({
240 '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
241 })
242 def ceph_client_changed():
243 ...
244
245 In this example, the cinder-api and cinder-volume services
246 would be restarted if /etc/ceph/ceph.conf is changed by the
247 ceph_client_changed function.
248 '''
249 def wrap(f):
250 def wrapped_f(*args):
251 checksums = {}
252 for path in restart_map:
253 checksums[path] = file_hash(path)
254 f(*args)
255 restarts = []
256 for path in restart_map:
257 if checksums[path] != file_hash(path):
258 restarts += restart_map[path]
259 for service_name in list(OrderedDict.fromkeys(restarts)):
260 service('restart', service_name)
261 return wrapped_f
262 return wrap
263
264
265def lsb_release():
266 '''Return /etc/lsb-release in a dict'''
267 d = {}
268 with open('/etc/lsb-release', 'r') as lsb:
269 for l in lsb:
270 k, v = l.split('=')
271 d[k.strip()] = v.strip()
272 return d
2730
=== removed directory 'lib/charm-helpers/charmhelpers/fetch'
=== removed file 'lib/charm-helpers/charmhelpers/fetch/__init__.py'
--- lib/charm-helpers/charmhelpers/fetch/__init__.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/fetch/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,152 +0,0 @@
1import importlib
2from yaml import safe_load
3from charmhelpers.core.host import (
4 apt_install,
5 apt_update,
6 filter_installed_packages,
7 lsb_release
8)
9from urlparse import (
10 urlparse,
11 urlunparse,
12)
13import subprocess
14from charmhelpers.core.hookenv import (
15 config,
16 log,
17)
18
19CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
20deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
21"""
22PROPOSED_POCKET = """# Proposed
23deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
24"""
25
26
27def add_source(source, key=None):
28 if ((source.startswith('ppa:') or
29 source.startswith('http:'))):
30 subprocess.check_call(['add-apt-repository', source])
31 elif source.startswith('cloud:'):
32 apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
33 fatal=True)
34 pocket = source.split(':')[-1]
35 with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
36 apt.write(CLOUD_ARCHIVE.format(pocket))
37 elif source == 'proposed':
38 release = lsb_release()['DISTRIB_CODENAME']
39 with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
40 apt.write(PROPOSED_POCKET.format(release))
41 if key:
42 subprocess.check_call(['apt-key', 'import', key])
43
44
45class SourceConfigError(Exception):
46 pass
47
48
49def configure_sources(update=False,
50 sources_var='install_sources',
51 keys_var='install_keys'):
52 """
53 Configure multiple sources from charm configuration
54
55 Example config:
56 install_sources:
57 - "ppa:foo"
58 - "http://example.com/repo precise main"
59 install_keys:
60 - null
61 - "a1b2c3d4"
62
63 Note that 'null' (a.k.a. None) should not be quoted.
64 """
65 sources = safe_load(config(sources_var))
66 keys = safe_load(config(keys_var))
67 if isinstance(sources, basestring) and isinstance(keys, basestring):
68 add_source(sources, keys)
69 else:
70 if not len(sources) == len(keys):
71 msg = 'Install sources and keys lists are different lengths'
72 raise SourceConfigError(msg)
73 for src_num in range(len(sources)):
74 add_source(sources[src_num], keys[src_num])
75 if update:
76 apt_update(fatal=True)
77
78# The order of this list is very important. Handlers should be listed in from
79# least- to most-specific URL matching.
80FETCH_HANDLERS = (
81 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
82)
83
84
85class UnhandledSource(Exception):
86 pass
87
88
89def install_remote(source):
90 """
91 Install a file tree from a remote source
92
93 The specified source should be a url of the form:
94 scheme://[host]/path[#[option=value][&...]]
95
96 Schemes supported are based on this modules submodules
97 Options supported are submodule-specific"""
98 # We ONLY check for True here because can_handle may return a string
99 # explaining why it can't handle a given source.
100 handlers = [h for h in plugins() if h.can_handle(source) is True]
101 for handler in handlers:
102 try:
103 installed_to = handler.install(source)
104 except UnhandledSource:
105 pass
106 if not installed_to:
107 raise UnhandledSource("No handler found for source {}".format(source))
108 return installed_to
109
110
111def install_from_config(config_var_name):
112 charm_config = config()
113 source = charm_config[config_var_name]
114 return install_remote(source)
115
116
117class BaseFetchHandler(object):
118 """Base class for FetchHandler implementations in fetch plugins"""
119 def can_handle(self, source):
120 """Returns True if the source can be handled. Otherwise returns
121 a string explaining why it cannot"""
122 return "Wrong source type"
123
124 def install(self, source):
125 """Try to download and unpack the source. Return the path to the
126 unpacked files or raise UnhandledSource."""
127 raise UnhandledSource("Wrong source type {}".format(source))
128
129 def parse_url(self, url):
130 return urlparse(url)
131
132 def base_url(self, url):
133 """Return url without querystring or fragment"""
134 parts = list(self.parse_url(url))
135 parts[4:] = ['' for i in parts[4:]]
136 return urlunparse(parts)
137
138
139def plugins(fetch_handlers=None):
140 if not fetch_handlers:
141 fetch_handlers = FETCH_HANDLERS
142 plugin_list = []
143 for handler_name in fetch_handlers:
144 package, classname = handler_name.rsplit('.', 1)
145 try:
146 handler_class = getattr(importlib.import_module(package), classname)
147 plugin_list.append(handler_class())
148 except (ImportError, AttributeError):
149 # Skip missing plugins so that they can be ommitted from
150 # installation if desired
151 log("FetchHandler {} not found, skipping plugin".format(handler_name))
152 return plugin_list
1530
=== removed file 'lib/charm-helpers/charmhelpers/fetch/archiveurl.py'
--- lib/charm-helpers/charmhelpers/fetch/archiveurl.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/fetch/archiveurl.py 1970-01-01 00:00:00 +0000
@@ -1,48 +0,0 @@
1import os
2import urllib2
3from charmhelpers.fetch import (
4 BaseFetchHandler,
5 UnhandledSource
6)
7from charmhelpers.payload.archive import (
8 get_archive_handler,
9 extract,
10)
11from charmhelpers.core.host import mkdir
12
13
14class ArchiveUrlFetchHandler(BaseFetchHandler):
15 """Handler for archives via generic URLs"""
16 def can_handle(self, source):
17 url_parts = self.parse_url(source)
18 if url_parts.scheme not in ('http', 'https', 'ftp', 'file'):
19 return "Wrong source type"
20 if get_archive_handler(self.base_url(source)):
21 return True
22 return False
23
24 def download(self, source, dest):
25 # propogate all exceptions
26 # URLError, OSError, etc
27 response = urllib2.urlopen(source)
28 try:
29 with open(dest, 'w') as dest_file:
30 dest_file.write(response.read())
31 except Exception as e:
32 if os.path.isfile(dest):
33 os.unlink(dest)
34 raise e
35
36 def install(self, source):
37 url_parts = self.parse_url(source)
38 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched')
39 if not os.path.exists(dest_dir):
40 mkdir(dest_dir, perms=0755)
41 dld_file = os.path.join(dest_dir, os.path.basename(url_parts.path))
42 try:
43 self.download(source, dld_file)
44 except urllib2.URLError as e:
45 raise UnhandledSource(e.reason)
46 except OSError as e:
47 raise UnhandledSource(e.strerror)
48 return extract(dld_file)
490
=== removed directory 'lib/charm-helpers/charmhelpers/payload'
=== removed file 'lib/charm-helpers/charmhelpers/payload/__init__.py'
--- lib/charm-helpers/charmhelpers/payload/__init__.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/payload/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1"Tools for working with files injected into a charm just before deployment."
20
=== removed file 'lib/charm-helpers/charmhelpers/payload/archive.py'
--- lib/charm-helpers/charmhelpers/payload/archive.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/payload/archive.py 1970-01-01 00:00:00 +0000
@@ -1,57 +0,0 @@
1import os
2import tarfile
3import zipfile
4from charmhelpers.core import (
5 host,
6 hookenv,
7)
8
9
10class ArchiveError(Exception):
11 pass
12
13
14def get_archive_handler(archive_name):
15 if os.path.isfile(archive_name):
16 if tarfile.is_tarfile(archive_name):
17 return extract_tarfile
18 elif zipfile.is_zipfile(archive_name):
19 return extract_zipfile
20 else:
21 # look at the file name
22 for ext in ('.tar', '.tar.gz', '.tgz', 'tar.bz2', '.tbz2', '.tbz'):
23 if archive_name.endswith(ext):
24 return extract_tarfile
25 for ext in ('.zip', '.jar'):
26 if archive_name.endswith(ext):
27 return extract_zipfile
28
29
30def archive_dest_default(archive_name):
31 archive_file = os.path.basename(archive_name)
32 return os.path.join(hookenv.charm_dir(), "archives", archive_file)
33
34
35def extract(archive_name, destpath=None):
36 handler = get_archive_handler(archive_name)
37 if handler:
38 if not destpath:
39 destpath = archive_dest_default(archive_name)
40 if not os.path.isdir(destpath):
41 host.mkdir(destpath)
42 handler(archive_name, destpath)
43 return destpath
44 else:
45 raise ArchiveError("No handler for archive")
46
47
48def extract_tarfile(archive_name, destpath):
49 "Unpack a tar archive, optionally compressed"
50 archive = tarfile.open(archive_name)
51 archive.extractall(destpath)
52
53
54def extract_zipfile(archive_name, destpath):
55 "Unpack a zip file"
56 archive = zipfile.ZipFile(archive_name)
57 archive.extractall(destpath)
580
=== removed file 'lib/charm-helpers/charmhelpers/payload/execd.py'
--- lib/charm-helpers/charmhelpers/payload/execd.py 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/charmhelpers/payload/execd.py 1970-01-01 00:00:00 +0000
@@ -1,50 +0,0 @@
1#!/usr/bin/env python
2
3import os
4import sys
5import subprocess
6from charmhelpers.core import hookenv
7
8
9def default_execd_dir():
10 return os.path.join(os.environ['CHARM_DIR'], 'exec.d')
11
12
13def execd_module_paths(execd_dir=None):
14 """Generate a list of full paths to modules within execd_dir."""
15 if not execd_dir:
16 execd_dir = default_execd_dir()
17
18 if not os.path.exists(execd_dir):
19 return
20
21 for subpath in os.listdir(execd_dir):
22 module = os.path.join(execd_dir, subpath)
23 if os.path.isdir(module):
24 yield module
25
26
27def execd_submodule_paths(command, execd_dir=None):
28 """Generate a list of full paths to the specified command within exec_dir.
29 """
30 for module_path in execd_module_paths(execd_dir):
31 path = os.path.join(module_path, command)
32 if os.access(path, os.X_OK) and os.path.isfile(path):
33 yield path
34
35
36def execd_run(command, execd_dir=None, die_on_error=False, stderr=None):
37 """Run command for each module within execd_dir which defines it."""
38 for submodule_path in execd_submodule_paths(command, execd_dir):
39 try:
40 subprocess.check_call(submodule_path, shell=True, stderr=stderr)
41 except subprocess.CalledProcessError as e:
42 hookenv.log("Error ({}) running {}. Output: {}".format(
43 e.returncode, e.cmd, e.output))
44 if die_on_error:
45 sys.exit(e.returncode)
46
47
48def execd_preinstall(execd_dir=None):
49 """Run charm-pre-install for each module within execd_dir."""
50 execd_run('charm-pre-install', execd_dir=execd_dir)
510
=== removed directory 'lib/charm-helpers/debian'
=== removed file 'lib/charm-helpers/debian/compat'
--- lib/charm-helpers/debian/compat 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/debian/compat 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
17
20
=== removed file 'lib/charm-helpers/debian/control'
--- lib/charm-helpers/debian/control 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/debian/control 1970-01-01 00:00:00 +0000
@@ -1,20 +0,0 @@
1Source: charmhelpers
2Maintainer: Matthew Wedgwood <matthew.wedgwood@ubuntu.com>
3Section: python
4Priority: optional
5Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7)
6Standards-Version: 3.9.1
7
8Package: python-charmhelpers
9Architecture: all
10Depends: ${misc:Depends}, ${python:Depends}
11Description: UNKNOWN
12 ============
13 CharmHelpers
14 ============
15 .
16 CharmHelpers provides an opinionated set of tools for building Juju
17 charms that work together. In addition to basic tasks like interact-
18 ing with the charm environment and the machine it runs on, it also
19 helps keep you build hooks and establish relations effortlessly.
20 .
210
=== removed file 'lib/charm-helpers/debian/rules'
--- lib/charm-helpers/debian/rules 2013-07-18 06:08:49 +0000
+++ lib/charm-helpers/debian/rules 1970-01-01 00:00:00 +0000
@@ -1,9 +0,0 @@
1#!/usr/bin/make -f
2
3# This file was automatically generated by stdeb 0.6.0+git at
4# 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: