ansiblecube.py 5.88 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
9
10
11
12
13
14
15
16
17
import tempfile
import posixpath

from data import mirror
from util import ONE_GB
from backend.catalog import CATALOGS
from backend.content import get_content

ansiblecube_path = "/var/lib/ansible/local"


def run(machine, tags, extra_vars={}, secret_keys=[]):
18
    """ run ansiblecube in given machine with specified tags and extra-vars """
19
20
21

    # predefined defaults we want to superseed whichever in ansiblecube
    ansible_vars = {
22
23
24
        "mirror": mirror,
        "catalogs": CATALOGS,
        "kernel_version": get_content("raspbian_image").get("kernel_version"),
25
26
27
28
29
    }
    ansible_vars.update(extra_vars)

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

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

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

    # review the list of tasks so the logger can  use it to track progression
55
56
57
58
59
60
61
    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)
        )
    )
62
63

    # run ansible-playbook
64
65
66
67
68
69
    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)
        )
    )
70
71
72


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

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

90
    # prepare ansible files
thiolliere's avatar
thiolliere committed
91
    machine.exec_cmd("sudo mkdir --mode 0755 -p /etc/ansible")
92
93
94
    machine.exec_cmd(
        "sudo cp {path}/hosts /etc/ansible/hosts".format(path=ansiblecube_path)
    )
95
96
97
98
    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(
99
100
101
102
103
        name="default",
        timezone="UTC",
        language="en",
        language_name="English",
        wifi_pwd="",
104
        edupi=True,
rgaudin's avatar
rgaudin committed
105
        edupi_resources=None,
106
107
108
        wikifundi_languages=["en", "fr"],
        aflatoun_languages=["en", "fr"],
        kalite_languages=["en", "fr", "es"],
109
110
111
        packages=[],
        admin_account=None,
        root_partition_size=root_partition_size,
112
113
        disk_size=disk_size,
    )
thiolliere's avatar
thiolliere committed
114

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

117

118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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 """
135
136

    extra_vars = {
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
        "root_partition_size": root_partition_size // ONE_GB,
        "disk_size": disk_size // ONE_GB,
        "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",
152
153
154
    }

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

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

    return extra_vars, secret_keys


171
172
def run_phase_one(machine, extra_vars, secret_keys, logo=None, favicon=None, css=None):
    """ run ansiblecube in machine to configure requested softwares """
173

174
    tags = ["resize", "rename", "reconfigure"]
175
176

    # copy branding files if set
177
    branding = {"favicon.png": favicon, "header-logo.png": logo, "style.css": css}
178
179
180
181
182
183

    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))

184
    extra_vars.update({"has_custom_branding": has_custom_branding})
185
186
187
188
189

    run(machine, tags, extra_vars, secret_keys)


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

192
    tags = ["move-content", "seal"]
193

194
    run(machine, tags, extra_vars, secret_keys)