ansiblecube.py 6.74 KB
Newer Older
1
2
3
4
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4 nu

import os
thiolliere's avatar
thiolliere committed
5
import json
6
7
8
import tempfile
import posixpath

9
10
import yaml

11
from data import mirror
12
from version import get_version_str
13
from backend.content import get_content
14
from backend.catalog import CATALOGS, get_catalogs
15
16

ansiblecube_path = "/var/lib/ansible/local"
17
18
19
20
21
# update CATALOGS to include our in-qemu local URL
for catalog in CATALOGS:
    catalog.update(
        {"local_url": "file:///home/pi/catalog_{}.yml".format(catalog["name"])}
    )
22
23
24


def run(machine, tags, extra_vars={}, secret_keys=[]):
25
    """ run ansiblecube in given machine with specified tags and extra-vars """
26
27
28

    # save extra_vars to a file on guest
    extra_vars_path = posixpath.join(ansiblecube_path, "extra_vars.json")
29
    with tempfile.NamedTemporaryFile("w", delete=False) as fp:
30
        json.dump(extra_vars, fp, indent=4)
31
32
33
34
35
        fp.close()
        machine.put_file(fp.name, extra_vars_path)
        os.unlink(fp.name)

    # prepare ansible command
36
37
38
39
40
41
42
    ansible_cmd = [
        "/usr/local/bin/ansible-playbook",
        "--inventory hosts",
        "--tags {}".format(",".join(tags)),
        '--extra-vars="@{}"'.format(extra_vars_path),
        "main.yml",
    ]
43
44
45
46

    # display sent configuration to logger
    machine._logger.std("ansiblecube extra_vars")
    machine._logger.raw_std(
47
        json.dumps(
48
            {k: "****" if k in secret_keys else v for k, v in extra_vars.items()},
49
50
51
            indent=4,
        )
    )
52
53

    # review the list of tasks so the logger can  use it to track progression
54
55
56
57
58
59
60
    tasks_cmd = ansible_cmd[0:1] + ["--list-tasks"] + ansible_cmd[1:]
    machine.exec_cmd(
        'sh -c \'cd {path} && tasks=$({cmd} | paste -sd "^" -) '
        '&& echo "### TASKS ### $tasks"\''.format(
            path=ansiblecube_path, cmd=" ".join(tasks_cmd)
        )
    )
61
62

    # run ansible-playbook
63
64
65
66
67
68
    ansible_cmd = ansible_cmd[0:1] + ["-vvv"] + ansible_cmd[1:]  # verbose
    machine.exec_cmd(
        "sudo sh -c 'cd {path} && {cmd}'".format(
            path=ansiblecube_path, cmd=" ".join(ansible_cmd)
        )
    )
69
70
71


def run_for_image(machine, root_partition_size, disk_size):
72
73
    """ initial launch of a bare raspbian to create a base (master) image """
    tags = ["master", "rename", "setup"]
thiolliere's avatar
thiolliere committed
74

rgaudin's avatar
rgaudin committed
75
    machine.exec_cmd("sudo apt-get update -y")
76
    # install ansible dependencies (packages)
77
    machine.exec_cmd(
rgaudin's avatar
rgaudin committed
78
        "sudo apt-get install -y python-dev libffi-dev libssl-dev git lsb-release"
79
    )
80
    # install the latest pip
rgaudin's avatar
rgaudin committed
81
    machine.exec_cmd("wget https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py")
82
83
    machine.exec_cmd("sudo python /tmp/get-pip.py")
    # install latest ansible and important python dependencies
84
85
    machine.exec_cmd(
        "sudo sudo python -m pip install -U "
86
        "pip virtualenv jinja2 paramiko pyyaml httplib2 ansible==2.6.18"
87
    )
thiolliere's avatar
thiolliere committed
88

89
    # prepare ansible files
thiolliere's avatar
thiolliere committed
90
    machine.exec_cmd("sudo mkdir --mode 0755 -p /etc/ansible")
91
92
93
    machine.exec_cmd(
        "sudo cp {path}/hosts /etc/ansible/hosts".format(path=ansiblecube_path)
    )
