2015-07-17 00:12:51 +00:00
|
|
|
# Copyright 2015 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 as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# 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 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/>.
|
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
import datetime
|
|
|
|
import logging
|
2015-07-06 15:55:49 +00:00
|
|
|
import os
|
2015-09-21 21:40:26 +00:00
|
|
|
import subprocess
|
2015-07-10 20:58:25 +00:00
|
|
|
import yaml
|
2016-07-27 02:52:16 +00:00
|
|
|
|
|
|
|
from subiquitycore import utils
|
2015-07-06 15:55:49 +00:00
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
|
2016-06-30 18:17:01 +00:00
|
|
|
log = logging.getLogger("subiquitycore.curtin")
|
2015-09-21 21:40:26 +00:00
|
|
|
|
|
|
|
TMPDIR = '/tmp'
|
2015-10-07 03:09:08 +00:00
|
|
|
CURTIN_SEARCH_PATH = ['/usr/local/curtin/bin', '/usr/bin']
|
|
|
|
CURTIN_INSTALL_PATH = ['/media/root-ro', '/']
|
2015-11-16 20:59:59 +00:00
|
|
|
CURTIN_INSTALL_LOG = '/tmp/subiquity-curtin-install.log'
|
|
|
|
CURTIN_POSTINSTALL_LOG = '/tmp/subiquity-curtin-postinstall.log'
|
2015-09-21 21:40:26 +00:00
|
|
|
CONF_PREFIX = os.path.join(TMPDIR, 'subiquity-config-')
|
|
|
|
CURTIN_NETWORK_CONFIG_FILE = CONF_PREFIX + 'network.yaml'
|
|
|
|
CURTIN_STORAGE_CONFIG_FILE = CONF_PREFIX + 'storage.yaml'
|
|
|
|
CURTIN_PRESERVED_CONFIG_FILE = CONF_PREFIX + 'storage-preserved.yaml'
|
|
|
|
POST_INSTALL_CONFIG_FILE = CONF_PREFIX + 'postinst.yaml'
|
|
|
|
CURTIN_CONFIGS = {
|
|
|
|
'network': CURTIN_NETWORK_CONFIG_FILE,
|
|
|
|
'storage': CURTIN_STORAGE_CONFIG_FILE,
|
|
|
|
'postinstall': POST_INSTALL_CONFIG_FILE,
|
|
|
|
'preserved': CURTIN_PRESERVED_CONFIG_FILE,
|
|
|
|
}
|
|
|
|
CURTIN_CONFIG_HEADER = """
|
2015-11-16 20:59:59 +00:00
|
|
|
reporting:
|
2015-07-23 19:56:45 +00:00
|
|
|
subiquity:
|
2015-11-16 20:59:59 +00:00
|
|
|
type: print
|
2015-07-23 19:56:45 +00:00
|
|
|
|
2015-07-10 20:58:25 +00:00
|
|
|
partitioning_commands:
|
|
|
|
builtin: curtin block-meta custom
|
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
"""
|
2015-11-16 20:59:59 +00:00
|
|
|
|
|
|
|
CURTIN_LOG_HEADER = """
|
|
|
|
install:
|
|
|
|
log_file: {}
|
|
|
|
"""
|
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
CURTIN_CONFIG_REBOOT = """
|
|
|
|
power_state:
|
|
|
|
message: s-Ubiquity install complete. Rebooting
|
|
|
|
mode: reboot
|
|
|
|
"""
|
|
|
|
CURTIN_STORAGE_CONFIG_HEADER = """
|
2015-07-10 20:58:25 +00:00
|
|
|
storage:
|
2015-08-12 17:15:31 +00:00
|
|
|
version: 1
|
|
|
|
config:
|
2015-07-10 20:58:25 +00:00
|
|
|
"""
|
2015-09-21 21:40:26 +00:00
|
|
|
CURTIN_NETWORK_CONFIG_HEADER = """
|
|
|
|
network:
|
|
|
|
version: 1
|
|
|
|
config:
|
|
|
|
"""
|
2015-07-06 15:55:49 +00:00
|
|
|
|
2015-11-02 22:48:37 +00:00
|
|
|
# TODO, this should be moved to the in-target cloud-config seed so on first
|
|
|
|
# boot of the target, it reconfigures datasource_list to none for subsequent
|
|
|
|
# boots.
|
|
|
|
# Reworked for flake8, but it does make it harder to read.
|
|
|
|
POST_INSTALL_LIST = [
|
|
|
|
("late_commands:"),
|
|
|
|
(" 10_mkdir_seed: curtin in-target -- "
|
|
|
|
"mkdir -p /var/lib/cloud/seed/nocloud-net"),
|
|
|
|
(" 11_postinst_metadata: [curtin, in-target, --, sh, '-c',"
|
|
|
|
'"/bin/echo -e instance-id: inst-3011 '
|
|
|
|
'> /var/lib/cloud/seed/nocloud-net/meta-data"]'),
|
|
|
|
(" 12_postinst_userdata: [curtin, in-target, --, sh, '-c',"
|
|
|
|
"\"/bin/echo -e '#cloud-config\\npassword: passw0rd\\nchpasswd: "
|
2015-12-04 15:11:18 +00:00
|
|
|
"{{ expire: False }}\\n{hostinfo}\\nusers:\\n{users}' > "
|
2015-11-02 22:48:37 +00:00
|
|
|
"/var/lib/cloud/seed/nocloud-net/user-data\"]"),
|
|
|
|
]
|
|
|
|
POST_INSTALL = '\n' + "\n".join(POST_INSTALL_LIST) + '\n'
|
2015-07-23 19:56:45 +00:00
|
|
|
|
2015-07-06 15:55:49 +00:00
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
def curtin_userinfo_to_config(userinfo):
|
2015-12-04 15:11:18 +00:00
|
|
|
user_template = ' - default\\n' + \
|
|
|
|
' - name: {username}\\n' + \
|
2015-09-21 21:40:26 +00:00
|
|
|
' gecos: {realname}\\n' + \
|
2015-09-21 18:17:24 +00:00
|
|
|
' passwd: {password}\\n' + \
|
|
|
|
' shell: /bin/bash\\n' + \
|
|
|
|
' groups: admin\\n' + \
|
|
|
|
' lock-passwd: false\\n'
|
2015-12-04 15:11:18 +00:00
|
|
|
if 'ssh_import_id' in userinfo:
|
|
|
|
user_template += ' ssh-import-id: [{ssh_import_id}]\\n'
|
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
return user_template.format(**userinfo)
|
|
|
|
|
|
|
|
|
2015-12-04 15:11:18 +00:00
|
|
|
def curtin_hostinfo_to_config(hostinfo):
|
|
|
|
host_template = 'hostname: {hostname}\\n'
|
|
|
|
return host_template.format(**hostinfo)
|
|
|
|
|
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
def curtin_write_postinst_config(userinfo):
|
2016-06-22 19:19:54 +00:00
|
|
|
# firstboot doesn't get hostinfo; but it's still present in the template
|
2015-09-21 21:40:26 +00:00
|
|
|
config = {
|
2015-12-04 15:11:18 +00:00
|
|
|
'users': curtin_userinfo_to_config(userinfo),
|
2016-06-23 20:27:09 +00:00
|
|
|
'hostinfo': curtin_hostinfo_to_config(userinfo),
|
2015-09-21 21:40:26 +00:00
|
|
|
}
|
2015-12-04 15:11:18 +00:00
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
with open(POST_INSTALL_CONFIG_FILE, 'w') as conf:
|
|
|
|
datestr = '# Autogenerated by SUbiquity: {} UTC'.format(
|
|
|
|
str(datetime.datetime.utcnow()))
|
|
|
|
conf.write(datestr)
|
|
|
|
conf.write(POST_INSTALL.format(**config))
|
|
|
|
conf.close()
|
|
|
|
|
|
|
|
|
2015-11-16 20:59:59 +00:00
|
|
|
def curtin_log_header(logfile=CURTIN_INSTALL_LOG):
|
|
|
|
return CURTIN_LOG_HEADER.format(logfile)
|
|
|
|
|
|
|
|
|
2015-07-10 20:58:25 +00:00
|
|
|
def curtin_write_storage_actions(actions):
|
|
|
|
curtin_config = yaml.dump(actions, default_flow_style=False)
|
2015-08-12 17:15:31 +00:00
|
|
|
curtin_config = " " + "\n ".join(curtin_config.splitlines())
|
2015-07-23 19:56:45 +00:00
|
|
|
datestr = '# Autogenerated by SUbiquity: {} UTC'.format(
|
|
|
|
str(datetime.datetime.utcnow()))
|
2015-07-10 20:58:25 +00:00
|
|
|
with open(CURTIN_STORAGE_CONFIG_FILE, 'w') as conf:
|
2015-07-23 19:56:45 +00:00
|
|
|
conf.write(datestr)
|
2015-11-16 20:59:59 +00:00
|
|
|
conf.write(CURTIN_CONFIG_HEADER)
|
|
|
|
conf.write(curtin_log_header(logfile=CURTIN_INSTALL_LOG))
|
|
|
|
conf.write(CURTIN_STORAGE_CONFIG_HEADER)
|
2015-09-21 21:40:26 +00:00
|
|
|
conf.write(curtin_config)
|
|
|
|
conf.close()
|
|
|
|
|
|
|
|
|
|
|
|
def curtin_write_network_actions(actions):
|
|
|
|
curtin_config = yaml.dump(actions, default_flow_style=False)
|
|
|
|
curtin_config = " " + "\n ".join(curtin_config.splitlines())
|
|
|
|
datestr = '# Autogenerated by SUbiquity: {} UTC'.format(
|
|
|
|
str(datetime.datetime.utcnow()))
|
|
|
|
with open(CURTIN_NETWORK_CONFIG_FILE, 'w') as conf:
|
|
|
|
conf.write(datestr)
|
|
|
|
conf.write(CURTIN_CONFIG_HEADER + CURTIN_NETWORK_CONFIG_HEADER)
|
2015-07-10 20:58:25 +00:00
|
|
|
conf.write(curtin_config)
|
|
|
|
conf.close()
|
|
|
|
|
|
|
|
|
2016-06-22 19:19:54 +00:00
|
|
|
def curtin_apply_networking(actions, dryrun=True):
|
|
|
|
log.info('Applying network actions:\n%s', actions)
|
|
|
|
network_commands = []
|
|
|
|
for entry in actions:
|
|
|
|
if entry['type'] == 'physical':
|
|
|
|
for subnet in entry.get('subnets', []):
|
|
|
|
if subnet['type'] == 'static':
|
|
|
|
cmd = "ifconfig %s %s" % (entry['name'], subnet['address'])
|
|
|
|
if 'netmask' in subnet:
|
|
|
|
cmd += " netmask %s" % subnet['netmask']
|
|
|
|
cmd += " up"
|
|
|
|
network_commands += [cmd]
|
|
|
|
|
|
|
|
for cmd in network_commands:
|
|
|
|
log.info('Running command: [%s]', cmd)
|
|
|
|
if not dryrun:
|
|
|
|
utils.run_command(cmd.split(), shell=False)
|
2016-07-25 02:44:51 +00:00
|
|
|
|
2016-06-22 19:19:54 +00:00
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
def curtin_write_preserved_actions(actions):
|
|
|
|
''' caller must use models.actions.preserve_action on
|
|
|
|
all elements of the actions'''
|
|
|
|
curtin_config = yaml.dump(actions, default_flow_style=False)
|
|
|
|
curtin_config = " " + "\n ".join(curtin_config.splitlines())
|
|
|
|
datestr = '# Autogenerated by SUbiquity: {} UTC'.format(
|
|
|
|
str(datetime.datetime.utcnow()))
|
|
|
|
with open(CURTIN_PRESERVED_CONFIG_FILE, 'w') as conf:
|
2015-07-23 19:56:45 +00:00
|
|
|
conf.write(datestr)
|
2015-11-16 20:59:59 +00:00
|
|
|
conf.write(CURTIN_CONFIG_HEADER)
|
|
|
|
conf.write(curtin_log_header(logfile=CURTIN_POSTINSTALL_LOG))
|
|
|
|
conf.write(CURTIN_STORAGE_CONFIG_HEADER)
|
2015-09-21 21:40:26 +00:00
|
|
|
conf.write(curtin_config)
|
2015-07-23 19:56:45 +00:00
|
|
|
conf.close()
|
2015-09-21 21:40:26 +00:00
|
|
|
|
|
|
|
|
2015-10-07 03:09:08 +00:00
|
|
|
def curtin_find_curtin():
|
|
|
|
for p in CURTIN_SEARCH_PATH:
|
|
|
|
curtin = os.path.join(p, 'curtin')
|
|
|
|
if os.path.exists(curtin):
|
|
|
|
log.debug('curtin found at: {}'.format(curtin))
|
|
|
|
return curtin
|
2015-10-20 21:21:32 +00:00
|
|
|
# This ensures we fail when we attempt to run curtin
|
|
|
|
# but it's not present
|
|
|
|
return '/bin/false'
|
2015-10-07 03:09:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
def curtin_find_install_path():
|
|
|
|
for p in CURTIN_INSTALL_PATH:
|
|
|
|
if os.path.exists(p):
|
|
|
|
log.debug('install path set: {}'.format(p))
|
|
|
|
return p
|
|
|
|
|
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
def curtin_install_cmd(configs):
|
|
|
|
'''
|
2015-11-16 20:59:59 +00:00
|
|
|
curtin -vvv --showtrace install -c $CONFIGS cp:///
|
2015-09-21 21:40:26 +00:00
|
|
|
'''
|
2015-10-07 03:09:08 +00:00
|
|
|
curtin = curtin_find_curtin()
|
|
|
|
install_path = curtin_find_install_path()
|
|
|
|
|
2015-11-16 20:59:59 +00:00
|
|
|
install_cmd = [curtin, '-vvv', '--showtrace']
|
2015-09-21 21:40:26 +00:00
|
|
|
if configs:
|
|
|
|
install_cmd += ['-c {}'.format(c) for c in configs]
|
2015-10-07 03:09:08 +00:00
|
|
|
install_cmd += ['install', 'cp://{}'.format(install_path)]
|
2015-09-21 21:40:26 +00:00
|
|
|
log.info('curtin install command: {}'.format(" ".join(install_cmd)))
|
|
|
|
|
|
|
|
return install_cmd
|
|
|
|
|
|
|
|
|
|
|
|
def curtin_reboot():
|
|
|
|
cmd = "/sbin/reboot"
|
|
|
|
log.info("powering off with %s", cmd)
|
|
|
|
fid = os.fork()
|
|
|
|
if fid == 0:
|
|
|
|
try:
|
|
|
|
subprocess.call([cmd])
|
|
|
|
os._exit(0)
|
|
|
|
except:
|
|
|
|
log.warn("%s returned non-zero" % cmd)
|
|
|
|
os._exit(1)
|
|
|
|
return
|