f4d17450 |
#! /usr/bin/python2
#
# Copyright (C) 2015 vmware inc.
#
# Author: Mahmoud Bassiouny <mbassiouny@vmware.com>
|
5d05cfcc |
from optparse import OptionParser
import os.path |
f4d17450 |
import curses
import sys |
4967065a |
import subprocess
import re
import requests
import json
import time
import os |
3c4cf0da |
import cracklib
import crypt
import string
import random |
386e9359 |
import urllib |
8e65c5ee |
import urllib2 |
022959f9 |
import modules.commons |
f4d17450 |
from diskpartitioner import DiskPartitioner
from packageselector import PackageSelector
from custompackageselector import CustomPackageSelector
from installer import Installer |
4d53ba03 |
from installercontainer import InstallerContainer
from ostreeinstaller import OstreeInstaller |
f4d17450 |
from windowstringreader import WindowStringReader |
3c4cf0da |
from ostreewindowstringreader import OSTreeWindowStringReader |
f4d17450 |
from jsonwrapper import JsonWrapper
from selectdisk import SelectDisk |
bc3b375f |
from license import License |
3c4cf0da |
from ostreeserverselector import OSTreeServerSelector |
f4d17450 |
class IsoInstaller(object): |
58f4c279 |
|
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 |
fda9bae9 |
wait = 1
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 |
022959f9 |
modules.commons.log(modules.commons.LOG_ERROR, "Failed to get the kickstart file at {0}, error msg: {1}".format(path, err_msg)) |
4967065a |
print "Failed to get the kickstart file at {0}, retry in a second".format(path) |
fda9bae9 |
time.sleep(wait)
wait = wait * 2 |
4967065a |
# Something went wrong
print "Failed to get the kickstart file at {0}, exiting the installer, check the logs for more details".format(path)
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)) |
4967065a |
return (JsonWrapper(path)).read();
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
for i in range(0,3):
process = subprocess.Popen(['mount', '/dev/cdrom', '/mnt/cdrom'])
retval = process.wait()
if retval == 0: |
bffb7f74 |
self.cd_path = "/mnt/cdrom"
return |
4967065a |
print "Failed to mount the cd, retry in a second"
time.sleep(1)
print "Failed to mount the cd, exiting the installer, check the logs for more details"
raise Exception("Can not mount the cd") |
3c4cf0da |
def validate_hostname(self, hostname): |
042e4ee4 |
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"
|
3c4cf0da |
if (hostname == None or len(hostname) == 0): |
042e4ee4 |
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 |
|
58f4c279 |
def validate_ostree_url_input(self, ostree_repo_url):
if not ostree_repo_url: |
1d241d9e |
return False, "Error: Invalid input" |
386e9359 |
|
58f4c279 |
exception_text = "Error: Invalid or unreachable URL"
error_text = "Error: Repo URL not accessible"
ret = self.validate_http_response(ostree_repo_url, [], exception_text, error_text)
if ret != "":
return False, ret |
8e65c5ee |
|
58f4c279 |
exception_text = "Error: Invalid repo - missing config"
ret = self.validate_http_response(
ostree_repo_url + "/config",
[ [".*\[core\]\s*", 1, "Error: Invalid config - 'core' group expected" ],
["\s*mode[ \t]*=[ \t]*archive-z2[^ \t]", 1, "Error: can't pull from repo in 'bare' mode, 'archive-z2' mode required" ] ],
exception_text, exception_text)
if ret != "":
return False, ret
exception_text = "Error: Invalid repo - missing refs"
ret = self.validate_http_response(ostree_repo_url + "/refs/heads", [], exception_text, exception_text)
if ret != "":
return False, ret
exception_text = "Error: Invalid repo - missing objects"
ret = self.validate_http_response(ostree_repo_url + "/objects", [], exception_text, exception_text)
if ret != "":
return False, ret
self.ostree_repo_url = ostree_repo_url |
386e9359 |
return True, None
|
58f4c279 |
def validate_ostree_refs_input(self, ostree_repo_ref):
if not ostree_repo_ref:
return False, "Error: Invalid input"
ret = self.validate_http_response(
self.ostree_repo_url + '/refs/heads/' + ostree_repo_ref,
[ ["^\s*[0-9A-Fa-f]{64}\s*$", 1, "Error: Incomplete Refspec path, or unexpected Refspec format"] ],
"Error: Invalid Refspec path",
"Error: Refspec not accessible")
if ret != "":
return False, ret
return True, None |
386e9359 |
|
3c4cf0da |
def validate_password(self, text):
try:
p = cracklib.VeryFascistCheck(text)
except ValueError, message:
p = str(message)
return p == text, "Error: " + p
def generate_password_hash(self, password):
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"):
response = urllib2.urlopen(url,cafile="/usr/lib/python2.7/site-packages/requests/cacert.pem")
else:
response = urllib2.urlopen(url)
except:
return exception_text
else:
if response.getcode() != 200:
return error_text
html = response.read()
for pattern, count, failed_check_text in checks:
match = re.findall(pattern, html)
if len(match) != count:
return failed_check_text
return ""
|
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))
self.maxy, self.maxx = self.screen.getmaxyx()
self.screen.addstr(self.maxy - 1, 0, '<Tab> moves; <Space> selects; <Enter> forward')
curses.curs_set(0)
|
bffb7f74 |
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()
rpm_path=os.path.join(self.cd_path, "RPMS") |
f4d17450 |
# This represents the installer screen, the bool indicated if I can go back to this window or not |
4967065a |
items = []
if not ks_config: |
a9158ca2 |
random_id = '%12x' % random.randrange(16**12)
random_hostname = "photon-" + random_id.strip() |
109493d5 |
install_config = {'iso_system': False} |
bffb7f74 |
license_agreement = License(self.maxy, self.maxx) |
109493d5 |
select_disk = SelectDisk(self.maxy, self.maxx, install_config)
package_selector = PackageSelector(self.maxy, self.maxx, install_config, options_file) |
3c4cf0da |
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)
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
self.validate_password, # validation function of the input
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
self.generate_password_hash, # post processing of the input field
'Confirm root password', 'Confirm Root password:', 2, install_config)
ostree_server_selector = OSTreeServerSelector(self.maxy, self.maxx, install_config)
ostree_url_reader = OSTreeWindowStringReader(
self.maxy, self.maxx, 10, 80,
'ostree_repo_url',
None, # confirmation error msg if it's a confirmation text
None, # echo char
None, # set of accepted chars |
386e9359 |
self.validate_ostree_url_input, # validation function of the input |
3c4cf0da |
None, # post processing of the input field
'Please provide the URL of OSTree repo', 'OSTree Repo URL:', 2, install_config, |
1d241d9e |
"http://") |
3c4cf0da |
ostree_ref_reader = OSTreeWindowStringReader(
self.maxy, self.maxx, 10, 70,
'ostree_repo_ref',
None, # confirmation error msg if it's a confirmation text
None, # echo char
None, # set of accepted chars |
386e9359 |
self.validate_ostree_refs_input, # validation function of the input |
3c4cf0da |
None, # post processing of the input field |
58f4c279 |
'Please provide the Refspec in OSTree repo', 'OSTree Repo Refspec:', 2, install_config,
"photon/1.0/x86_64/minimal") |
bffb7f74 |
|
4967065a |
items = items + [ |
b84da80d |
(license_agreement.display, False),
(select_disk.display, True), |
f4d17450 |
(package_selector.display, True),
(hostname_reader.get_user_string, True),
(root_password_reader.get_user_string, True), |
1ec3a643 |
(confirm_password_reader.get_user_string, False), |
3c4cf0da |
(ostree_server_selector.display, True),
(ostree_url_reader.get_user_string, True),
(ostree_ref_reader.get_user_string, True), |
4967065a |
] |
109493d5 |
else:
install_config = ks_config
install_config['iso_system'] = False
installer = InstallerContainer(install_config, self.maxy, self.maxx, True, rpm_path=rpm_path, log_path="/var/log", ks_config=ks_config) |
4d53ba03 |
|
4967065a |
items = items + [(installer.install, False)] |
f4d17450 |
index = 0
params = None
while True:
result = items[index][0](params)
if result.success:
index += 1
params = result.result
if index == len(items):
break
else:
index -= 1
while index >= 0 and items[index][1] == False:
index -= 1
if index < 0:
index = 0
if __name__ == '__main__': |
5d05cfcc |
usage = "Usage: %prog [options]"
parser = OptionParser(usage)
parser.add_option("-j", "--json-file", dest="options_file", default="input.json")
(options, args) = parser.parse_args()
curses.wrapper(IsoInstaller, options.options_file) |