~canonical-ci-engineering/charms/precise/uci-engine-key-secret-subordinate/trunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# Ubuntu CI Engine
# Copyright 2014 Canonical Ltd.

# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License version 3, as
# published by the Free Software Foundation.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
import mock
import os
import unittest
import shutil
import tempfile
import base64

import yaml

import hooks


PATCHES = [
    'charmhelpers.core.hookenv.config',
    'charmhelpers.core.hookenv.log',
    'charmhelpers.core.hookenv.relations_of_type',
    'charmhelpers.core.hookenv.remote_unit',
    'charmhelpers.core.host.mkdir',
]


class JujuTestCase(unittest.TestCase):
    def setUp(self):
        """Setup a testing hook environment.

        Patches the required charmhelper and intercom objects and
        initializes few mocks for tests.
        """
        super(JujuTestCase, self).setUp()
        self.tmpdir = tempfile.mkdtemp()
        self.addCleanup(shutil.rmtree, self.tmpdir)

        for spec in PATCHES:
            attr_name = spec.split('.')[-1]
            _m = mock.patch(spec)
            setattr(self, attr_name, _m.start())
            self.addCleanup(_m.stop)

        self.unit_name = 'bazinga/0'
        self.service_dir = os.path.join(self.tmpdir, 'bazinga')
        self.keys_dir = os.path.join(self.service_dir, 'keys')
        self.remote_unit.return_value = self.unit_name
        self.relations_of_type.side_effect = (
            lambda rel_type: [{'__unit__': self.unit_name}])
        self.mkdir.side_effect = lambda d, perms=None: os.mkdir(d)

        self._set_default_config()

    def _set_default_config(self):
        defaults = {}
        config = os.path.join(os.path.dirname(__file__), '../config.yaml')
        if os.path.exists(config):
            with open(config) as f:
                config = yaml.safe_load(f)
                for name, option in config['options'].iteritems():
                    defaults[name] = option.get('default')
        defaults['install_root'] = self.tmpdir
        self.config.return_value = defaults

    def test_service_dir(self):
        # Unit names get sanitized before discovering its service name.
        expected = os.path.join(self.tmpdir, 'bazinga')
        self.assertEqual(expected, hooks._service_dir(self.unit_name))
        expected = os.path.join(self.tmpdir, 'foo-bar_x')
        self.assertEqual(expected, hooks._service_dir('foo-bar:x/42'))

    def test_config_changed_no_container(self):
        # Configuration changes have not effect until the subordinate
        # charm gets a container (parent).
        self.relations_of_type.side_effect = lambda rel_type: []
        os.mkdir(self.service_dir)

        hooks.hooks.execute(['hooks/config-changed'])
        self.assertEqual([], os.listdir(self.service_dir))

    def test_config_changed_full(self):
        # Configuration changes have immediate effect on bound charms
        # and existing 'keys' directory is removed and a new one is created
        # with configured key contents.
        # Fake and previous 'keys' directory.
        os.makedirs(self.keys_dir)
        with open(os.path.join(self.keys_dir, 'deleted'), 'w') as fd:
            fd.write('gone!')
        # Set of keys for this test.
        config = {
            'ssh-private-key': base64.b64encode('secret!'),
            'ssh-public-key': base64.b64encode('public?'),
            'gpg-secret-keyring': base64.b64encode('secret!'),
            'gpg-public-keyring': base64.b64encode('public?'),
            'launchpad-credentials': base64.b64encode('launchpad!'),
        }
        self.config.return_value.update(config)

        hooks.hooks.execute(['hooks/config-changed'])
        self.assertEqual(['keys'], os.listdir(self.service_dir))
        keys = sorted(os.listdir(self.keys_dir))
        self.assertEqual(
            ['gpg.pub', 'gpg.sec', 'id_rsa', 'id_rsa.pub',
             'launchpad.credentials'], keys)

    def test_juju_info_joined(self):
        # Keys are placed immediately when the subordinate charm is bound.
        os.makedirs(self.service_dir)
        self.config.return_value['ssh-private-key'] = base64.b64encode(
            'secret!')

        hooks.hooks.execute(['hooks/juju-info-relation-joined'])
        self.assertEqual(['keys'], os.listdir(self.service_dir))
        self.assertEqual(['id_rsa'], os.listdir(self.keys_dir))