94
95
96
97
    machine.exec_cmd("sudo mkdir --mode 0755 -p /etc/ansible/facts.d")

    # default configuration on the master: all contents enabled for setup tag
    extra_vars, _ = build_extra_vars(
98
99
100
101
102
        name="default",
        timezone="UTC",
        language="en",
        language_name="English",
        wifi_pwd="",
103
        edupi=True,
rgaudin's avatar
rgaudin committed
104
        edupi_resources=None,
105
106
107
        wikifundi_languages=["en", "fr"],
        aflatoun_languages=["en", "fr"],
        kalite_languages=["en", "fr", "es"],
108
109
110
        packages=[],
        admin_account=None,
        root_partition_size=root_partition_size,
111
112
        disk_size=disk_size,
    )
thiolliere's avatar
thiolliere committed
113

114
    run(machine, tags, extra_vars)
thiolliere's avatar
thiolliere committed
115

116

117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def build_extra_vars(
    name,
    timezone,
    language,
    language_name,
    wifi_pwd,
    edupi,
    edupi_resources,
    wikifundi_languages,
    aflatoun_languages,
    kalite_languages,
    packages,
    admin_account,
    root_partition_size,
    disk_size,
):
    """ extra-vars friendly format of the ansiblecube configuration """
134
135

    extra_vars = {
136
137
138
139
140
        # predefined defaults we want to superseed whichever in ansiblecube
        "installer_version": get_version_str(),
        "mirror": mirror,
        "catalogs": CATALOGS,
        "kernel_version": get_content("raspbian_image").get("kernel_version"),
141
142
        "root_partition_size": root_partition_size,
        "disk_size": disk_size,
143
144
145
146
147
148
149
150
151
152
153
154
155
        "project_name": name,
        "timezone": timezone,
        "language": language,
        "language_name": language_name,
        "kalite_languages": kalite_languages,
        "wikifundi_languages": wikifundi_languages,
        "aflatoun_languages": aflatoun_languages,
        "edupi": edupi,
        "edupi_has_resources": bool(edupi_resources),
        "packages": packages,
        "custom_branding_path": "/tmp",
        "admin_account": "admin",
        "admin_password": "admin-password",
156
157
158
    }

    if wifi_pwd:
159
        extra_vars.update({"wpa_pass": wifi_pwd})
thiolliere's avatar
thiolliere committed
160

161
    if admin_account is not None:
162
163
164
165
166
167
        extra_vars.update(
            {
                "admin_account": admin_account["login"],
                "admin_password": admin_account["pwd"],
            }
        )
168
        secret_keys = ["admin_password"]
169
    else:
170
171
172
173
174
        secret_keys = []

    return extra_vars, secret_keys


rgaudin's avatar
rgaudin committed
175
176
177
def run_phase_one(
    machine, extra_vars, secret_keys, homepage, logo=None, favicon=None, css=None
):
178
    """ run ansiblecube in machine to configure requested softwares """
179

180
    tags = ["rename", "reconfigure"]
181

rgaudin's avatar
rgaudin committed
182
183
184
    # copy homepage
    machine.put_file(homepage, "/tmp/home.html")

185
    # copy branding files if set
rgaudin's avatar
rgaudin committed
186
    branding = {"favicon.png": favicon, "logo.png": logo, "style.css": css}
187
188
189
190
191
192

    has_custom_branding = False
    for fname, item in [(k, v) for k, v in branding.items() if v is not None]:
        has_custom_branding = True
        machine.put_file(item, "/tmp/{}".format(fname))

193
    extra_vars.update({"has_custom_branding": has_custom_branding})
194

195
196
    # save YAML catalogs into local files inside VM for use by ideascube
    for index, catalog in enumerate(CATALOGS):
197
        with tempfile.NamedTemporaryFile(suffix=".yml", delete=False) as fd:
198
199
200
201
202
203
204
205
            yaml.safe_dump(
                get_catalogs(machine._logger)[index],
                fd,
                default_flow_style=False,
                allow_unicode=True,
                encoding="utf-8",
            )
            machine.put_file(fd.name, catalog["local_url"].replace("file://", ""))
206
207
208
209
            try:
                os.unlink(fd.name)
            except Exception as exp:
                machine._logger.err(str(exp))
210

211
212
213
214
    run(machine, tags, extra_vars, secret_keys)


def run_phase_two(machine, extra_vars, secret_keys):
215
    """ run ansiblecube to complete config now that content is in data part """
216

217
    tags = ["move-content", "seal"]
218

219
    run(machine, tags, extra_vars, secret_keys)