~containers/flannel

Owner: mbruzek
Status: Needs Review
Vote: +0 (+2 needed for approval)

CPP?: No
OIL?: No

This is our attempt at getting the kubernetes charms promulgated. Please review this charm for policy and best practices.

Thanks.


Tests

Substrate Status Results Last Updated
aws RETRY 19 days ago
gce RETRY 19 days ago
lxc RETRY 19 days ago

Add Comment

Login to comment/vote on this review.


Policy Checklist

Description Unreviewed Pass Fail

General

Must verify that any software installed or utilized is verified as coming from the intended source. petevg
  • Any software installed from the Ubuntu or CentOS default archives satisfies this due to the apt and yum sources including cryptographic signing information.
  • Third party repositories must be listed as a configuration option that can be overridden by the user and not hard coded in the charm itself.
  • Launchpad PPAs are acceptable as the add-apt-repository command retrieves the keys securely.
  • Other third party repositories are acceptable if the signing key is embedded in the charm.
Must provide a means to protect users from known security vulnerabilities in a way consistent with best practices as defined by either operating system policies or upstream documentation. petevg
Basically, this means there must be instructions on how to apply updates if you use software not from distribution channels.
Must have hooks that are idempotent. petevg
Should be built using charm layers. petevg
Should use Juju Resources to deliver required payloads. petevg

Testing and Quality

charm proof must pass without errors or warnings.
Must include passing unit, functional, or integration tests. petevg
Tests must exercise all relations.
Tests must exercise config.
set-config, unset-config, and re-set must be tested as a minimum
Must not use anything infrastructure-provider specific (i.e. querying EC2 metadata service). petevg
Must be self contained unless the charm is a proxy for an existing cloud service, e.g. ec2-elb charm.
Must not use symlinks. petevg
Bundles must only use promulgated charms, they cannot reference charms in personal namespaces. petevg
Must call Juju hook tools (relation-*, unit-*, config-*, etc) without a hard coded path. petevg
Should include a tests.yaml for all integration tests. petevg

Metadata

Must include a full description of what the software does. petevg
Must include a maintainer email address for a team or individual who will be responsive to contact. petevg
Must include a license. Call the file 'copyright' and make sure all files' licenses are specified clearly. petevg
Must be under a Free license. petevg
Must have a well documented and valid README.md. petevg
Must describe the service. petevg
Must describe how it interacts with other services, if applicable. petevg
Must document the interfaces. petevg
Must show how to deploy the charm. petevg
Must define external dependencies, if applicable. petevg
Should link to a recommend production usage bundle and recommended configuration if this differs from the default.
Should reference and link to upstream documentation and best practices. petevg

Security

Must not run any network services using default passwords. petevg
Must verify and validate any external payload petevg
  • Known and understood packaging systems that verify packages like apt, pip, and yum are ok.
  • wget | sh style is not ok.
Should make use of whatever Mandatory Access Control system is provided by the distribution. petevg
Should avoid running services as root. petevg

All changes | Changes since last revision

Source Diff

Files changed 74

Inline diff comments 0

No comments yet.

Back to file index

Makefile

 1
--- 
 2
+++ Makefile
 3
@@ -0,0 +1,24 @@
 4
+#!/usr/bin/make
 5
+
 6
+all: lint unit_test
 7
+
 8
+
 9
+.PHONY: clean
10
+clean:
11
+	@rm -rf .tox
12
+
13
+.PHONY: apt_prereqs
14
+apt_prereqs:
15
+	@# Need tox, but don't install the apt version unless we have to (don't want to conflict with pip)
16
+	@which tox >/dev/null || (sudo apt-get install -y python-pip && sudo pip install tox)
17
+
18
+.PHONY: lint
19
+lint: apt_prereqs
20
+	@tox --notest
21
+	@PATH=.tox/py34/bin:.tox/py35/bin flake8 $(wildcard hooks reactive lib unit_tests tests)
22
+	@charm proof
23
+
24
+.PHONY: unit_test
25
+unit_test: apt_prereqs
26
+	@echo Starting tests...
27
+	tox
Back to file index

README.md

 1
--- 
 2
+++ README.md
 3
@@ -0,0 +1,64 @@
 4
+# Flannel Charm
 5
+
 6
+Flannel is a virtual network that gives a subnet to each host for use with
 7
+container runtimes.
 8
+
 9
