summaryrefslogtreecommitdiff
path: root/morphlib/plugins/deploy_plugin.py
blob: 89f7ed0728b6d9bf63f5331b101c1e95d9c1af39 (plain)
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# Copyright (C) 2013  Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


import cliapp
import gzip
import os
import shutil
import tarfile
import tempfile
import uuid

import morphlib
import morphlib.plugins.branch_and_merge_plugin


class DeployPlugin(cliapp.Plugin):

    def enable(self):
        self.app.add_subcommand(
            'deploy', self.deploy,
            arg_synopsis='TYPE SYSTEM LOCATION [KEY=VALUE]')
        self.other = \
            morphlib.plugins.branch_and_merge_plugin.BranchAndMergePlugin()

    def disable(self):
        pass

    def deploy(self, args):
        '''Build a system from the current system branch'''

        if len(args) < 3:
            raise cliapp.AppException('morph build expects exactly one '
                                      'parameter: the system to build')

        deployment_type = args[0]
        system_name = args[1]
        location = args[2]
        env_vars = args[3:]

        # Deduce workspace and system branch and branch root repository.
        workspace = self.other.deduce_workspace()
        branch, branch_dir = self.other.deduce_system_branch()
        branch_root = self.other.get_branch_config(branch_dir, 'branch.root')
        branch_uuid = self.other.get_branch_config(branch_dir, 'branch.uuid')

        # Generate a UUID for the build.
        build_uuid = uuid.uuid4().hex

        build_command = morphlib.buildcommand.BuildCommand(self.app)
        build_command = self.app.hookmgr.call('new-build-command',
                                              build_command)
        push = self.app.settings['push-build-branches']

        self.app.status(msg='Starting build %(uuid)s', uuid=build_uuid)

        self.app.status(msg='Collecting morphologies involved in '
                            'building %(system)s from %(branch)s',
                            system=system_name, branch=branch)

        # Get repositories of morphologies involved in building this system
        # from the current system branch.
        build_repos = self.other.get_system_build_repos(
                branch, branch_dir, branch_root, system_name)

        # Generate temporary build ref names for all these repositories.
        self.other.generate_build_ref_names(build_repos, branch_uuid)

        # Create the build refs for all these repositories and commit
        # all uncommitted changes to them, updating all references
        # to system branch refs to point to the build refs instead.
        self.other.update_build_refs(build_repos, branch, build_uuid, push)

        if push:
            self.other.push_build_refs(build_repos)
            build_branch_root = branch_root
        else:
            dirname = build_repos[branch_root]['dirname']
            build_branch_root = urlparse.urljoin('file://', dirname)

        # Run the build.
        build_ref = build_repos[branch_root]['build-ref']
        order = build_command.compute_build_order(
            build_branch_root,
            build_ref,
            system_name + '.morph')
        artifact = order.groups[-1][-1]

        if push:
            self.other.delete_remote_build_refs(build_repos)
            
        # Unpack the artifact (tarball) to a temporary directory.
        self.app.status(msg='Unpacking system for configuration')

        system_tree = tempfile.mkdtemp()
            
        if build_command.lac.has(artifact):
            f = build_command.lac.get(artifact)
        else:
            f = build_command.rac.get(artifact)
        ff = gzip.GzipFile(fileobj=f)
        tf = tarfile.TarFile(fileobj=ff)
        tf.extractall(path=system_tree)
        
        self.app.status(
            msg='System unpacked at %(system_tree)s',
            system_tree=system_tree)

        # Set up environment for running extensions.
        env = dict(os.environ)
        for spec in env_vars:
            name, value = spec.split('=', 1)
            if name in env:
                raise morphlib.Error(
                    '%s is already set in the enviroment' % name)
            env[name] = value

        # Run configuration extensions.
        self.app.status(msg='Configure system')
        m = artifact.source.morphology
        if 'configuration-extensions' in m:
            names = m['configuration-extensions']
            for name in names:
                self._run_extension(
                    build_branch_root,
                    build_ref,
                    name,
                    '.configure',
                    [system_tree],
                    env)
        
        # Run write extension.
        self.app.status(msg='Writing to device')
        self._run_extension(
            build_branch_root,
            build_ref,
            name,
            '.write',
            [system_tree, location],
            env)
        
        # Cleanup.
        self.app.status(msg='Cleaning up')
        shutil.rmtree(system_tree)

        self.app.status(msg='Finished deployment')

    def _run_extension(self, repo_dir, ref, name, kind, args, env):
        '''Run an extension.
        
        The ``kind`` should be either ``.configure`` or ``.write``,
        depending on the kind of extension that is sought.
        
        The extension is found either in the git repository of the
        system morphology (repo, ref), or with the Morph code.
        
        '''
        
        ext = self._cat_file(repo_dir, ref, name + kind)
        if ext is None:
            code_dir = os.path.dirname(morphlib.__file__)
            ext_filename = os.path.join(code_dir, 'exts', name + kind)
            if not os.path.exists(ext_filename):
                raise morphlib.Error(
                    'Could not find extenstion %s%s' % (name, kind))
            delete_ext = False
        else:
            fd, ext_filename = tempfile.mkstemp()
            os.write(fd, ext)
            os.close(fd)
            os.chmod(ext_filename, 0700)
            delete_ext = True

        self.app.runcmd(
            [ext_filename] + args, env=env, stdout=None, stderr=None)
        
        if delete_ext:
            os.remove(ext_filename)
        
    def _cat_file(self, repo_dir, ref, pathname):
        '''Retrieve contents of a file from a git repository.'''
        
        argv = ['git', 'cat-file', 'blob', '%s:%s' % (ref, filename)]
        try:
            return self.app.runcmd(argv, cwd=repo_dir)
        except cliapp.AppException:
            return None