f4d17450 |
#! /usr/bin/python2
#
# Copyright (C) 2015 vmware inc.
#
# Author: Mahmoud Bassiouny <mbassiouny@vmware.com>
|
0f3948ba |
from argparse import ArgumentParser |
5d05cfcc |
import os.path |
f4d17450 |
import curses |
4967065a |
import subprocess
import re
import json
import time |
3c4cf0da |
import crypt
import string
import random |
8e65c5ee |
import urllib2 |
130228a5 |
import requests
import cracklib |
022959f9 |
import modules.commons |
72832a72 |
from partitionISO import PartitionISO |
f4d17450 |
from packageselector import PackageSelector
from installer import Installer |
4d53ba03 |
from installercontainer import InstallerContainer |
f4d17450 |
from windowstringreader import WindowStringReader
from jsonwrapper import JsonWrapper
from selectdisk import SelectDisk |
bc3b375f |
from license import License |
564d9533 |
from linuxselector import LinuxSelector |
f4d17450 |
class IsoInstaller(object): |
564d9533 |
|
bffb7f74 |
def get_config(self, path):
if path.startswith("http://"): |
fda9bae9 |
# Do 5 trials to get the kick start |
4967065a |
# TODO: make sure the installer run after network is up |
1b11aa2d |
ks_file_error = "Failed to get the kickstart file at {0}".format(path) |
fda9bae9 |
wait = 1 |
130228a5 |
for x in range(0, 5): |
4967065a |
err_msg = ""
try:
response = requests.get(path, timeout=3)
if response.ok:
return json.loads(response.text)
err_msg = response.text
except Exception as e:
err_msg = e |
1b11aa2d |
modules.commons.log(modules.commons.LOG_ERROR,
ks_file_error)
modules.commons.log(modules.commons.LOG_ERROR,
"error msg: {0}".format(err_msg))
print(ks_file_error)
print("retry in a second") |
fda9bae9 |
time.sleep(wait)
wait = wait * 2 |
4967065a |
# Something went wrong |
1b11aa2d |
print(ks_file_error)
print("exiting the installer, check the logs for more details") |
4967065a |
raise Exception(err_msg)
else:
if path.startswith("cdrom:/"): |
bffb7f74 |
self.mount_RPMS_cd()
path = os.path.join(self.cd_path, path.replace("cdrom:/", "", 1)) |
130228a5 |
return (JsonWrapper(path)).read() |
4967065a |
def mount_RPMS_cd(self): |
bffb7f74 |
# check if the cd is already mounted
if self.cd_path:
return
|
4967065a |
# Mount the cd to get the RPMS
process = subprocess.Popen(['mkdir', '-p', '/mnt/cdrom'])
retval = process.wait()
# Retry mount the CD |
130228a5 |
for i in range(0, 3): |
4967065a |
process = subprocess.Popen(['mount', '/dev/cdrom', '/mnt/cdrom'])
retval = process.wait()
if retval == 0: |
bffb7f74 |
self.cd_path = "/mnt/cdrom"
return |
1b11aa2d |
print("Failed to mount the cd, retry in a second") |
4967065a |
time.sleep(1) |
1b11aa2d |
print("Failed to mount the cd, exiting the installer")
print("check the logs for more details") |
4967065a |
raise Exception("Can not mount the cd") |
3c4cf0da |
def validate_hostname(self, hostname): |
4c700ec8 |
error_empty = "Empty hostname or domain is not allowed"
error_dash = "Hostname or domain should not start or end with '-'"
error_hostname = "Hostname should start with alpha char and <= 64 chars"
|
130228a5 |
if hostname is None or len(hostname) == 0: |
4c700ec8 |
return False, error_empty
fields = hostname.split('.')
for field in fields:
if len(field) == 0:
return False, error_empty
if field[0] == '-' or field[-1] == '-':
return False, error_dash
machinename = fields[0]
return (len(machinename) <= 64) and (ord(machinename[0]) in self.alpha_chars), error_hostname |
3c4cf0da |
|
1b11aa2d |
@staticmethod
def validate_password(text): |
3c4cf0da |
try:
p = cracklib.VeryFascistCheck(text)
except ValueError, message:
p = str(message)
return p == text, "Error: " + p
|
1b11aa2d |
@staticmethod
def generate_password_hash(password): |
3c4cf0da |
shadow_password = crypt.crypt(password, "$6$" + "".join([random.choice(string.ascii_letters + string.digits) for _ in range(16)]))
return shadow_password
|
58f4c279 |
def validate_http_response(self, url, checks, exception_text, error_text):
try:
if url.startswith("https"): |
130228a5 |
response = urllib2.urlopen(url, cafile="/usr/lib/python2.7/site-packages/requests/cacert.pem") |
58f4c279 |
else:
response = urllib2.urlopen(url)
except:
return exception_text
else:
if response.getcode() != 200:
return error_text |
130228a5 |
|
58f4c279 |
html = response.read() |
130228a5 |
|
58f4c279 |
for pattern, count, failed_check_text in checks:
match = re.findall(pattern, html)
if len(match) != count:
return failed_check_text
return "" |
130228a5 |
def ui_install(self, options_file, rpm_path):
# This represents the installer screen, the bool indicated if I can go back to this window or not
items = []
random_id = '%12x' % random.randrange(16**12)
random_hostname = "photon-" + random_id.strip()
install_config = {'iso_system': False} |
1b11aa2d |
install_config['ui_install'] = True |
130228a5 |
license_agreement = License(self.maxy, self.maxx)
select_disk = SelectDisk(self.maxy, self.maxx, install_config)
select_partition = PartitionISO(self.maxy, self.maxx, install_config)
package_selector = PackageSelector(self.maxy, self.maxx, install_config, options_file)
self.alpha_chars = range(65, 91)
self.alpha_chars.extend(range(97, 123))
hostname_accepted_chars = list(self.alpha_chars)
# Adding the numeric chars
hostname_accepted_chars.extend(range(48, 58))
# Adding the . and -
hostname_accepted_chars.extend([ord('.'), ord('-')])
hostname_reader = WindowStringReader(
self.maxy, self.maxx, 10, 70,
'hostname',
None, # confirmation error msg if it's a confirmation text
None, # echo char
hostname_accepted_chars, # set of accepted chars
self.validate_hostname, # validation function of the input
None, # post processing of the input field
'Choose the hostname for your system', 'Hostname:', 2, install_config,
random_hostname,
True)
root_password_reader = WindowStringReader(
self.maxy, self.maxx, 10, 70,
'password',
None, # confirmation error msg if it's a confirmation text
'*', # echo char
None, # set of accepted chars |
1b11aa2d |
IsoInstaller.validate_password, # validation function of the input |
130228a5 |
None, # post processing of the input field
'Set up root password', 'Root password:', 2, install_config)
confirm_password_reader = WindowStringReader(
self.maxy, self.maxx, 10, 70,
'password',
"Passwords don't match, please try again.", # confirmation error msg if it's a confirmation text
'*', # echo char
None, # set of accepted chars
None, # validation function of the input |
1b11aa2d |
IsoInstaller.generate_password_hash, # post processing of the input field |
130228a5 |
'Confirm root password', 'Confirm Root password:', 2, install_config)
items.append((license_agreement.display, False))
items.append((select_disk.display, True))
items.append((select_partition.display, False))
items.append((select_disk.guided_partitions, False))
items.append((package_selector.display, True))
select_linux_index = -1
if self.is_vmware_virtualization():
linux_selector = LinuxSelector(self.maxy, self.maxx, install_config)
items.append((linux_selector.display, True))
select_linux_index = items.index((linux_selector.display, True))
items.append((hostname_reader.get_user_string, True))
items.append((root_password_reader.get_user_string, True))
items.append((confirm_password_reader.get_user_string, False))
installer = InstallerContainer(
install_config,
self.maxy,
self.maxx,
True,
rpm_path=rpm_path,
log_path="/var/log")
items = items + [(installer.install, False)]
index = 0
params = None
while True:
result = items[index][0](params)
if result.success:
index += 1
params = result.result
if index == len(items) - 1:
self.screen.clear()
if index == len(items):
break
#Skip linux select screen for ostree installation.
if index == select_linux_index:
if install_config['type'] == 'ostree_server':
index += 1
else:
index -= 1
while index >= 0 and items[index][1] is False:
index -= 1
if index < 0:
index = 0
#Skip linux select screen for ostree installation.
if index == select_linux_index:
if install_config['type'] == 'ostree_server':
index -= 1
|
1b11aa2d |
def ks_install(self, options_file, rpm_path, ks_config):
install_config = ks_config
install_config['iso_system'] = False
if self.is_vmware_virtualization() and 'install_linux_esx' not in install_config:
install_config['install_linux_esx'] = True
json_wrapper_option_list = JsonWrapper("build_install_options_all.json")
option_list_json = json_wrapper_option_list.read()
options_sorted = option_list_json.items()
base_path = os.path.dirname("build_install_options_all.json")
package_list = []
package_list = PackageSelector.get_packages_to_install(options_sorted, install_config['type'], base_path)
if 'additional_packages' in install_config:
package_list.extend(install_config['additional_packages'])
install_config['packages'] = package_list
if 'partitions' in install_config:
partitions = install_config['partitions']
else:
partitions = modules.commons.default_partitions
install_config['disk'] = modules.commons.partition_disk(install_config['disk'], partitions)
if "hostname" in install_config:
evalhostname = os.popen('printf ' + install_config["hostname"].strip(" ")).readlines()
install_config['hostname'] = evalhostname[0]
if "hostname" not in install_config or install_config['hostname'] == "":
random_id = '%12x' % random.randrange(16**12)
install_config['hostname'] = "photon-" + random_id.strip()
# crypt the password if needed
if install_config['password']['crypted']:
install_config['password'] = install_config['password']['text']
else:
install_config['password'] = crypt.crypt(install_config['password']['text'],
"$6$" + "".join([random.choice(string.ascii_letters + string.digits) for _ in range(16)]))
installer = InstallerContainer(
install_config,
self.maxy, self.maxx,
True,
rpm_path=rpm_path,
log_path="/var/log")
installer.install(None) |
58f4c279 |
|
564d9533 |
def is_vmware_virtualization(self):
process = subprocess.Popen(['systemd-detect-virt'], stdout=subprocess.PIPE) |
130228a5 |
out, err = process.communicate()
if err is not None and err != 0: |
564d9533 |
return False
else:
return out == 'vmware\n'
|
5d05cfcc |
def __init__(self, stdscreen, options_file): |
f4d17450 |
self.screen = stdscreen
# Init the colors
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_WHITE)
curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_GREEN)
curses.init_pair(4, curses.COLOR_RED, curses.COLOR_WHITE)
self.screen.bkgd(' ', curses.color_pair(1))
|
130228a5 |
self.maxy, self.maxx = self.screen.getmaxyx() |
5f854778 |
self.screen.addstr(self.maxy - 1, 0, ' Arrow keys make selections; <Enter> activates.') |
f4d17450 |
curses.curs_set(0)
|
130228a5 |
self.cd_path = None
|
4967065a |
kernel_params = subprocess.check_output(['cat', '/proc/cmdline']) |
bffb7f74 |
# check the kickstart param
ks_config = None |
4967065a |
m = re.match(r".*ks=(\S+)\s*.*\s*", kernel_params)
if m != None: |
bffb7f74 |
ks_config = self.get_config(m.group(1))
# check for the repo param
m = re.match(r".*repo=(\S+)\s*.*\s*", kernel_params)
if m != None:
rpm_path = m.group(1)
else:
# the rpms should be in the cd
self.mount_RPMS_cd() |
130228a5 |
rpm_path = os.path.join(self.cd_path, "RPMS") |
f4d17450 |
|
1b11aa2d |
if ks_config:
self.ks_install(options_file, rpm_path, ks_config) |
109493d5 |
else: |
1b11aa2d |
self.ui_install(options_file, rpm_path) |
f4d17450 |
if __name__ == '__main__': |
5d05cfcc |
usage = "Usage: %prog [options]" |
0f3948ba |
parser = ArgumentParser(usage) |
130228a5 |
parser.add_argument("-j", "--json-file", dest="options_file", default="input.json") |
5d05cfcc |
|
0f3948ba |
options = parser.parse_args() |
5d05cfcc |
curses.wrapper(IsoInstaller, options.options_file) |