+This charm will deploy flannel as a background service, and configure CNI for
10
+use with flannel, on any principal charm that implements the
11
+[`kubernetes-cni`](https://github.com/juju-solutions/interface-kubernetes-cni) interface.
12
+
13
+
14
+## Usage
15
+
16
+The flannel charm is a
17
+[subordinate](https://jujucharms.com/docs/stable/authors-subordinate-services).
18
+This charm will require a principal charm that implements the `kubernetes-cni`
19
+interface in order to properly deploy.
20
+
21
+```
22
+juju deploy flannel
23
+juju deploy etcd
24
+juju deploy kubernetes-master
25
+juju deploy kubernetes-worker
26
+juju add-relation flannel etcd
27
+juju add-relation flannel kubernetes-master
28
+juju add-relation flannel kubernetes-worker
29
+```
30
+
31
+## Configuration
32
+
33
+**iface** The interface to configure the flannel SDN binding. If this value is
34
+empty string or undefined the code will attempt to find the default network
35
+adapter similar to the following command:  
36
+```bash
37
+route | grep default | head -n 1 | awk {'print $8'}
38
+```
39
+
40
+**cidr** The network range to configure the flannel SDN to declare when
41
+establishing networking setup with etcd. Ensure this network range is not active
42
+on the vlan you're deploying to, as it will cause collisions and odd behavior
43
+if care is not taken when selecting a good CIDR range to assign to flannel.
44
+
45
+**nagios_context** A string that will be prepended to instance name to set the
46
+host name in nagios.If you're running multiple environments with the same
47
+services in them this allows you to differentiate between them. Used by the
48
+nrpe subordinate charm.
49
+
50
+**nagios_servicegroups** The comma-separated list of servicegroups that the
51
+generated Nagios checks will belong to.
52
+
53
+## Known Limitations
54
+
55
+This subordinate does not support being co-located with other deployments of
56
+the flannel subordinate (to gain 2 vlans on a single application). If you
57
+require this support please file a bug.
58
+
59
+This subordinate also leverages juju-resources, so it is currently only available
60
+on juju 2.0+ controllers.
61
+
62
+
63
+## Further information
64
+
65
+- [Flannel Homepage](https://coreos.com/flannel/docs/latest/flannel-config.html)
66
+- [Flannel Charm Issue Tracker]()
67
+- [Flannel Issue Tracker]()
Back to file index

bin/layer_option

 1
--- 
 2
+++ bin/layer_option
 3
@@ -0,0 +1,24 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+import sys
 7
+sys.path.append('lib')
 8
+
 9
+import argparse
10
+from charms.layer import options
11
+
12
+
13
+parser = argparse.ArgumentParser(description='Access layer options.')
14
+parser.add_argument('section',
15
+                    help='the section, or layer, the option is from')
16
+parser.add_argument('option',
17
+                    help='the option to access')
18
+
19
+args = parser.parse_args()
20
+value = options(args.section).get(args.option, '')
21
+if isinstance(value, bool):
22
+    sys.exit(0 if value else 1)
23
+elif isinstance(value, list):
24
+    for val in value:
25
+        print(val)
26
+else:
27
+    print(value)
Back to file index

config.yaml

 1
--- 
 2
+++ config.yaml
 3
@@ -0,0 +1,29 @@
 4
+"options":
 5
+  "nagios_context":
 6
+    "default": "juju"
 7
+    "type": "string"
 8
+    "description": |
 9
+      Used by the nrpe subordinate charms.
10
+      A string that will be prepended to instance name to set the host name
11
+      in nagios. So for instance the hostname would be something like:
12
+          juju-myservice-0
13
+      If you're running multiple environments with the same services in them
14
+      this allows you to differentiate between them.
15
+  "nagios_servicegroups":
16
+    "default": ""
17
+    "type": "string"
18
+    "description": |
19
+      A comma-separated list of nagios servicegroups.
20
+      If left empty, the nagios_context will be used as the servicegroup
21
+  "iface":
22
+    "type": "string"
23
+    "default": ""
24
+    "description": |
25
+      The interface to bind flannel overlay networking. The default value is
26
+      the result of running the following command:
27
+      `route | grep default | head -n 1 | awk {'print $8'}`.
28
+  "cidr":
29
+    "type": "string"
30
+    "default": "10.1.0.0/16"
31
+    "description": |
32
+      Network CIDR to assign to Flannel
Back to file index

copyright

 1
--- 
 2
+++ copyright
 3
@@ -0,0 +1,13 @@
 4
+Copyright 2016 Canonical LTD
 5
+
 6
+Licensed under the Apache License, Version 2.0 (the "License");
 7
+you may not use this file except in compliance with the License.
 8
+You may obtain a copy of the License at
 9
+
10
+    http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+Unless required by applicable law or agreed to in writing, software
13
+distributed under the License is distributed on an "AS IS" BASIS,
14
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+See the License for the specific language governing permissions and
16
+limitations under the License.
Back to file index

hooks/cni-relation-broken

 1
--- 
 2
+++ hooks/cni-relation-broken
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/cni-relation-changed

 1
--- 
 2
+++ hooks/cni-relation-changed
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/cni-relation-departed

 1
--- 
 2
+++ hooks/cni-relation-departed
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/cni-relation-joined

 1
--- 
 2
+++ hooks/cni-relation-joined
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/config-changed

 1
--- 
 2
+++ hooks/config-changed
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/etcd-relation-broken

 1
--- 
 2
+++ hooks/etcd-relation-broken
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/etcd-relation-changed

 1
--- 
 2
+++ hooks/etcd-relation-changed
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/etcd-relation-departed

 1
--- 
 2
+++ hooks/etcd-relation-departed
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/etcd-relation-joined

 1
--- 
 2
+++ hooks/etcd-relation-joined
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/hook.template

 1
--- 
 2
+++ hooks/hook.template
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/install

 1
--- 
 2
+++ hooks/install
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/leader-elected

 1
--- 
 2
+++ hooks/leader-elected
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/leader-settings-changed

 1
--- 
 2
+++ hooks/leader-settings-changed
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/nrpe-external-master-relation-broken

 1
--- 
 2
+++ hooks/nrpe-external-master-relation-broken
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/nrpe-external-master-relation-changed

 1
--- 
 2
+++ hooks/nrpe-external-master-relation-changed
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/nrpe-external-master-relation-departed

 1
--- 
 2
+++ hooks/nrpe-external-master-relation-departed
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/nrpe-external-master-relation-joined

 1
--- 
 2
+++ hooks/nrpe-external-master-relation-joined
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/relations/etcd/.gitignore

1
--- 
2
+++ hooks/relations/etcd/.gitignore
3
@@ -0,0 +1 @@
4
+.DS_Store
Back to file index

hooks/relations/etcd/README.md

 1
--- 
 2
+++ hooks/relations/etcd/README.md
 3
@@ -0,0 +1,89 @@
 4
+# Overview
 5
+
 6
+This interface layer handles the communication with Etcd via the `etcd`
 7
+interface.
 8
+
 9
+# Usage
10
+
11
+## Requires
12
+
13
+This interface layer will set the following states, as appropriate:
14
+
15
+  * `{relation_name}.connected` The relation is established, but Etcd may not
16
+  yet have provided any connection or service information.
17
+
18
+  * `{relation_name}.available` Etcd has provided its connection string
19
+    information, and is ready to serve as a KV store.
20
+    The provided information can be accessed via the following methods:
21
+      * `etcd.get_connection_string()`
22
+      * `etcd.get_version()`
23
+  * `{relation_name}.tls.available` Etcd has provided the connection string
24
+    information, and the tls client credentials to communicate with it.
25
+    The client credentials can be accessed via:
26
+    * `{relation_name}.get_client_credentials()` returning a dictionary of
27
+       the clinet certificate, key and CA.
28
+    * `{relation_name}.save_client_credentials(key, cert, ca)` is a convenience
29
+      method to save the client certificate, key and CA to files of your
30
+      choosing.
31
+
32
+
33
+For example, a common application for this is configuring an applications
34
+backend key/value storage, like Docker.
35
+
36
+```python
37
+@when('etcd.available', 'docker.available')
38
+def swarm_etcd_cluster_setup(etcd):
39
+    con_string = etcd.connection_string().replace('http', 'etcd')
40
+    opts = {}
41
+    opts['connection_string'] = con_string
42
+    render('docker-compose.yml', 'files/swarm/docker-compose.yml', opts)
43
+
44
+```
45
+
46
+
47
+## Provides
48
+
49
+A charm providing this interface is providing the Etcd rest api service.
50
+
51
+This interface layer will set the following states, as appropriate:
52
+
53
+  * `{relation_name}.connected` One or more clients of any type have
54
+    been related. The charm should call the following methods to provide the
55
+    appropriate information to the clients:
56
+
57
+    * `{relation_name}.set_connection_string(string, version)`
58
+    * `{relation_name}.set_client_credentials(key, cert, ca)`
59
+
60
+Example:
61
+
62
+```python
63
+@when('db.connected')
64
+def send_connection_details(db):
65
+    cert = leader_get('client_certificate')
66
+    key = leader_get('client_key')
67
+    ca = leader_get('certificate_authority')
68
+    # Set the key, cert, and ca on the db relation
69
+    db.set_client_credentials(key, cert, ca)
70
+
71
+    port = hookenv.config().get('port')
72
+    # Get all the peers participating in the cluster relation.
73
+    addresses = cluster.get_peer_addresses()
74
+    connections = []
75
+    for address in addresses:
76
+        connections.append('http://{0}:{1}'.format(address, port))
77
+    # Set the connection string on the db relation.
78
+    db.set_connection_string(','.join(conections))
79
+```
80
+
81
+
82
+# Contact Information
83
+
84
+### Maintainer
85
+- Charles Butler <charles.butler@canonical.com>
86
+
87
+
88
+# Etcd
89
+
90
+- [Etcd](https://coreos.com/etcd/) home page
91
+- [Etcd bug trackers](https://github.com/coreos/etcd/issues)
92
+- [Etcd Juju Charm](http://jujucharms.com/?text=etcd)
Back to file index

hooks/relations/etcd/interface.yaml

1
--- 
2
+++ hooks/relations/etcd/interface.yaml
3
@@ -0,0 +1,4 @@
4
+name: etcd
5
+summary: Interface for relating to ETCD
6
+version: 2
7
+maintainer: "Charles Butler <charles.butler@canonical.com>"
Back to file index

hooks/relations/etcd/peers.py

 1
--- 
 2
+++ hooks/relations/etcd/peers.py
 3
@@ -0,0 +1,64 @@
 4
+#!/usr/bin/python
 5
+# Licensed under the Apache License, Version 2.0 (the "License");
 6
+# you may not use this file except in compliance with the License.
 7
+# You may obtain a copy of the License at
 8
+#
 9
+#     http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+# Unless required by applicable law or agreed to in writing, software
12
+# distributed under the License is distributed on an "AS IS" BASIS,
13
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+# See the License for the specific language governing permissions and
15
+# limitations under the License.
16
+
17
+from charms.reactive import RelationBase
18
+from charms.reactive import hook
19
+from charms.reactive import scopes
20
+
21
+
22
+class EtcdPeer(RelationBase):
23
+    '''This class handles peer relation communication by setting states that
24
+    the reactive code can respond to. '''
25
+
26
+    scope = scopes.UNIT
27
+
28
+    @hook('{peers:etcd}-relation-joined')
29
+    def peer_joined(self):
30
+        '''A new peer has joined, set the state on the unit so we can track
31
+        when they are departed. '''
32
+        conv = self.conversation()
33
+        conv.set_state('{relation_name}.joined')
34
+
35
+    @hook('{peers:etcd}-relation-departed')
36
+    def peers_going_away(self):
37
+        '''Trigger a state on the unit that it is leaving. We can use this
38
+        state in conjunction with the joined state to determine which unit to
39
+        unregister from the etcd cluster. '''
40
+        conv = self.conversation()
41
+        conv.remove_state('{relation_name}.joined')
42
+        conv.set_state('{relation_name}.departing')
43
+
44
+    def dismiss(self):
45
+        '''Remove the departing state from all other units in the conversation,
46
+        and we can resume normal operation.
47
+        '''
48
+        for conv in self.conversations():
49
+            conv.remove_state('{relation_name}.departing')
50
+
51
+    def get_peers(self):
52
+        '''Return a list of names for the peers participating in this
53
+        conversation scope. '''
54
+        peers = []
55
+        # Iterate over all the conversations of this type.
56
+        for conversation in self.conversations():
57
+            peers.append(conversation.scope)
58
+        return peers
59
+
60
+    def get_peer_addresses(self):
61
+        '''Return a list of private addresses for the peers participating in
62
+        this conversation scope. '''
63
+        addresses = []
64
+        # Iterate over all the conversations of this type.
65
+        for conversation in self.conversations():
66
+            addresses.append(conversation.get_remote('private-address'))
67
+        return addresses
Back to file index

hooks/relations/etcd/provides.py

 1
--- 
 2
+++ hooks/relations/etcd/provides.py
 3
@@ -0,0 +1,45 @@
 4
+#!/usr/bin/python
 5
+# Licensed under the Apache License, Version 2.0 (the "License");
 6
+# you may not use this file except in compliance with the License.
 7
+# You may obtain a copy of the License at
 8
+#
 9
+#     http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+# Unless required by applicable law or agreed to in writing, software
12
+# distributed under the License is distributed on an "AS IS" BASIS,
13
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+# See the License for the specific language governing permissions and
15
+# limitations under the License.
16
+
17
+from charms.reactive import RelationBase
18
+from charms.reactive import hook
19
+from charms.reactive import scopes
20
+
21
+
22
+class EtcdProvider(RelationBase):
23
+    scope = scopes.GLOBAL
24
+
25
+    @hook('{provides:etcd}-relation-{joined,changed}')
26
+    def joined_or_changed(self):
27
+        ''' Set the connected state from the provides side of the relation. '''
28
+        self.set_state('{relation_name}.connected')
29
+
30
+    @hook('{provides:etcd}-relation-{broken,departed}')
31
+    def broken_or_departed(self):
32
+        '''Remove connected state from the provides side of the relation. '''
33
+        self.remove_state('{relation_name}.connected')
34
+
35
+    def set_client_credentials(self, key, cert, ca):
36
+        ''' Set the client credentials on the global conversation for this
37
+        relation. '''
38
+        self.set_remote('client_key', key)
39
+        self.set_remote('client_ca', ca)
40
+        self.set_remote('client_cert', cert)
41
+
42
+    def set_connection_string(self, connection_string, version=''):
43
+        ''' Set the connection string on the global conversation for this
44
+        relation. '''
45
+        # Note: Version added as a late-dependency for 2 => 3 migration
46
+        # If no version is specified, consumers should presume etcd 2.x
47
+        self.set_remote('connection_string', connection_string)
48
+        self.set_remote('version', version)
Back to file index

hooks/relations/etcd/requires.py

 1
--- 
 2
+++ hooks/relations/etcd/requires.py
 3
@@ -0,0 +1,80 @@
 4
+#!/usr/bin/python
 5
+# Licensed under the Apache License, Version 2.0 (the "License");
 6
+# you may not use this file except in compliance with the License.
 7
+# You may obtain a copy of the License at
 8
+#
 9
+#     http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+# Unless required by applicable law or agreed to in writing, software
12
+# distributed under the License is distributed on an "AS IS" BASIS,
13
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+# See the License for the specific language governing permissions and
15
+# limitations under the License.
16
+
17
+import os
18
+
19
+from charms.reactive import RelationBase
20
+from charms.reactive import hook
21
+from charms.reactive import scopes
22
+
23
+
24
+class EtcdClient(RelationBase):
25
+    scope = scopes.GLOBAL
26
+
27
+    @hook('{requires:etcd}-relation-{joined,changed}')
28
+    def changed(self):
29
+        ''' Indicate the relation is connected, and if the relation data is
30
+        set it is also available. '''
31
+        self.set_state('{relation_name}.connected')
32
+
33
+        if self.get_connection_string():
34
+            self.set_state('{relation_name}.available')
35
+            # Get the ca, key, cert from the relation data.
36
+            cert = self.get_client_credentials()
37
+            # The tls state depends on the existance of the ca, key and cert.
38
+            if cert['client_cert'] and cert['client_key'] and cert['client_ca']:  # noqa
39
+                self.set_state('{relation_name}.tls.available')
40
+
41
+    @hook('{requires:etcd}-relation-{broken, departed}')
42
+    def broken(self):
43
+        ''' Indicate the relation is no longer available and not connected. '''
44
+        self.remove_state('{relation_name}.available')
45
+        self.remove_state('{relation_name}.connected')
46
+        self.remove_state('{relation_name}.tls.available')
47
+
48
+    def connection_string(self):
49
+        ''' This method is depreciated but ensures backward compatibility
50
+        @see get_connection_string(self). '''
51
+        return self.get_connection_string()
52
+
53
+    def get_connection_string(self):
54
+        ''' Return the connection string, if available, or None. '''
55
+        return self.get_remote('connection_string')
56
+
57
+    def get_version(self):
58
+        ''' Return the version of the etd protocol being used, or None. '''
59
+        return self.get_remote('version')
60
+
61
+    def get_client_credentials(self):
62
+        ''' Return a dict with the client certificate, ca and key to
63
+        communicate with etcd using tls. '''
64
+        return {'client_cert': self.get_remote('client_cert'),
65
+                'client_key': self.get_remote('client_key'),
66
+                'client_ca': self.get_remote('client_ca')}
67
+
68
+    def save_client_credentials(self, key, cert, ca):
69
+        ''' Save all the client certificates for etcd to local files. '''
70
+        self._save_remote_data('client_cert', cert)
71
+        self._save_remote_data('client_key', key)
72
+        self._save_remote_data('client_ca', ca)
73
+
74
+    def _save_remote_data(self, key, path):
75
+        ''' Save the remote data to a file indicated by path creating the
76
+        parent directory if needed.'''
77
+        value = self.get_remote(key)
78
+        if value:
79
+            parent = os.path.dirname(path)
80
+            if not os.path.isdir(parent):
81
+                os.makedirs(parent)
82
+            with open(path, 'w') as stream:
83
+                stream.write(value)
Back to file index

hooks/relations/kubernetes-cni/.gitignore

1
--- 
2
+++ hooks/relations/kubernetes-cni/.gitignore
3
@@ -0,0 +1 @@
4
+.DS_Store
Back to file index

hooks/relations/kubernetes-cni/interface.yaml

1
--- 
2
+++ hooks/relations/kubernetes-cni/interface.yaml
3
@@ -0,0 +1,4 @@
4
+name: kubernetes-cni
5
+summary: Interface for relating various CNI implementations
6
+version: 0
7
+maintainer: "Rye Terrell <rye.terrell@canonical.com>"
Back to file index

hooks/relations/kubernetes-cni/provides.py

 1
--- 
 2
+++ hooks/relations/kubernetes-cni/provides.py
 3
@@ -0,0 +1,43 @@
 4
+#!/usr/bin/python
 5
+
 6
+from charms.reactive import RelationBase
 7
+from charms.reactive import hook
 8
+from charms.reactive import scopes
 9
+
10
+
11
+class CNIPluginProvider(RelationBase):
12
+    scope = scopes.GLOBAL
13
+
14
+    @hook('{provides:kubernetes-cni}-relation-{joined,changed}')
15
+    def joined_or_changed(self):
16
+        ''' Set the connected state from the provides side of the relation. '''
17
+        self.set_state('{relation_name}.connected')
18
+        if self.config_available():
19
+            self.set_state('{relation_name}.available')
20
+
21
+    @hook('{provides:kubernetes-cni}-relation-{departed}')
22
+    def broken_or_departed(self):
23
+        '''Remove connected state from the provides side of the relation. '''
24
+        self.remove_state('{relation_name}.connected')
25
+        self.remove_state('{relation_name}.available')
26
+        self.remove_state('{relation_name}.configured')
27
+
28
+    def set_config(self, is_master, kubeconfig_path):
29
+        ''' Relays a dict of kubernetes configuration information. '''
30
+        self.set_remote(data={
31
+            'is_master': is_master,
32
+            'kubeconfig_path': kubeconfig_path
33
+        })
34
+        self.set_state('{relation_name}.configured')
35
+
36
+    def config_available(self):
37
+        ''' Ensures all config from the CNI plugin is available. '''
38
+        if not self.get_remote('cidr'):
39
+            return False
40
+        return True
41
+
42
+    def get_config(self):
43
+        ''' Returns all config from the CNI plugin. '''
44
+        return {
45
+            'cidr': self.get_remote('cidr'),
46
+        }
Back to file index

hooks/relations/kubernetes-cni/requires.py

 1
--- 
 2
+++ hooks/relations/kubernetes-cni/requires.py
 3
@@ -0,0 +1,40 @@
 4
+#!/usr/bin/python
 5
+
 6
+from charms.reactive import RelationBase
 7
+from charms.reactive import hook
 8
+from charms.reactive import scopes
 9
+
10
+
11
+class CNIPluginClient(RelationBase):
12
+    scope = scopes.GLOBAL
13
+
14
+    @hook('{requires:kubernetes-cni}-relation-{joined,changed}')
15
+    def changed(self):
16
+        ''' Indicate the relation is connected, and if the relation data is
17
+        set it is also available. '''
18
+        self.set_state('{relation_name}.connected')
19
+        config = self.get_config()
20
+        if config['is_master'] == 'True':
21
+            self.set_state('{relation_name}.is-master')
22
+        elif config['is_master'] == 'False':
23
+            self.set_state('{relation_name}.is-worker')
24
+
25
+    @hook('{requires:kubernetes-cni}-relation-{departed}')
26
+    def broken(self):
27
+        ''' Indicate the relation is no longer available and not connected. '''
28
+        self.remove_state('{relation_name}.connected')
29
+        self.remove_state('{relation_name}.is-master')
30
+        self.remove_state('{relation_name}.is-worker')
31
+
32
+    def get_config(self):
33
+        ''' Get the kubernetes configuration information. '''
34
+        return {
35
+            'is_master': self.get_remote('is_master'),
36
+            'kubeconfig_path': self.get_remote('kubeconfig_path')
37
+        }
38
+
39
+    def set_config(self, cidr):
40
+        ''' Sets the CNI configuration information. '''
41
+        self.set_remote(data={
42
+            'cidr': cidr
43
+        })
Back to file index

hooks/relations/nrpe-external-master/README.md

 1
--- 
 2
+++ hooks/relations/nrpe-external-master/README.md
 3
@@ -0,0 +1,65 @@
 4
+# nrpe-external-master interface
 5
+
 6
+Use this interface to register nagios checks in your charm layers.
 7
+
 8
+## Purpose
 9
+
10
+This interface is designed to interoperate with the
11
+[nrpe-external-master](https://jujucharms.com/nrpe-external-master) subordinate charm.
12
+
13
+## How to use in your layers
14
+
15
+The event handler for `nrpe-external-master.available` is called with an object
16
+through which you can register your own custom nagios checks, when a relation
17
+is established with `nrpe-external-master:nrpe-external-master`.
18
+
19
+This object provides a method,
20
+
21
+_add_check_(args, name=_check_name_, description=_description_, context=_context_, unit=_unit_)
22
+
23
+which is called to register a nagios plugin check for your service.
24
+
25
+All arguments are required.
26
+
27
+*args* is a list of nagios plugin command line arguments, starting with the path to the plugin executable.
28
+
29
+*name* is the name of the check registered in nagios
30
+
31
+*description* is some text that describes what the check is for and what it does
32
+
33
+*context* is the nagios context name, something that identifies your application
34
+
35
+*unit* is `hookenv.local_unit()`
36
+
37
+The nrpe subordinate installs `check_http`, so you can use it like this:
38
+
39
+```
40
+@when('nrpe-external-master.available')
41
+def setup_nagios(nagios):
42
+    config = hookenv.config()
43
+    unit_name = hookenv.local_unit()
44
+    nagios.add_check(['/usr/lib/nagios/plugins/check_http',
45
+            '-I', '127.0.0.1', '-p', str(config['port']),
46
+            '-e', " 200 OK", '-u', '/publickey'],
47
+        name="check_http",
48
+        description="Verify my awesome service is responding",
49
+        context=config["nagios_context"],
50
+        unit=unit_name,
51
+    )
52
+```
53
+
54
+Consult the nagios documentation for more information on [how to write your own
55
+plugins](https://assets.nagios.com/downloads/nagioscore/docs/nagioscore/4/en/pluginapi.html)
56
+or [find one](https://www.nagios.org/projects/nagios-plugins/) that does what you need.
57
+
58
+## Example deployment
59
+
60
+```
61
+$ juju deploy your-awesome-charm
62
+$ juju deploy nrpe-external-master --config site-nagios.yaml
63
+$ juju add-relation your-awesome-charm nrpe-external-master
64
+```
65
+
66
+where `site-nagios.yaml` has the necessary configuration settings for the
67
+subordinate to connect to nagios.
68
+
Back to file index

hooks/relations/nrpe-external-master/interface.yaml

1
--- 
2
+++ hooks/relations/nrpe-external-master/interface.yaml
3
@@ -0,0 +1,3 @@
4
+name: nrpe-external-master
5
+summary: Nagios interface
6
+version: 1
Back to file index

hooks/relations/nrpe-external-master/provides.py

 1
--- 
 2
+++ hooks/relations/nrpe-external-master/provides.py
 3
@@ -0,0 +1,62 @@
 4
+import datetime
 5
+
 6
+from charms.reactive import hook
 7
+from charms.reactive import RelationBase
 8
+from charms.reactive import scopes
 9
+
10
+
11
+class NrpeExternalMasterProvides(RelationBase):
12
+    scope = scopes.GLOBAL
13
+
14
+    @hook('{provides:nrpe-external-master}-relation-{joined,changed}')
15
+    def changed_nrpe(self):
16
+        self.set_state('{relation_name}.available')
17
+
18
+    @hook('{provides:nrpe-external-master}-relation-{broken,departed}')
19
+    def broken_nrpe(self):
20
+        self.remove_state('{relation_name}.available')
21
+
22
+    def add_check(self, args, name=None, description=None, context=None,
23
+                  servicegroups=None, unit=None):
24
+        unit = unit.replace('/', '-')
25
+        check_tmpl = """
26
+#---------------------------------------------------
27
+# This file is Juju managed
28
+#---------------------------------------------------
29
+command[%(check_name)s]=%(check_args)s
30
+"""
31
+        service_tmpl = """
32
+#---------------------------------------------------
33
+# This file is Juju managed
34
+#---------------------------------------------------
35
+define service {
36
+    use                             active-service
37
+    host_name                       %(context)s-%(unit_name)s
38
+    service_description             %(description)s
39
+    check_command                   check_nrpe!%(check_name)s
40
+    servicegroups                   %(servicegroups)s
41
+}
42
+"""
43
+        check_filename = "/etc/nagios/nrpe.d/%s.cfg" % (name)
44
+        with open(check_filename, "w") as fh:
45
+            fh.write(check_tmpl % {
46
+                'check_args': ' '.join(args),
47
+                'check_name': name,
48
+            })
49
+        service_filename = "/var/lib/nagios/export/service__%s_%s.cfg" % (
50
+                           unit, name)
51
+        with open(service_filename, "w") as fh:
52
+            fh.write(service_tmpl % {
53
+                'servicegroups': servicegroups or context,
54
+                'context': context,
55
+                'description': description,
56
+                'check_name': name,
57
+                'unit_name': unit,
58
+            })
59
+
60
+    def updated(self):
61
+        relation_info = {
62
+            'timestamp': datetime.datetime.now().isoformat(),
63
+        }
64
+        self.set_remote(**relation_info)
65
+        self.remove_state('{relation_name}.available')
Back to file index

hooks/start

 1
--- 
 2
+++ hooks/start
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/stop

 1
--- 
 2
+++ hooks/stop
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/update-status

 1
--- 
 2
+++ hooks/update-status
 3
@@ -0,0 +1,19 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import sys
 8
+sys.path.append('lib')
 9
+
10
+from charms.layer import basic
11
+basic.bootstrap_charm_deps()
12
+basic.init_config_states()
13
+
14
+
15
+# This will load and run the appropriate @hook and other decorated
16
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
17
+# and $JUJU_CHARM_DIR/hooks/relations.
18
+#
19
+# See https://jujucharms.com/docs/stable/authors-charm-building
20
+# for more information on this pattern.
21
+from charms.reactive import main
22
+main()
Back to file index

hooks/upgrade-charm

 1
--- 
 2
+++ hooks/upgrade-charm
 3
@@ -0,0 +1,28 @@
 4
+#!/usr/bin/env python3
 5
+
 6
+# Load modules from $JUJU_CHARM_DIR/lib
 7
+import os
 8
+import sys
 9
+sys.path.append('lib')
10
+
11
+# This is an upgrade-charm context, make sure we install latest deps
12
+if not os.path.exists('wheelhouse/.upgrade'):
13
+    open('wheelhouse/.upgrade', 'w').close()
14
+    if os.path.exists('wheelhouse/.bootstrapped'):
15
+        os.unlink('wheelhouse/.bootstrapped')
16
+else:
17
+    os.unlink('wheelhouse/.upgrade')
18
+
19
+from charms.layer import basic
20
+basic.bootstrap_charm_deps()
21
+basic.init_config_states()
22
+
23
+
24
+# This will load and run the appropriate @hook and other decorated
25
+# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive,
26
+# and $JUJU_CHARM_DIR/hooks/relations.
27
+#
28
+# See https://jujucharms.com/docs/stable/authors-charm-building
29
+# for more information on this pattern.
30
+from charms.reactive import main
31
+main()
Back to file index

icon.svg

  1
--- 
  2
+++ icon.svg
  3
@@ -0,0 +1,357 @@
  4
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  5
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
  6
+
  7
+<svg
  8
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
  9
+   xmlns:cc="http://creativecommons.org/ns#"
 10
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 11
+   xmlns:svg="http://www.w3.org/2000/svg"
 12
+   xmlns="http://www.w3.org/2000/svg"
 13
+   xmlns:xlink="http://www.w3.org/1999/xlink"
 14
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 15
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 16
+   width="96"
 17
+   height="96"
 18
+   id="svg6517"
 19
+   version="1.1"
 20
+   inkscape:version="0.91 r13725"
 21
+   sodipodi:docname="flannel_circle.svg"
 22
+   viewBox="0 0 96 96">
 23
+  <defs
 24
+     id="defs6519">
 25
+    <linearGradient
 26
+       id="Background">
 27
+      <stop
 28
+         id="stop4178"
 29
+         offset="0"
 30
+         style="stop-color:#22779e;stop-opacity:1" />
 31
+      <stop
 32
+         id="stop4180"
 33
+         offset="1"
 34
+         style="stop-color:#2991c0;stop-opacity:1" />
 35
+    </linearGradient>
 36
+    <filter
 37
+       style="color-interpolation-filters:sRGB"
 38
+       inkscape:label="Inner Shadow"
 39
+       id="filter1121">
 40
+      <feFlood
 41
+         flood-opacity="0.59999999999999998"
 42
+         flood-color="rgb(0,0,0)"
 43
+         result="flood"
 44
+         id="feFlood1123" />
 45
+      <feComposite
 46
+         in="flood"
 47
+         in2="SourceGraphic"
 48
+         operator="out"
 49
+         result="composite1"
 50
+         id="feComposite1125" />
 51
+      <feGaussianBlur
 52
+         in="composite1"
 53
+         stdDeviation="1"
 54
+         result="blur"
 55
+         id="feGaussianBlur1127" />
 56
+      <feOffset
 57
+         dx="0"
 58
+         dy="2"
 59
+         result="offset"
 60
+         id="feOffset1129" />
 61
+      <feComposite
 62
+         in="offset"
 63
+         in2="SourceGraphic"
 64
+         operator="atop"
 65
+         result="composite2"
 66
+         id="feComposite1131" />
 67
+    </filter>
 68
+    <filter
 69
+       style="color-interpolation-filters:sRGB"
 70
+       inkscape:label="Drop Shadow"
 71
+       id="filter950">
 72
+      <feFlood
 73
+         flood-opacity="0.25"
 74
+         flood-color="rgb(0,0,0)"
 75
+         result="flood"
 76
+         id="feFlood952" />
 77
+      <feComposite
 78
+         in="flood"
 79
+         in2="SourceGraphic"
 80
+         operator="in"
 81
+         result="composite1"
 82
+         id="feComposite954" />
 83
+      <feGaussianBlur
 84
+         in="composite1"
 85
+         stdDeviation="1"
 86
+         result="blur"
 87
+         id="feGaussianBlur956" />
 88
+      <feOffset
 89
+         dx="0"
 90
+         dy="1"
 91
+         result="offset"
 92
+         id="feOffset958" />
 93
+      <feComposite
 94
+         in="SourceGraphic"
 95
+         in2="offset"
 96
+         operator="over"
 97
+         result="composite2"
 98
+         id="feComposite960" />
 99
+    </filter>
100
+    <clipPath
101
+       clipPathUnits="userSpaceOnUse"
102
+       id="clipPath873">
103
+      <g
104
+         transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
105
+         id="g875"
106
+         inkscape:label="Layer 1"
107
+         style="display:inline;fill:#ff00ff;fill-opacity:1;stroke:none">
108
+        <path
109
+           style="display:inline;fill:#ff00ff;fill-opacity:1;stroke:none"
110
+           d="M 46.702703,898.22775 H 97.297297 C 138.16216,898.22775 144,904.06497 144,944.92583 v 50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 H 46.702703 C 5.8378378,1042.3622 0,1036.525 0,995.66429 v -50.73846 c 0,-40.86086 5.8378378,-46.69808 46.702703,-46.69808 z"
111
+           id="path877"
112
+           inkscape:connector-curvature="0"
113
+           sodipodi:nodetypes="sssssssss" />
114
+      </g>
115
+    </clipPath>
116
+    <style
117
+       id="style867"
118
+       type="text/css"><![CDATA[
119
+    .fil0 {fill:#1F1A17}
120
+   ]]></style>
121
+    <clipPath
122
+       id="clipPath16">
123
+      <path
124
+         id="path18"
125
+         d="M -9,-9 H 605 V 222 H -9 Z"
126
+         inkscape:connector-curvature="0" />
127
+    </clipPath>
128
+    <clipPath
129
+       id="clipPath116">
130
+      <path
131
+         id="path118"
132
+         d="m 91.7368,146.3253 -9.7039,-1.577 -8.8548,-3.8814 -7.5206,-4.7308 -7.1566,-8.7335 -4.0431,-4.282 -3.9093,-1.4409 -1.034,2.5271 1.8079,2.6096 0.4062,3.6802 1.211,-0.0488 1.3232,-1.2069 -0.3569,3.7488 -1.4667,0.9839 0.0445,1.4286 -3.4744,-1.9655 -3.1462,-3.712 -0.6559,-3.3176 1.3453,-2.6567 1.2549,-4.5133 2.5521,-1.2084 2.6847,0.1318 2.5455,1.4791 -1.698,-8.6122 1.698,-9.5825 -1.8692,-4.4246 -6.1223,-6.5965 1.0885,-3.941 2.9002,-4.5669 5.4688,-3.8486 2.9007,-0.3969 3.225,-0.1094 -2.012,-8.2601 7.3993,-3.0326 9.2188,-1.2129 3.1535,2.0619 0.2427,5.5797 3.5178,5.8224 0.2426,4.6094 8.4909,-0.6066 7.8843,0.7279 -7.8843,-4.7307 1.3343,-5.701 4.9731,-7.763 4.8521,-2.0622 3.8814,1.5769 1.577,3.1538 8.1269,6.1861 1.5769,-1.3343 12.7363,-0.485 2.5473,2.0619 0.2426,3.6391 -0.849,1.5767 -0.6066,9.8251 -4.2454,8.4909 0.7276,3.7605 2.5475,-1.3343 7.1566,-6.6716 3.5175,-0.2424 3.8815,1.5769 3.8818,2.9109 1.9406,6.3077 11.4021,-0.7277 6.914,2.6686 5.5797,5.2157 4.0028,7.5206 0.9706,8.8546 -0.8493,10.3105 -2.1832,9.2185 -2.1836,2.9112 -3.0322,0.9706 -5.3373,-5.8224 -4.8518,-1.6982 -4.2455,7.0353 -4.2454,3.8815 -2.3049,1.4556 -9.2185,7.6419 -7.3993,4.0028 -7.3993,0.6066 -8.6119,-1.4556 -7.5206,-2.7899 -5.2158,-4.2454 -4.1241,-4.9734 -4.2454,-1.2129"
133
+         inkscape:connector-curvature="0" />
134
+    </clipPath>
135
+    <clipPath
136
+       id="clipPath128">
137
+      <path
138
+         id="path130"
139
+         d="m 91.7368,146.3253 -9.7039,-1.577 -8.8548,-3.8814 -7.5206,-4.7308 -7.1566,-8.7335 -4.0431,-4.282 -3.9093,-1.4409 -1.034,2.5271 1.8079,2.6096 0.4062,3.6802 1.211,-0.0488 1.3232,-1.2069 -0.3569,3.7488 -1.4667,0.9839 0.0445,1.4286 -3.4744,-1.9655 -3.1462,-3.712 -0.6559,-3.3176 1.3453,-2.6567 1.2549,-4.5133 2.5521,-1.2084 2.6847,0.1318 2.5455,1.4791 -1.698,-8.6122 1.698,-9.5825 -1.8692,-4.4246 -6.1223,-6.5965 1.0885,-3.941 2.9002,-4.5669 5.4688,-3.8486 2.9007,-0.3969 3.225,-0.1094 -2.012,-8.2601 7.3993,-3.0326 9.2188,-1.2129 3.1535,2.0619 0.2427,5.5797 3.5178,5.8224 0.2426,4.6094 8.4909,-0.6066 7.8843,0.7279 -7.8843,-4.7307 1.3343,-5.701 4.9731,-7.763 4.8521,-2.0622 3.8814,1.5769 1.577,3.1538 8.1269,6.1861 1.5769,-1.3343 12.7363,-0.485 2.5473,2.0619 0.2426,3.6391 -0.849,1.5767 -0.6066,9.8251 -4.2454,8.4909 0.7276,3.7605 2.5475,-1.3343 7.1566,-6.6716 3.5175,-0.2424 3.8815,1.5769 3.8818,2.9109 1.9406,6.3077 11.4021,-0.7277 6.914,2.6686 5.5797,5.2157 4.0028,7.5206 0.9706,8.8546 -0.8493,10.3105 -2.1832,9.2185 -2.1836,2.9112 -3.0322,0.9706 -5.3373,-5.8224 -4.8518,-1.6982 -4.2455,7.0353 -4.2454,3.8815 -2.3049,1.4556 -9.2185,7.6419 -7.3993,4.0028 -7.3993,0.6066 -8.6119,-1.4556 -7.5206,-2.7899 -5.2158,-4.2454 -4.1241,-4.9734 -4.2454,-1.2129"
140
+         inkscape:connector-curvature="0" />
141
+    </clipPath>
142
+    <linearGradient
143
+       id="linearGradient3850"
144
+       inkscape:collect="always">
145
+      <stop
146
+         id="stop3852"
147
+         offset="0"
148
+         style="stop-color:#000000;stop-opacity:1;" />
149
+      <stop
150
+         id="stop3854"
151
+         offset="1"
152
+         style="stop-color:#000000;stop-opacity:0;" />
153
+    </linearGradient>
154
+    <clipPath
155
+       clipPathUnits="userSpaceOnUse"
156
+       id="clipPath3095">
157
+      <path
158
+         d="M 976.648,389.551 H 134.246 V 1229.55 H 976.648 V 389.551"
159
+         id="path3097"
160
+         inkscape:connector-curvature="0" />
161
+    </clipPath>
162
+    <clipPath
163
+       clipPathUnits="userSpaceOnUse"
164
+       id="clipPath3195">
165
+      <path
166
+         d="m 611.836,756.738 -106.34,105.207 c -8.473,8.289 -13.617,20.102 -13.598,33.379 L 598.301,790.207 c -0.031,-13.418 5.094,-25.031 13.535,-33.469"
167
+         id="path3197"
168
+         inkscape:connector-curvature="0" />
169
+    </clipPath>
170
+    <clipPath
171
+       clipPathUnits="userSpaceOnUse"
172
+       id="clipPath3235">
173
+      <path
174
+         d="m 1095.64,1501.81 c 35.46,-35.07 70.89,-70.11 106.35,-105.17 4.4,-4.38 7.11,-10.53 7.11,-17.55 l -106.37,105.21 c 0,7 -2.71,13.11 -7.09,17.51"
175
+         id="path3237"
176
+         inkscape:connector-curvature="0" />
177
+    </clipPath>
178
+    <clipPath
179
+       id="clipPath4591"
180
+       clipPathUnits="userSpaceOnUse">
181
+      <path
182
+         inkscape:connector-curvature="0"
183
+         d="m 1106.6009,730.43734 -0.036,21.648 c -0.01,3.50825 -2.8675,6.61375 -6.4037,6.92525 l -83.6503,7.33162 c -3.5205,0.30763 -6.3812,-2.29987 -6.3671,-5.8145 l 0.036,-21.6475 20.1171,-1.76662 -0.011,4.63775 c 0,1.83937 1.4844,3.19925 3.3262,3.0395 l 49.5274,-4.33975 c 1.8425,-0.166 3.3425,-1.78125 3.3538,-3.626 l 0.01,-4.63025 20.1,-1.7575"
184
+         style="fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none"
185
+         id="path4593" />
186
+    </clipPath>
187
+    <radialGradient
188
+       gradientUnits="userSpaceOnUse"
189
+       gradientTransform="matrix(-1.4333926,-2.2742838,1.1731823,-0.73941125,-174.08025,98.374394)"
190
+       r="20.40658"
191
+       fy="93.399292"
192
+       fx="-26.508606"
193
+       cy="93.399292"
194
+       cx="-26.508606"
195
+       id="radialGradient3856"
196
+       xlink:href="#linearGradient3850"
197
+       inkscape:collect="always" />
198
+    <linearGradient
199
+       gradientTransform="translate(-318.48033,212.32022)"
200
+       gradientUnits="userSpaceOnUse"
201
+       y2="993.19702"
202
+       x2="-51.879555"
203
+       y1="593.11615"
204
+       x1="348.20132"
205
+       id="linearGradient3895"
206
+       xlink:href="#linearGradient3850"
207
+       inkscape:collect="always" />
208
+    <clipPath
209
+       id="clipPath3906"
210
+       clipPathUnits="userSpaceOnUse">
211
+      <rect
212
+         transform="scale(1,-1)"
213
+         style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.8;fill:#ff00ff;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
214
+         id="rect3908"
215
+         width="1019.1371"
216
+         height="1019.1371"
217
+         x="357.9816"
218
+         y="-1725.8152" />
219
+    </clipPath>
220
+  </defs>
221
+  <sodipodi:namedview
222
+     id="base"
223
+     pagecolor="#ffffff"
224
+     bordercolor="#666666"
225
+     borderopacity="1.0"
226
+     inkscape:pageopacity="0.0"
227
+     inkscape:pageshadow="2"
228
+     inkscape:zoom="4.074536"
229
+     inkscape:cx="-140.43595"
230
+     inkscape:cy="32.11876"
231
+     inkscape:document-units="px"
232
+     inkscape:current-layer="layer1"
233
+     showgrid="true"
234
+     fit-margin-top="0"
235
+     fit-margin-left="0"
236
+     fit-margin-right="0"
237
+     fit-margin-bottom="0"
238
+     inkscape:window-width="1920"
239
+     inkscape:window-height="1029"
240
+     inkscape:window-x="0"
241
+     inkscape:window-y="24"
242
+     inkscape:window-maximized="1"
243
+     showborder="true"
244
+     showguides="false"
245
+     inkscape:guide-bbox="true"
246
+     inkscape:showpageshadow="false"
247
+     inkscape:snap-global="false"
248
+     inkscape:snap-bbox="true"
249
+     inkscape:bbox-paths="true"
250
+     inkscape:bbox-nodes="true"
251
+     inkscape:snap-bbox-edge-midpoints="true"
252
+     inkscape:snap-bbox-midpoints="true"
253
+     inkscape:object-paths="true"
254
+     inkscape:snap-intersection-paths="true"
255
+     inkscape:object-nodes="true"
256
+     inkscape:snap-smooth-nodes="true"
257
+     inkscape:snap-midpoints="true"
258
+     inkscape:snap-object-midpoints="true"
259
+     inkscape:snap-center="true"
260
+     inkscape:snap-nodes="true"
261
+     inkscape:snap-others="true"
262
+     inkscape:snap-page="true">
263
+    <inkscape:grid
264
+       type="xygrid"
265
+       id="grid821" />
266
+    <sodipodi:guide
267
+       orientation="1,0"
268
+       position="16,48"
269
+       id="guide823"
270
+       inkscape:locked="false" />
271
+    <sodipodi:guide
272
+       orientation="0,1"
273
+       position="64,80"
274
+       id="guide825"
275
+       inkscape:locked="false" />
276
+    <sodipodi:guide
277
+       orientation="1,0"
278
+       position="80,40"
279
+       id="guide827"
280
+       inkscape:locked="false" />
281
+    <sodipodi:guide
282
+       orientation="0,1"
283
+       position="64,16"
284
+       id="guide829"
285
+       inkscape:locked="false" />
286
+  </sodipodi:namedview>
287
+  <metadata
288
+     id="metadata6522">
289
+    <rdf:RDF>
290
+      <cc:Work
291
+         rdf:about="">
292
+        <dc:format>image/svg+xml</dc:format>
293
+        <dc:type
294
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
295
+        <dc:title />
296
+      </cc:Work>
297
+    </rdf:RDF>
298
+  </metadata>
299
+  <g
300
+     inkscape:label="BACKGROUND"
301
+     inkscape:groupmode="layer"
302
+     id="layer1"
303
+     transform="translate(268,-635.29076)"
304
+     style="display:inline">
305
+    <path
306
+       style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none"
307
+       d="M 48 0 A 48 48 0 0 0 0 48 A 48 48 0 0 0 48 96 A 48 48 0 0 0 96 48 A 48 48 0 0 0 48 0 z "
308
+       id="path6455"
309
+       transform="translate(-268,635.29076)" />
310
+    <path
311
+       inkscape:connector-curvature="0"
312
+       style="display:inline;fill:#53a2da;fill-opacity:1;stroke:none"
313
+       d="m -220,635.29076 a 48,48 0 0 0 -48,48 48,48 0 0 0 48,48 48,48 0 0 0 48,-48 48,48 0 0 0 -48,-48 z"
314
+       id="path6455-3" />
315
+    <path
316
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:19.79999924;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
317
+       d="m -210.73109,647.92331 c -12.45421,0 -22.59379,10.13959 -22.59379,22.59379 l 0,7.0975 0,3.7238 a 0.6425535,0.6425535 0 0 0 0.6425,0.64249 l 6.36089,0 a 0.6425535,0.6425535 0 0 0 0.64248,-0.64249 l 0,-3.7238 0,-7.0975 c 0,-8.35215 6.59577,-14.94791 14.94792,-14.94791 l 4.27719,0 a 0.6425535,0.6425535 0 0 0 0.6425,-0.64249 l 0,-6.36089 a 0.6425535,0.6425535 0 0 0 -0.6425,-0.6425 l -4.27719,0 z m 0.34885,10.87777 c -6.4289,0 -11.73385,5.15219 -11.98518,11.52529 a 0.6425535,0.6425535 0 0 0 0.64186,0.66757 l 6.37094,0 a 0.6425535,0.6425535 0 0 0 0.63997,-0.58915 c 0.18997,-2.28704 1.97407,-3.95846 4.33241,-3.95846 l 3.92834,0 a 0.6425535,0.6425535 0 0 0 0.6425,-0.64249 l 0,-6.36027 a 0.6425535,0.6425535 0 0 0 -0.6425,-0.64249 l -3.92834,0 z m -29.65426,13.29841 a 0.6425535,0.6425535 0 0 0 -0.63309,0.53142 l -1.12436,6.4249 a 0.6425535,0.6425535 0 0 0 0.63309,0.75354 l 6.09674,0 a 0.6425535,0.6425535 0 0 0 0.64249,-0.64249 l 0,-6.42488 a 0.6425535,0.6425535 0 0 0 -0.64249,-0.64249 l -4.97238,0 z m 16.17454,0 a 0.6425535,0.6425535 0 0 0 -0.6425,0.64249 l 0,6.42488 a 0.6425535,0.6425535 0 0 0 0.6425,0.64249 l 17.82593,0 a 0.6425535,0.6425535 0 0 0 0.63309,-0.53144 l 1.12434,-6.42488 a 0.6425535,0.6425535 0 0 0 -0.63307,-0.75354 l -18.95029,0 z m 2.1251,8.81164 a 0.6425535,0.6425535 0 0 0 -0.64248,0.64248 l 0,24.03939 c 0,4.13118 -1.61958,7.82464 -4.26277,10.51198 a 0.6425535,0.6425535 0 0 0 0.004,0.90475 l 4.4604,4.46041 a 0.6425535,0.6425535 0 0 0 0.91103,-0.003 c 4.03527,-4.08214 6.53344,-9.69461 6.53344,-15.87463 l 0,-24.0394 a 0.6425535,0.6425535 0 0 0 -0.6425,-0.64248 l -6.36089,0 z m -20.22711,2.14266 a 0.6425535,0.6425535 0 0 0 -0.63309,0.53144 l -1.12434,6.4249 a 0.6425535,0.6425535 0 0 0 0.63307,0.75355 l 18.94653,0 a 0.6425535,0.6425535 0 0 0 0.64249,-0.6425 l 0,-6.42489 a 0.6425535,0.6425535 0 0 0 -0.64249,-0.6425 l -17.82217,0 z m 29.02432,0 a 0.6425535,0.6425535 0 0 0 -0.64248,0.6425 l 0,6.42489 a 0.6425535,0.6425535 0 0 0 0.64248,0.6425 l 4.97615,0 a 0.6425535,0.6425535 0 0 0 0.63309,-0.53144 l 1.12436,-6.4249 a 0.6425535,0.6425535 0 0 0 -0.63309,-0.75355 l -6.10051,0 z m -19.74273,8.84365 a 0.6425535,0.6425535 0 0 0 -0.6425,0.64249 l 0,12.77198 c 0,1.18698 -0.43422,2.21835 -1.14883,2.9872 a 0.6425535,0.6425535 0 0 0 0.0163,0.89157 l 4.46355,4.46355 a 0.6425535,0.6425535 0 0 0 0.91542,-0.007 c 2.09711,-2.162 3.39942,-5.10779 3.39942,-8.33542 l 0,-12.77198 a 0.6425535,0.6425535 0 0 0 -0.64249,-0.64249 l -6.36089,0 z"
318
+       id="rect4219"
319
+       inkscape:connector-curvature="0" />
320
+  </g>
321
+  <g
322
+     inkscape:groupmode="layer"
323
+     id="layer3"
324
+     inkscape:label="PLACE YOUR PICTOGRAM HERE"
325
+     style="display:inline">
326
+    <g
327
+       id="g4185" />
328
+  </g>
329
+  <style
330
+     id="style4217"
331
+     type="text/css">
332
+	.st0{fill:#419EDA;}
333
+</style>
334
+  <style
335
+     id="style4285"
336
+     type="text/css">
337
+	.st0{clip-path:url(#SVGID_2_);fill:#EFBF1B;}
338
+	.st1{clip-path:url(#SVGID_2_);fill:#40BEB0;}
339
+	.st2{clip-path:url(#SVGID_2_);fill:#0AA5DE;}
340
+	.st3{clip-path:url(#SVGID_2_);fill:#231F20;}
341
+	.st4{fill:#D7A229;}
342
+	.st5{fill:#009B8F;}
343
+</style>
344
+  <style
345
+     id="style4240"
346
+     type="text/css">
347
+	.st0{fill:#E8478B;}
348
+	.st1{fill:#40BEB0;}
349
+	.st2{fill:#37A595;}
350
+	.st3{fill:#231F20;}
351
+</style>
352
+  <style
353
+     id="style4812"
354
+     type="text/css">
355
+	.st0{fill:#0AA5DE;}
356
+	.st1{fill:#40BEB0;}
357
+	.st2{opacity:0.26;fill:#353535;}
358
+	.st3{fill:#231F20;}
359
+</style>
360
+</svg>
Back to file index

layer.yaml

 1
--- 
 2
+++ layer.yaml
 3
@@ -0,0 +1,15 @@
 4
+"options":
 5
+  "basic":
 6
+    "use_venv": !!bool "false"
 7
+    "packages": []
 8
+    "include_system_packages": !!bool "false"
 9
+  "nagios": {}
10
+  "flannel": {}
11
+"repo": "https://github.com/juju-solutions/charm-flannel.git"
12
+"includes":
13
+- "layer:basic"
14
+- "interface:nrpe-external-master"
15
+- "interface:etcd"
16
+- "interface:kubernetes-cni"
17
+- "layer:nagios"
18
+"is": "flannel"
Back to file index

lib/charms/layer/__init__.py

 1
--- 
 2
+++ lib/charms/layer/__init__.py
 3
@@ -0,0 +1,21 @@
 4
+import os
 5
+
 6
+
 7
+class LayerOptions(dict):
 8
+    def __init__(self, layer_file, section=None):
 9
+        import yaml  # defer, might not be available until bootstrap
10
+        with open(layer_file) as f:
11
+            layer = yaml.safe_load(f.read())
12
+        opts = layer.get('options', {})
13
+        if section and section in opts:
14
+            super(LayerOptions, self).__init__(opts.get(section))
15
+        else:
16
+            super(LayerOptions, self).__init__(opts)
17
+
18
+
19
+def options(section=None, layer_file=None):
20
+    if not layer_file:
21
+        base_dir = os.environ.get('JUJU_CHARM_DIR', os.getcwd())
22
+        layer_file = os.path.join(base_dir, 'layer.yaml')
23
+
24
+    return LayerOptions(layer_file, section)
Back to file index

lib/charms/layer/basic.py

  1
--- 
  2
+++ lib/charms/layer/basic.py
  3
@@ -0,0 +1,205 @@
  4
+import os
  5
+import sys
  6
+import shutil
  7
+from glob import glob
  8
+from subprocess import check_call, CalledProcessError
  9
+from time import sleep
 10
+
 11
+from charms.layer.execd import execd_preinstall
 12
+
 13
+
 14
+def lsb_release():
 15
+    """Return /etc/lsb-release in a dict"""
 16
+    d = {}
 17
+    with open('/etc/lsb-release', 'r') as lsb:
 18
+        for l in lsb:
 19
+            k, v = l.split('=')
 20
+            d[k.strip()] = v.strip()
 21
+    return d
 22
+
 23
+
 24
+def bootstrap_charm_deps():
 25
+    """
 26
+    Set up the base charm dependencies so that the reactive system can run.
 27
+    """
 28
+    # execd must happen first, before any attempt to install packages or
 29
+    # access the network, because sites use this hook to do bespoke
 30
+    # configuration and install secrets so the rest of this bootstrap
 31
+    # and the charm itself can actually succeed. This call does nothing
 32
+    # unless the operator has created and populated $JUJU_CHARM_DIR/exec.d.
 33
+    execd_preinstall()
 34
+    # ensure that $JUJU_CHARM_DIR/bin is on the path, for helper scripts
 35
+    charm_dir = os.environ['JUJU_CHARM_DIR']
 36
+    os.environ['PATH'] += ':%s' % os.path.join(charm_dir, 'bin')
 37
+    venv = os.path.abspath('../.venv')
 38
+    vbin = os.path.join(venv, 'bin')
 39
+    vpip = os.path.join(vbin, 'pip')
 40
+    vpy = os.path.join(vbin, 'python')
 41
+    if os.path.exists('wheelhouse/.bootstrapped'):
 42
+        activate_venv()
 43
+        return
 44
+    # bootstrap wheelhouse
 45
+    if os.path.exists('wheelhouse'):
 46
+        with open('/root/.pydistutils.cfg', 'w') as fp:
 47
+            # make sure that easy_install also only uses the wheelhouse
 48
+            # (see https://github.com/pypa/pip/issues/410)
 49
+            fp.writelines([
 50
+                "[easy_install]\n",
 51
+                "allow_hosts = ''\n",
 52
+                "find_links = file://{}/wheelhouse/\n".format(charm_dir),
 53
+            ])
 54
+        apt_install([
 55
+            'python3-pip',
 56
+            'python3-setuptools',
 57
+            'python3-yaml',
 58
+            'python3-dev',
 59
+        ])
 60
+        from charms import layer
 61
+        cfg = layer.options('basic')
 62
+        # include packages defined in layer.yaml
 63
+        apt_install(cfg.get('packages', []))
 64
+        # if we're using a venv, set it up
 65
+        if cfg.get('use_venv'):
 66
+            if not os.path.exists(venv):
 67
+                series = lsb_release()['DISTRIB_CODENAME']
 68
+                if series in ('precise', 'trusty'):
 69
+                    apt_install(['python-virtualenv'])
 70
+                else:
 71
+                    apt_install(['virtualenv'])
 72
+                cmd = ['virtualenv', '-ppython3', '--never-download', venv]
 73
+                if cfg.get('include_system_packages'):
 74
+                    cmd.append('--system-site-packages')
 75
+                check_call(cmd)
 76
+            os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
 77
+            pip = vpip
 78
+        else:
 79
+            pip = 'pip3'
 80
+            # save a copy of system pip to prevent `pip3 install -U pip`
 81
+            # from changing it
 82
+            if os.path.exists('/usr/bin/pip'):
 83
+                shutil.copy2('/usr/bin/pip', '/usr/bin/pip.save')
 84
+        # need newer pip, to fix spurious Double Requirement error:
 85
+        # https://github.com/pypa/pip/issues/56
 86
+        check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
 87
+                    'pip'])
 88
+        # install the rest of the wheelhouse deps
 89
+        check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse'] +
 90
+                   glob('wheelhouse/*'))
 91
+        if not cfg.get('use_venv'):
 92
+            # restore system pip to prevent `pip3 install -U pip`
 93
+            # from changing it
 94
+            if os.path.exists('/usr/bin/pip.save'):
 95
+                shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip')
 96
+                os.remove('/usr/bin/pip.save')
 97
+        os.remove('/root/.pydistutils.cfg')
 98
+        # flag us as having already bootstrapped so we don't do it again
 99
+        open('wheelhouse/.bootstrapped', 'w').close()
100
+        # Ensure that the newly bootstrapped libs are available.
101
+        # Note: this only seems to be an issue with namespace packages.
102
+        # Non-namespace-package libs (e.g., charmhelpers) are available
103
+        # without having to reload the interpreter. :/
104
+        reload_interpreter(vpy if cfg.get('use_venv') else sys.argv[0])
105
+
106
+
107
+def activate_venv():
108
+    """
109
+    Activate the venv if enabled in ``layer.yaml``.
110
+
111
+    This is handled automatically for normal hooks, but actions might
112
+    need to invoke this manually, using something like:
113
+
114
+        # Load modules from $JUJU_CHARM_DIR/lib
115
+        import sys
116
+        sys.path.append('lib')
117
+
118
+        from charms.layer.basic import activate_venv
119
+        activate_venv()
120
+
121
+    This will ensure that modules installed in the charm's
122
+    virtual environment are available to the action.
123
+    """
124
+    venv = os.path.abspath('../.venv')
125
+    vbin = os.path.join(venv, 'bin')
126
+    vpy = os.path.join(vbin, 'python')
127
+    from charms import layer
128
+    cfg = layer.options('basic')
129
+    if cfg.get('use_venv') and '.venv' not in sys.executable:
130
+        # activate the venv
131
+        os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
132
+        reload_interpreter(vpy)
133
+
134
+
135
+def reload_interpreter(python):
136
+    """
137
+    Reload the python interpreter to ensure that all deps are available.
138
+
139
+    Newly installed modules in namespace packages sometimes seemt to
140
+    not be picked up by Python 3.
141
+    """
142
+    os.execve(python, [python] + list(sys.argv), os.environ)
143
+
144
+
145
+def apt_install(packages):
146
+    """
147
+    Install apt packages.
148
+
149
+    This ensures a consistent set of options that are often missed but
150
+    should really be set.
151
+    """
152
+    if isinstance(packages, (str, bytes)):
153
+        packages = [packages]
154
+
155
+    env = os.environ.copy()
156
+
157
+    if 'DEBIAN_FRONTEND' not in env:
158
+        env['DEBIAN_FRONTEND'] = 'noninteractive'
159
+
160
+    cmd = ['apt-get',
161
+           '--option=Dpkg::Options::=--force-confold',
162
+           '--assume-yes',
163
+           'install']
164
+    for attempt in range(3):
165
+        try:
166
+            check_call(cmd + packages, env=env)
167
+        except CalledProcessError:
168
+            if attempt == 2:  # third attempt
169
+                raise
170
+            sleep(5)
171
+        else:
172
+            break
173
+
174
+
175
+def init_config_states():
176
+    import yaml
177
+    from charmhelpers.core import hookenv
178
+    from charms.reactive import set_state
179
+    from charms.reactive import toggle_state
180
+    config = hookenv.config()
181
+    config_defaults = {}
182
+    config_defs = {}
183
+    config_yaml = os.path.join(hookenv.charm_dir(), 'config.yaml')
184
+    if os.path.exists(config_yaml):
185
+        with open(config_yaml) as fp:
186
+            config_defs = yaml.safe_load(fp).get('options', {})
187
+            config_defaults = {key: value.get('default')
188
+                               for key, value in config_defs.items()}
189
+    for opt in config_defs.keys():
190
+        if config.changed(opt):
191
+            set_state('config.changed')
192
+            set_state('config.changed.{}'.format(opt))
193
+        toggle_state('config.set.{}'.format(opt), config.get(opt))
194
+        toggle_state('config.default.{}'.format(opt),
195
+                     config.get(opt) == config_defaults[opt])
196
+    hookenv.atexit(clear_config_states)
197
+
198
+
199
+def clear_config_states():
200
+    from charmhelpers.core import hookenv, unitdata
201
+    from charms.reactive import remove_state
202
+    config = hookenv.config()
203
+    remove_state('config.changed')
204
+    for opt in config.keys():
205
+        remove_state('config.changed.{}'.format(opt))
206
+        remove_state('config.set.{}'.format(opt))
207
+        remove_state('config.default.{}'.format(opt))
208
+    unitdata.kv().flush()
Back to file index

lib/charms/layer/execd.py

  1
--- 
  2
+++ lib/charms/layer/execd.py
  3
@@ -0,0 +1,138 @@
  4
+# Copyright 2014-2016 Canonical Limited.
  5
+#
  6
+# This file is part of layer-basic, the reactive base layer for Juju.
  7
+#
  8
+# charm-helpers is free software: you can redistribute it and/or modify
  9
+# it under the terms of the GNU Lesser General Public License version 3 as
 10
+# published by the Free Software Foundation.
 11
+#
 12
+# charm-helpers is distributed in the hope that it will be useful,
 13
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
 14
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15
+# GNU Lesser General Public License for more details.
 16
+#
 17
+# You should have received a copy of the GNU Lesser General Public License
 18
+# along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>.
 19
+
 20
+# This module may only import from the Python standard library.
 21
+import os
 22
+import sys
 23
+import subprocess
 24
+import time
 25
+
 26
+'''
 27
+execd/preinstall
 28
+
 29
+It is often necessary to configure and reconfigure machines
 30
+after provisioning, but before attempting to run the charm.
 31
+Common examples are specialized network configuration, enabling
 32
+of custom hardware, non-standard disk partitioning and filesystems,
 33
+adding secrets and keys required for using a secured network.
 34
+
 35
+The reactive framework's base layer invokes this mechanism as
 36
+early as possible, before any network access is made or dependencies
 37
+unpacked or non-standard modules imported (including the charms.reactive
 38
+framework itself).
 39
+
 40
+Operators needing to use this functionality may branch a charm and
 41
+create an exec.d directory in it. The exec.d directory in turn contains
 42
+one or more subdirectories, each of which contains an executable called
 43
+charm-pre-install and any other required resources. The charm-pre-install
 44
+executables are run, and if successful, state saved so they will not be
 45
+run again.
 46
+
 47
+    $JUJU_CHARM_DIR/exec.d/mynamespace/charm-pre-install
 48
+
 49
+An alternative to branching a charm is to compose a new charm that contains
 50
+the exec.d directory, using the original charm as a layer,
 51
+
 52
+A charm author could also abuse this mechanism to modify the charm
 53
+environment in unusual ways, but for most purposes it is saner to use
 54
+charmhelpers.core.hookenv.atstart().
 55
+'''
 56
+
 57
+
 58
+def default_execd_dir():
 59
+    return os.path.join(os.environ['JUJU_CHARM_DIR'], 'exec.d')
 60
+
 61
+
 62
+def execd_module_paths(execd_dir=None):
 63
+    """Generate a list of full paths to modules within execd_dir."""
 64
+    if not execd_dir:
 65
+        execd_dir = default_execd_dir()
 66
+
 67
+    if not os.path.exists(execd_dir):
 68
+        return
 69
+
 70
+    for subpath in os.listdir(execd_dir):
 71
+        module = os.path.join(execd_dir, subpath)
 72
+        if os.path.isdir(module):
 73
+            yield module
 74
+
 75
+
 76
+def execd_submodule_paths(command, execd_dir=None):
 77
+    """Generate a list of full paths to the specified command within exec_dir.
 78
+    """
 79
+    for module_path in execd_module_paths(execd_dir):
 80
+        path = os.path.join(module_path, command)
 81
+        if os.access(path, os.X_OK) and os.path.isfile(path):
 82
+            yield path
 83
+
 84
+
 85
+def execd_sentinel_path(submodule_path):
 86
+    module_path = os.path.dirname(submodule_path)
 87
+    execd_path = os.path.dirname(module_path)
 88
+    module_name = os.path.basename(module_path)
 89
+    submodule_name = os.path.basename(submodule_path)
 90
+    return os.path.join(execd_path,
 91
+                        '.{}_{}.done'.format(module_name, submodule_name))
 92
+
 93
+
 94
+def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None):
 95
+    """Run command for each module within execd_dir which defines it."""
 96
+    if stderr is None:
 97
+        stderr = sys.stdout
 98
+    for submodule_path in execd_submodule_paths(command, execd_dir):
 99
+        # Only run each execd once. We cannot simply run them in the
100
+        # install hook, as potentially storage hooks are run before that.
101
+        # We cannot rely on them being idempotent.
102
+        sentinel = execd_sentinel_path(submodule_path)
103
+        if os.path.exists(sentinel):
104
+            continue
105
+
106
+        try:
107
+            subprocess.check_call([submodule_path], stderr=stderr,
108
+                                  universal_newlines=True)
109
+            with open(sentinel, 'w') as f:
110
+                f.write('{} ran successfully {}\n'.format(submodule_path,
111
+                                                          time.ctime()))
112
+                f.write('Removing this file will cause it to be run again\n')
113
+        except subprocess.CalledProcessError as e:
114
+            # Logs get the details. We can't use juju-log, as the
115
+            # output may be substantial and exceed command line
116
+            # length limits.
117
+            print("ERROR ({}) running {}".format(e.returncode, e.cmd),
118
+                  file=stderr)
119
+            print("STDOUT<<EOM", file=stderr)
120
+            print(e.output, file=stderr)
121
+            print("EOM", file=stderr)
122
+
123
+            # Unit workload status gets a shorter fail message.
124
+            short_path = os.path.relpath(submodule_path)
125
+            block_msg = "Error ({}) running {}".format(e.returncode,
126
+                                                       short_path)
127
+            try:
128
+                subprocess.check_call(['status-set', 'blocked', block_msg],
129
+                                      universal_newlines=True)
130
+                if stop_on_error:
131
+                    sys.exit(0)  # Leave unit in blocked state.
132
+            except Exception:
133
+                pass  # We care about the exec.d/* failure, not status-set.
134
+
135
+            if stop_on_error:
136
+                sys.exit(e.returncode or 1)  # Error state for pre-1.24 Juju
137
+
138
+
139
+def execd_preinstall(execd_dir=None):
140
+    """Run charm-pre-install for each module within execd_dir."""
141
+    execd_run('charm-pre-install', execd_dir=execd_dir)
Back to file index

metadata.yaml

 1
--- 
 2
+++ metadata.yaml
 3
@@ -0,0 +1,27 @@
 4
+"name": "flannel"
 5
+"summary": "A charm that provides a robust Software Defined Network"
 6
+"maintainers":
 7
+- "Charles Butler <charles.butler@canonical.com>"
 8
+"description": |
 9
+  it is a generic overlay network that can be used as a simple alternative
10
+  to existing software defined networking solutions
11
+"tags":
12
+- "networking"
13
+"series":
14
+- "xenial"
15
+"requires":
16
+  "etcd":
17
+    "interface": "etcd"
18
+  "cni":
19
+    "interface": "kubernetes-cni"
20
+    "scope": "container"
21
+"provides":
22
+  "nrpe-external-master":
23
+    "interface": "nrpe-external-master"
24
+    "scope": "container"
25
+"resources":
26
+  "flannel":
27
+    "type": "file"
28
+    "filename": "flannel.tar.gz"
29
+    "description": "A tarball packaged release of flannel"
30
+"subordinate": !!bool "true"
Back to file index

reactive/flannel.py

  1
--- 
  2
+++ reactive/flannel.py
  3
@@ -0,0 +1,262 @@
  4
+import os
  5
+import json
  6
+from shlex import split
  7
+from subprocess import check_output, check_call, CalledProcessError, STDOUT
  8
+
  9
+from charms.reactive import set_state, remove_state, when, when_not, hook
 10
+from charms.reactive import when_any
 11
+from charms.templating.jinja2 import render
 12
+from charmhelpers.core.host import service_start, service_stop, service_restart
 13
+from charmhelpers.core.host import service_running, service
 14
+from charmhelpers.core.hookenv import log, status_set, resource_get
 15
+from charmhelpers.core.hookenv import config, application_version_set
 16
+from charmhelpers.contrib.charmsupport import nrpe
 17
+
 18
+
 19
+ETCD_PATH = '/etc/ssl/flannel'
 20
+ETCD_KEY_PATH = os.path.join(ETCD_PATH, 'client-key.pem')
 21
+ETCD_CERT_PATH = os.path.join(ETCD_PATH, 'client-cert.pem')
 22
+ETCD_CA_PATH = os.path.join(ETCD_PATH, 'client-ca.pem')
 23
+
 24
+
 25
+@when_not('flannel.binaries.installed')
 26
+def install_flannel_binaries():
 27
+    ''' Unpack the Flannel binaries. '''
 28
+    try:
 29
+        archive = resource_get('flannel')
 30
+    except Exception:
 31
+        message = 'Error fetching the flannel resource.'
 32
+        log(message)
 33
+        status_set('blocked', message)
 34
+        return
 35
+    if not archive:
 36
+        message = 'Missing flannel resource.'
 37
+        log(message)
 38
+        status_set('blocked', message)
 39
+        return
 40
+    filesize = os.stat(archive).st_size
 41
+    if filesize < 1000000:
 42
+        message = 'Incomplete flannel resource'
 43
+        log(message)
 44
+        status_set('blocked', message)
 45
+        return
 46
+    status_set('maintenance', 'Unpacking flannel resource.')
 47
+    charm_dir = os.getenv('CHARM_DIR')
 48
+    unpack_path = os.path.join(charm_dir, 'files', 'flannel')
 49
+    os.makedirs(unpack_path, exist_ok=True)
 50
+    cmd = ['tar', 'xfz', archive, '-C', unpack_path]
 51
+    log(cmd)
 52
+    check_call(cmd)
 53
+    apps = [
 54
+        {'name': 'flanneld', 'path': '/usr/local/bin'},
 55
+        {'name': 'etcdctl', 'path': '/usr/local/bin'},
 56
+        {'name': 'flannel', 'path': '/opt/cni/bin'},
 57
+        {'name': 'bridge', 'path': '/opt/cni/bin'},
 58
+        {'name': 'host-local', 'path': '/opt/cni/bin'}
 59
+    ]
 60
+    for app in apps:
 61
+        unpacked = os.path.join(unpack_path, app['name'])
 62
+        app_path = os.path.join(app['path'], app['name'])
 63
+        install = ['install', '-v', '-D', unpacked, app_path]
 64
+        check_call(install)
 65
+    set_state('flannel.binaries.installed')
 66
+
 67
+
 68
+@when('cni.is-worker')
 69
+@when_not('flannel.cni.configured')
 70
+def configure_cni(cni):
 71
+    ''' Set up the flannel cni configuration file. '''
 72
+    render('10-flannel.conf', '/etc/cni/net.d/10-flannel.conf', {})
 73
+    set_state('flannel.cni.configured')
 74
+
 75
+
 76
+@when('etcd.tls.available')
 77
+@when_not('flannel.etcd.credentials.installed')
 78
+def install_etcd_credentials(etcd):
 79
+    ''' Install the etcd credential files. '''
 80
+    etcd.save_client_credentials(ETCD_KEY_PATH, ETCD_CERT_PATH, ETCD_CA_PATH)
 81
+    set_state('flannel.etcd.credentials.installed')
 82
+
 83
+
 84
+@when('flannel.binaries.installed', 'flannel.etcd.credentials.installed',
 85
+      'etcd.available')
 86
+@when_not('flannel.service.installed')
 87
+def install_flannel_service(etcd):
 88
+    ''' Install the flannel service. '''
 89
+    status_set('maintenance', 'Installing flannel service.')
 90
+    default_interface = None
 91
+    cmd = ['route']
 92
+    output = check_output(cmd).decode('utf8')
 93
+    for line in output.split('\n'):
 94
+        if 'default' in line:
 95
+            default_interface = line.split(' ')[-1]
 96
+            break
 97
+    context = {'iface': config('iface') or default_interface,
 98
+               'connection_string': etcd.get_connection_string(),
 99
+               'cert_path': ETCD_PATH}
100
+    render('flannel.service', '/lib/systemd/system/flannel.service', context)
101
+    service('enable', 'flannel')
102
+    set_state('flannel.service.installed')
103
+    remove_state('flannel.service.started')
104
+
105
+
106
+@when('config.changed.iface')
107
+def reconfigure_flannel_service():
108
+    ''' Handle interface configuration change. '''
109
+    remove_state('flannel.service.installed')
110
+
111
+
112
+@when('flannel.binaries.installed', 'flannel.etcd.credentials.installed',
113
+      'etcd.available')
114
+@when_not('flannel.network.configured')
115
+def configure_network(etcd):
116
+    ''' Store initial flannel data in etcd. '''
117
+    data = json.dumps({
118
+        'Network': config('cidr'),
119
+        'Backend': {
120
+            'Type': 'vxlan'
121
+        }
122
+    })
123
+    cmd = "etcdctl "
124
+    cmd += "--endpoint '{0}' ".format(etcd.get_connection_string())
125
+    cmd += "--cert-file {0} ".format(ETCD_CERT_PATH)
126
+    cmd += "--key-file {0} ".format(ETCD_KEY_PATH)
127
+    cmd += "--ca-file {0} ".format(ETCD_CA_PATH)
128
+    cmd += "set /coreos.com/network/config '{0}'".format(data)
129
+    check_call(split(cmd))
130
+    set_state('flannel.network.configured')
131
+    remove_state('flannel.service.started')
132
+
133
+
134
+@when('config.changed.cidr')
135
+def reconfigure_network():
136
+    ''' Trigger the network configuration method. '''
137
+    remove_state('flannel.network.configured')
138
+
139
+
140
+@when('flannel.binaries.installed', 'flannel.service.installed',
141
+      'flannel.network.configured')
142
+@when_not('flannel.service.started')
143
+def start_flannel_service():
144
+    ''' Start the flannel service. '''
145
+    status_set('maintenance', 'Starting flannel service.')
146
+    if service_running('flannel'):
147
+        service_restart('flannel')
148
+    else:
149
+        service_start('flannel')
150
+    set_state('flannel.service.started')
151
+
152
+
153
+@when('cni.connected', 'flannel.service.started', 'flannel.cni.configured')
154
+@when_not('flannel.cni.available')
155
+def set_available(cni):
156
+    ''' Indicate to the CNI provider that we're ready. '''
157
+    cni.set_config(cidr=config('cidr'))
158
+    set_state('flannel.cni.available')
159
+
160
+
161
+@when('flannel.binaries.installed')
162
+@when_not('flannel.version.set')
163
+def set_flannel_version():
164
+    ''' Surface the currently deployed version of flannel to Juju '''
165
+    cmd = 'flanneld -version'
166
+    version = check_output(split(cmd), stderr=STDOUT).decode('utf-8')
167
+    if version:
168
+        application_version_set(version.split('v')[-1].strip())
169
+        set_state('flannel.version.set')
170
+
171
+
172
+@when('nrpe-external-master.available')
173
+@when_not('nrpe-external-master.initial-config')
174
+def initial_nrpe_config(nagios=None):
175
+    set_state('nrpe-external-master.initial-config')
176
+    update_nrpe_config(nagios)
177
+
178
+
179
+@when('flannel.service.started')
180
+@when('nrpe-external-master.available')
181
+@when_any('config.changed.nagios_context',
182
+          'config.changed.nagios_servicegroups')
183
+def update_nrpe_config(unused=None):
184
+    # List of systemd services that will be checked
185
+    services = ('flannel',)
186
+
187
+    # The current nrpe-external-master interface doesn't handle a lot of logic,
188
+    # use the charm-helpers code for now.
189
+    hostname = nrpe.get_nagios_hostname()
190
+    current_unit = nrpe.get_nagios_unit_name()
191
+    nrpe_setup = nrpe.NRPE(hostname=hostname, primary=False)
192
+    nrpe.add_init_service_checks(nrpe_setup, services, current_unit)
193
+    nrpe_setup.write()
194
+
195
+
196
+@when('flannel.service.started')
197
+@when_any('cni.is-master', 'flannel.cni.available')
198
+def ready():
199
+    ''' Indicate that flannel is active. '''
200
+    try:
201
+        status_set('active', 'Flannel subnet ' + get_flannel_subnet())
202
+    except FlannelSubnetNotFound:
203
+        status_set('waiting', 'Waiting for Flannel')
204
+
205
+
206
+@when_not('etcd.connected')
207
+def halt_execution():
208
+    ''' send a clear message to the user that we are waiting on etcd '''
209
+    status_set('blocked', 'Waiting for etcd relation.')
210
+
211
+
212
+@hook('upgrade-charm')
213
+def reset_states_and_redeploy():
214
+    ''' Remove state and redeploy '''
215
+    remove_state('flannel.cni.available')
216
+    remove_state('flannel.binaries.installed')
217
+    remove_state('flannel.service.started')
218
+    remove_state('flannel.version.set')
219
+    remove_state('flannel.network.configured')
220
+    remove_state('flannel.service.installed')
221
+
222
+
223
+@hook('stop')
224
+def cleanup_deployment():
225
+    ''' Terminate services, and remove the deployed bins '''
226
+    service_stop('flannel')
227
+    down = 'ip link set flannel.1 down'
228
+    delete = 'ip link delete flannel.1'
229
+    try:
230
+        check_call(split(down))
231
+        check_call(split(delete))
232
+    except CalledProcessError:
233
+        log('Unable to remove iface flannel.1')
234
+        log('Potential indication that cleanup is not possible')
235
+    files = ['/usr/local/bin/flanneld',
236
+             '/lib/systemd/system/flannel',
237
+             '/lib/systemd/system/flannel.service',
238
+             '/run/flannel/subnet.env',
239
+             '/usr/local/bin/flanneld',
240
+             '/usr/local/bin/etcdctl',
241
+             '/opt/cni/bin/flannel',
242
+             '/opt/cni/bin/bridge',
243
+             '/opt/cni/bin/host-local',
244
+             '/etc/cni/net.d/10-flannel.conf',
245
+             ETCD_KEY_PATH,
246
+             ETCD_CERT_PATH,
247
+             ETCD_CA_PATH]
248
+    for f in files:
249
+        if os.path.exists(f):
250
+            log('Removing {}'.format(f))
251
+            os.remove(f)
252
+
253
+
254
+def get_flannel_subnet():
255
+    ''' Returns the flannel subnet reserved for this unit '''
256
+    try:
257
+        with open('/run/flannel/subnet.env') as f:
258
+            raw_data = dict(line.strip().split('=') for line in f)
259
+        return raw_data['FLANNEL_SUBNET']
260
+    except FileNotFoundError as e:
261
+        raise FlannelSubnetNotFound() from e
262
+
263
+
264
+class FlannelSubnetNotFound(Exception):
265
+    pass
Back to file index

requirements.txt

1
--- 
2
+++ requirements.txt
3
@@ -0,0 +1,2 @@
4
+flake8
5
+pytest
Back to file index

revision

1
--- 
2
+++ revision
3
@@ -0,0 +1 @@
4
+0
Back to file index

templates/10-flannel.conf

 1
--- 
 2
+++ templates/10-flannel.conf
 3
@@ -0,0 +1,7 @@
 4
+{
 5
+    "name": "CDK-flannel-network",
 6
+    "type": "flannel",
 7
+    "delegate": {
 8
+      "isDefaultGateway": true
 9
+    }
10
+}
Back to file index

templates/flannel.service

 1
--- 
 2
+++ templates/flannel.service
 3
@@ -0,0 +1,14 @@
 4
+[Unit]
 5
+Description=Flannel Overlay Network
 6
+Documentation=https://github.com/coreos/flannel
 7
+Wants=network-online.target
 8
+After=network.target network-online.target
 9
+
10
+[Service]
11
+ExecStart=/usr/local/bin/flanneld -iface={{ iface }} -etcd-endpoints={{ connection_string }} -etcd-certfile={{ cert_path }}/client-cert.pem -etcd-keyfile={{ cert_path }}/client-key.pem  -etcd-cafile={{ cert_path }}/client-ca.pem
12
+TimeoutStartSec=0
13
+Restart=on-failure
14
+LimitNOFILE=655536
15
+
16
+[Install]
17
+WantedBy=multi-user.target
Back to file index

tests/tests.yaml

1
--- 
2
+++ tests/tests.yaml
3
@@ -0,0 +1,2 @@
4
+packages:
5
+  - amulet
Back to file index

tox.ini

 1
--- 
 2
+++ tox.ini
 3
@@ -0,0 +1,12 @@
 4
+[tox]
 5
+skipsdist=True
 6
+envlist = py34, py35
 7
+skip_missing_interpreters = True
 8
+
 9
+[testenv]
10
+commands = py.test -v
11
+deps =
12
+    -r{toxinidir}/requirements.txt
13
+
14
+[flake8]
15
+exclude=docs