Change-Id: Idad0f5e11a41c048ec8b187e1bf93a5ea29af918
Reviewed-on: http://photon-jenkins.eng.vmware.com:8082/4614
Tested-by: gerrit-photon <photon-checkins@vmware.com>
Reviewed-by: Divya Thaluru <dthaluru@vmware.com>
... | ... |
@@ -3,7 +3,6 @@ |
3 | 3 |
# Author: Mahmoud Bassiouny <mbassiouny@vmware.com> |
4 | 4 |
|
5 | 5 |
import subprocess |
6 |
-import curses |
|
7 | 6 |
import os |
8 | 7 |
import shutil |
9 | 8 |
import signal |
... | ... |
@@ -34,7 +33,7 @@ class Installer(object): |
34 | 34 |
else: |
35 | 35 |
self.working_directory = "/mnt/photon-root" |
36 | 36 |
self.photon_root = self.working_directory + "/photon-chroot" |
37 |
- |
|
37 |
+ self.rpms_tobeinstalled = None |
|
38 | 38 |
self.restart_command = "shutdown" |
39 | 39 |
|
40 | 40 |
if self.iso_installer: |
... | ... |
@@ -62,7 +61,9 @@ class Installer(object): |
62 | 62 |
signal.signal(signal.SIGINT, self.exit_gracefully) |
63 | 63 |
|
64 | 64 |
# This will be called if the installer interrupted by Ctrl+C or exception |
65 |
- def exit_gracefully(self, signal, frame): |
|
65 |
+ def exit_gracefully(self, signal1, frame1): |
|
66 |
+ del signal1 |
|
67 |
+ del frame1 |
|
66 | 68 |
if self.iso_installer: |
67 | 69 |
self.progress_bar.hide() |
68 | 70 |
self.window.addstr(0, 0, 'Oops, Installer got interrupted.\n\n' + |
... | ... |
@@ -73,8 +74,9 @@ class Installer(object): |
73 | 73 |
sys.exit(1) |
74 | 74 |
|
75 | 75 |
def install(self, params): |
76 |
+ del params |
|
76 | 77 |
try: |
77 |
- return self.unsafe_install(params) |
|
78 |
+ return self.unsafe_install() |
|
78 | 79 |
except Exception as inst: |
79 | 80 |
if self.iso_installer: |
80 | 81 |
modules.commons.log(modules.commons.LOG_ERROR, repr(inst)) |
... | ... |
@@ -82,105 +84,13 @@ class Installer(object): |
82 | 82 |
else: |
83 | 83 |
raise |
84 | 84 |
|
85 |
- def unsafe_install(self, params): |
|
86 |
- |
|
87 |
- if self.iso_installer: |
|
88 |
- self.window.show_window() |
|
89 |
- self.progress_bar.initialize('Initializing installation...') |
|
90 |
- self.progress_bar.show() |
|
91 |
- #self.rpm_path = "https://dl.bintray.com/vmware/photon_release_1.0_TP2_x86_64" |
|
92 |
- if self.rpm_path.startswith("https://") or self.rpm_path.startswith("http://"): |
|
93 |
- cmdoption = 's/baseurl.*/baseurl={}/g'.format(self.rpm_path.replace('/', '\/')) |
|
94 |
- process = subprocess.Popen(['sed', '-i', cmdoption, |
|
95 |
- '/etc/yum.repos.d/photon-iso.repo']) |
|
96 |
- retval = process.wait() |
|
97 |
- if retval != 0: |
|
98 |
- modules.commons.log(modules.commons.LOG_INFO, "Failed to reset repo") |
|
99 |
- self.exit_gracefully(None, None) |
|
100 |
- |
|
101 |
- cmdoption = ('s/cachedir=\/var/cachedir={}/g' |
|
102 |
- .format(self.photon_root.replace('/', '\/'))) |
|
103 |
- process = subprocess.Popen(['sed', '-i', cmdoption, '/etc/tdnf/tdnf.conf']) |
|
104 |
- retval = process.wait() |
|
105 |
- if retval != 0: |
|
106 |
- modules.commons.log(modules.commons.LOG_INFO, "Failed to reset tdnf cachedir") |
|
107 |
- self.exit_gracefully(None, None) |
|
85 |
+ def unsafe_install(self): |
|
86 |
+ self.setup_install_repo() |
|
108 | 87 |
self.execute_modules(modules.commons.PRE_INSTALL) |
109 | 88 |
|
110 | 89 |
self.initialize_system() |
111 |
- |
|
112 |
- if self.iso_installer: |
|
113 |
- self.adjust_packages_for_vmware_virt() |
|
114 |
- selected_packages = self.install_config['packages'] |
|
115 |
- state = 0 |
|
116 |
- packages_to_install = {} |
|
117 |
- total_size = 0 |
|
118 |
- with open(modules.commons.TDNF_CMDLINE_FILE_NAME, "w") as tdnf_cmdline_file: |
|
119 |
- tdnf_cmdline_file.write("tdnf install --installroot {0} --nogpgcheck {1}" |
|
120 |
- .format(self.photon_root, " ".join(selected_packages))) |
|
121 |
- with open(modules.commons.TDNF_LOG_FILE_NAME, "w") as tdnf_errlog: |
|
122 |
- process = subprocess.Popen(['tdnf', 'install'] + selected_packages + |
|
123 |
- ['--installroot', self.photon_root, '--nogpgcheck', |
|
124 |
- '--assumeyes'], stdout=subprocess.PIPE, |
|
125 |
- stderr=tdnf_errlog) |
|
126 |
- while True: |
|
127 |
- output = process.stdout.readline().decode() |
|
128 |
- if output == '': |
|
129 |
- retval = process.poll() |
|
130 |
- if retval is not None: |
|
131 |
- break |
|
132 |
- if state == 0: |
|
133 |
- if output == 'Installing:\n': |
|
134 |
- state = 1 |
|
135 |
- elif state == 1: #N A EVR Size(readable) Size(in bytes) |
|
136 |
- if output == '\n': |
|
137 |
- state = 2 |
|
138 |
- self.progress_bar.update_num_items(total_size) |
|
139 |
- else: |
|
140 |
- info = output.split() |
|
141 |
- package = '{0}-{1}.{2}'.format(info[0], info[2], info[1]) |
|
142 |
- packages_to_install[package] = int(info[5]) |
|
143 |
- total_size += int(info[5]) |
|
144 |
- elif state == 2: |
|
145 |
- if output == 'Downloading:\n': |
|
146 |
- self.progress_bar.update_message('Preparing ...') |
|
147 |
- state = 3 |
|
148 |
- elif state == 3: |
|
149 |
- self.progress_bar.update_message(output) |
|
150 |
- if output == 'Running transaction\n': |
|
151 |
- state = 4 |
|
152 |
- else: |
|
153 |
- modules.commons.log(modules.commons.LOG_INFO, "[tdnf] {0}".format(output)) |
|
154 |
- prefix = 'Installing/Updating: ' |
|
155 |
- if output.startswith(prefix): |
|
156 |
- package = output[len(prefix):].rstrip('\n') |
|
157 |
- self.progress_bar.increment(packages_to_install[package]) |
|
158 |
- |
|
159 |
- self.progress_bar.update_message(output) |
|
160 |
- # 0 : succeed; 137 : package already installed; 65 : package not found in repo. |
|
161 |
- if retval != 0 and retval != 137: |
|
162 |
- modules.commons.log(modules.commons.LOG_ERROR, |
|
163 |
- "Failed to install some packages, refer to {0}" |
|
164 |
- .format(modules.commons.TDNF_LOG_FILE_NAME)) |
|
165 |
- self.exit_gracefully(None, None) |
|
166 |
- else: |
|
167 |
- #install packages |
|
168 |
- rpms = [] |
|
169 |
- for rpm in self.rpms_tobeinstalled: |
|
170 |
- # We already installed the filesystem in the preparation |
|
171 |
- if rpm['package'] == 'filesystem': |
|
172 |
- continue |
|
173 |
- rpms.append(rpm['filename']) |
|
174 |
- return_value = self.install_package(rpms) |
|
175 |
- if return_value != 0: |
|
176 |
- self.exit_gracefully(None, None) |
|
177 |
- |
|
178 |
- |
|
179 |
- if self.iso_installer: |
|
180 |
- self.progress_bar.show_loading('Finalizing installation') |
|
181 |
- |
|
182 |
- if os.path.exists("/etc/resolv.conf"): |
|
183 |
- shutil.copy("/etc/resolv.conf", self.photon_root + '/etc/.') |
|
90 |
+ self.install_packages() |
|
91 |
+ self.enable_network_in_chroot() |
|
184 | 92 |
self.finalize_system() |
185 | 93 |
|
186 | 94 |
if not self.install_config['iso_system']: |
... | ... |
@@ -228,9 +138,7 @@ class Installer(object): |
228 | 228 |
retval = process.wait() |
229 | 229 |
|
230 | 230 |
self.update_fstab() |
231 |
- |
|
232 |
- if os.path.exists(self.photon_root + '/etc/resolv.conf'): |
|
233 |
- os.remove(self.photon_root + '/etc/resolv.conf') |
|
231 |
+ self.disable_network_in_chroot() |
|
234 | 232 |
|
235 | 233 |
command = [self.unmount_disk_command, '-w', self.photon_root] |
236 | 234 |
if not self.install_config['iso_system']: |
... | ... |
@@ -240,17 +148,10 @@ class Installer(object): |
240 | 240 |
|
241 | 241 |
if self.iso_installer: |
242 | 242 |
self.progress_bar.hide() |
243 |
- self.window.addstr(0, 0, 'Congratulations, Photon has been installed in {0} secs.\n\n' + |
|
243 |
+ self.window.addstr(0, 0, 'Congratulations, Photon has been installed in {0} secs.\n\n' |
|
244 | 244 |
'Press any key to continue to boot...' |
245 | 245 |
.format(self.progress_bar.time_elapsed)) |
246 |
- eject_cdrom = True |
|
247 |
- if 'ui_install' in self.install_config: |
|
248 |
- self.window.content_window().getch() |
|
249 |
- if 'eject_cdrom' in self.install_config and not self.install_config['eject_cdrom']: |
|
250 |
- eject_cdrom = False |
|
251 |
- if eject_cdrom: |
|
252 |
- process = subprocess.Popen(['eject', '-r'], stdout=self.output) |
|
253 |
- process.wait() |
|
246 |
+ self.eject_cdrom() |
|
254 | 247 |
return ActionResult(True, None) |
255 | 248 |
|
256 | 249 |
def copy_rpms(self): |
... | ... |
@@ -263,7 +164,7 @@ class Installer(object): |
263 | 263 |
|
264 | 264 |
for pkg in selected_packages: |
265 | 265 |
if pkg in pkg_to_rpm_map: |
266 |
- if not pkg_to_rpm_map[pkg]['rpm'] is None: |
|
266 |
+ if pkg_to_rpm_map[pkg]['rpm'] is not None: |
|
267 | 267 |
name = pkg_to_rpm_map[pkg]['rpm'] |
268 | 268 |
basename = os.path.basename(name) |
269 | 269 |
self.rpms_tobeinstalled.append({'filename': basename, 'path': name, |
... | ... |
@@ -421,7 +322,7 @@ class Installer(object): |
421 | 421 |
|
422 | 422 |
rpms = set(rpm_file_names) |
423 | 423 |
rpm_paths = [] |
424 |
- for root, dirs, files in os.walk(self.rpm_path): |
|
424 |
+ for root, _, files in os.walk(self.rpm_path): |
|
425 | 425 |
for f in files: |
426 | 426 |
if f in rpms: |
427 | 427 |
rpm_paths.append(os.path.join(root, f)) |
... | ... |
@@ -457,7 +358,7 @@ class Installer(object): |
457 | 457 |
continue |
458 | 458 |
|
459 | 459 |
# the module default is disabled |
460 |
- if not hasattr(mod, 'enabled') or mod.enabled == False: |
|
460 |
+ if not hasattr(mod, 'enabled') or mod.enabled is False: |
|
461 | 461 |
modules.commons.log(modules.commons.LOG_INFO, |
462 | 462 |
"module {} is not enabled".format(module)) |
463 | 463 |
continue |
... | ... |
@@ -475,7 +376,7 @@ class Installer(object): |
475 | 475 |
"Error: not able to execute module {}".format(module)) |
476 | 476 |
continue |
477 | 477 |
|
478 |
- mod.execute(module, self.install_config, self.photon_root) |
|
478 |
+ mod.execute(self.install_config, self.photon_root) |
|
479 | 479 |
|
480 | 480 |
def adjust_packages_for_vmware_virt(self): |
481 | 481 |
try: |
... | ... |
@@ -492,3 +393,115 @@ class Installer(object): |
492 | 492 |
selected_packages.append('linux-esx') |
493 | 493 |
except KeyError: |
494 | 494 |
pass |
495 |
+ |
|
496 |
+ def setup_install_repo(self): |
|
497 |
+ if self.iso_installer: |
|
498 |
+ self.window.show_window() |
|
499 |
+ self.progress_bar.initialize('Initializing installation...') |
|
500 |
+ self.progress_bar.show() |
|
501 |
+ #self.rpm_path = "https://dl.bintray.com/vmware/photon_release_1.0_TP2_x86_64" |
|
502 |
+ if self.rpm_path.startswith("https://") or self.rpm_path.startswith("http://"): |
|
503 |
+ cmdoption = 's/baseurl.*/baseurl={}/g'.format(self.rpm_path.replace('/', '\/')) |
|
504 |
+ process = subprocess.Popen(['sed', '-i', cmdoption, |
|
505 |
+ '/etc/yum.repos.d/photon-iso.repo']) |
|
506 |
+ retval = process.wait() |
|
507 |
+ if retval != 0: |
|
508 |
+ modules.commons.log(modules.commons.LOG_INFO, "Failed to reset repo") |
|
509 |
+ self.exit_gracefully(None, None) |
|
510 |
+ |
|
511 |
+ cmdoption = ('s/cachedir=\/var/cachedir={}/g' |
|
512 |
+ .format(self.photon_root.replace('/', '\/'))) |
|
513 |
+ process = subprocess.Popen(['sed', '-i', cmdoption, '/etc/tdnf/tdnf.conf']) |
|
514 |
+ retval = process.wait() |
|
515 |
+ if retval != 0: |
|
516 |
+ modules.commons.log(modules.commons.LOG_INFO, "Failed to reset tdnf cachedir") |
|
517 |
+ self.exit_gracefully(None, None) |
|
518 |
+ |
|
519 |
+ def install_packages(self): |
|
520 |
+ if self.iso_installer: |
|
521 |
+ self.tdnf_install_packages() |
|
522 |
+ else: |
|
523 |
+ #install packages |
|
524 |
+ rpms = [] |
|
525 |
+ for rpm in self.rpms_tobeinstalled: |
|
526 |
+ # We already installed the filesystem in the preparation |
|
527 |
+ if rpm['package'] == 'filesystem': |
|
528 |
+ continue |
|
529 |
+ rpms.append(rpm['filename']) |
|
530 |
+ return_value = self.install_package(rpms) |
|
531 |
+ if return_value != 0: |
|
532 |
+ self.exit_gracefully(None, None) |
|
533 |
+ |
|
534 |
+ def tdnf_install_packages(self): |
|
535 |
+ self.adjust_packages_for_vmware_virt() |
|
536 |
+ selected_packages = self.install_config['packages'] |
|
537 |
+ state = 0 |
|
538 |
+ packages_to_install = {} |
|
539 |
+ total_size = 0 |
|
540 |
+ with open(modules.commons.TDNF_CMDLINE_FILE_NAME, "w") as tdnf_cmdline_file: |
|
541 |
+ tdnf_cmdline_file.write("tdnf install --installroot {0} --nogpgcheck {1}" |
|
542 |
+ .format(self.photon_root, " ".join(selected_packages))) |
|
543 |
+ with open(modules.commons.TDNF_LOG_FILE_NAME, "w") as tdnf_errlog: |
|
544 |
+ process = subprocess.Popen(['tdnf', 'install'] + selected_packages + |
|
545 |
+ ['--installroot', self.photon_root, '--nogpgcheck', |
|
546 |
+ '--assumeyes'], stdout=subprocess.PIPE, |
|
547 |
+ stderr=tdnf_errlog) |
|
548 |
+ while True: |
|
549 |
+ output = process.stdout.readline().decode() |
|
550 |
+ if output == '': |
|
551 |
+ retval = process.poll() |
|
552 |
+ if retval is not None: |
|
553 |
+ break |
|
554 |
+ if state == 0: |
|
555 |
+ if output == 'Installing:\n': |
|
556 |
+ state = 1 |
|
557 |
+ elif state == 1: #N A EVR Size(readable) Size(in bytes) |
|
558 |
+ if output == '\n': |
|
559 |
+ state = 2 |
|
560 |
+ self.progress_bar.update_num_items(total_size) |
|
561 |
+ else: |
|
562 |
+ info = output.split() |
|
563 |
+ package = '{0}-{1}.{2}'.format(info[0], info[2], info[1]) |
|
564 |
+ packages_to_install[package] = int(info[5]) |
|
565 |
+ total_size += int(info[5]) |
|
566 |
+ elif state == 2: |
|
567 |
+ if output == 'Downloading:\n': |
|
568 |
+ self.progress_bar.update_message('Preparing ...') |
|
569 |
+ state = 3 |
|
570 |
+ elif state == 3: |
|
571 |
+ self.progress_bar.update_message(output) |
|
572 |
+ if output == 'Running transaction\n': |
|
573 |
+ state = 4 |
|
574 |
+ else: |
|
575 |
+ modules.commons.log(modules.commons.LOG_INFO, "[tdnf] {0}".format(output)) |
|
576 |
+ prefix = 'Installing/Updating: ' |
|
577 |
+ if output.startswith(prefix): |
|
578 |
+ package = output[len(prefix):].rstrip('\n') |
|
579 |
+ self.progress_bar.increment(packages_to_install[package]) |
|
580 |
+ |
|
581 |
+ self.progress_bar.update_message(output) |
|
582 |
+ # 0 : succeed; 137 : package already installed; 65 : package not found in repo. |
|
583 |
+ if retval != 0 and retval != 137: |
|
584 |
+ modules.commons.log(modules.commons.LOG_ERROR, |
|
585 |
+ "Failed to install some packages, refer to {0}" |
|
586 |
+ .format(modules.commons.TDNF_LOG_FILE_NAME)) |
|
587 |
+ self.exit_gracefully(None, None) |
|
588 |
+ self.progress_bar.show_loading('Finalizing installation') |
|
589 |
+ |
|
590 |
+ def eject_cdrom(self): |
|
591 |
+ eject_cdrom = True |
|
592 |
+ if 'ui_install' in self.install_config: |
|
593 |
+ self.window.content_window().getch() |
|
594 |
+ if 'eject_cdrom' in self.install_config and not self.install_config['eject_cdrom']: |
|
595 |
+ eject_cdrom = False |
|
596 |
+ if eject_cdrom: |
|
597 |
+ process = subprocess.Popen(['eject', '-r'], stdout=self.output) |
|
598 |
+ process.wait() |
|
599 |
+ |
|
600 |
+ def enable_network_in_chroot(self): |
|
601 |
+ if os.path.exists("/etc/resolv.conf"): |
|
602 |
+ shutil.copy("/etc/resolv.conf", self.photon_root + '/etc/.') |
|
603 |
+ |
|
604 |
+ def disable_network_in_chroot(self): |
|
605 |
+ if os.path.exists(self.photon_root + '/etc/resolv.conf'): |
|
606 |
+ os.remove(self.photon_root + '/etc/resolv.conf') |
... | ... |
@@ -18,24 +18,35 @@ from license import License |
18 | 18 |
from linuxselector import LinuxSelector |
19 | 19 |
|
20 | 20 |
class IsoConfig(object): |
21 |
- def Configure(self,options_file, maxy, maxx): |
|
21 |
+ """This class handles iso installer configuration.""" |
|
22 |
+ def __init__(self): |
|
22 | 23 |
self.cd_path = None |
24 |
+ self.alpha_chars = list(range(65, 91)) |
|
25 |
+ self.alpha_chars.extend(range(97, 123)) |
|
26 |
+ self.hostname_accepted_chars = self.alpha_chars |
|
27 |
+ # Adding the numeric chars |
|
28 |
+ self.hostname_accepted_chars.extend(range(48, 58)) |
|
29 |
+ # Adding the . and - |
|
30 |
+ self.hostname_accepted_chars.extend([ord('.'), ord('-')]) |
|
31 |
+ self.random_id = '%12x' % random.randrange(16**12) |
|
32 |
+ self.random_hostname = "photon-" + self.random_id.strip() |
|
23 | 33 |
|
34 |
+ def Configure(self, options_file, maxy, maxx): |
|
24 | 35 |
kernel_params = subprocess.check_output(['cat', '/proc/cmdline']) |
25 | 36 |
|
26 | 37 |
# check for the repo param |
27 | 38 |
m = re.match(r".*repo=(\S+)\s*.*\s*", kernel_params.decode()) |
28 |
- if m != None: |
|
39 |
+ if m is not None: |
|
29 | 40 |
rpm_path = m.group(1) |
30 | 41 |
else: |
31 | 42 |
# the rpms should be in the cd |
32 |
- self.mount_RPMS_cd() |
|
43 |
+ self.mount_cd() |
|
33 | 44 |
rpm_path = os.path.join(self.cd_path, "RPMS") |
34 | 45 |
|
35 | 46 |
# check the kickstart param |
36 | 47 |
ks_config = None |
37 | 48 |
m = re.match(r".*ks=(\S+)\s*.*\s*", kernel_params.decode()) |
38 |
- if m != None: |
|
49 |
+ if m is not None: |
|
39 | 50 |
ks_config = self.get_config(m.group(1)) |
40 | 51 |
|
41 | 52 |
install_config = None |
... | ... |
@@ -45,21 +56,23 @@ class IsoConfig(object): |
45 | 45 |
install_config = self.ui_config(options_file, maxy, maxx) |
46 | 46 |
return rpm_path, install_config |
47 | 47 |
|
48 |
- def is_vmware_virtualization(self): |
|
48 |
+ @staticmethod |
|
49 |
+ def is_vmware_virtualization(): |
|
50 |
+ """Detect vmware vm""" |
|
49 | 51 |
process = subprocess.Popen(['systemd-detect-virt'], stdout=subprocess.PIPE) |
50 | 52 |
out, err = process.communicate() |
51 | 53 |
if err is not None and err != 0: |
52 | 54 |
return False |
53 |
- else: |
|
54 |
- return out == 'vmware\n' |
|
55 |
+ return out == 'vmware\n' |
|
55 | 56 |
|
56 | 57 |
def get_config(self, path): |
58 |
+ """kick start configuration""" |
|
57 | 59 |
if path.startswith("http://"): |
58 | 60 |
# Do 5 trials to get the kick start |
59 | 61 |
# TODO: make sure the installer run after network is up |
60 | 62 |
ks_file_error = "Failed to get the kickstart file at {0}".format(path) |
61 | 63 |
wait = 1 |
62 |
- for x in range(0, 5): |
|
64 |
+ for _ in range(0, 5): |
|
63 | 65 |
err_msg = "" |
64 | 66 |
try: |
65 | 67 |
response = requests.get(path, timeout=3) |
... | ... |
@@ -70,9 +83,9 @@ class IsoConfig(object): |
70 | 70 |
err_msg = e |
71 | 71 |
|
72 | 72 |
modules.commons.log(modules.commons.LOG_ERROR, |
73 |
- ks_file_error) |
|
73 |
+ ks_file_error) |
|
74 | 74 |
modules.commons.log(modules.commons.LOG_ERROR, |
75 |
- "error msg: {0}".format(err_msg)) |
|
75 |
+ "error msg: {0}".format(err_msg)) |
|
76 | 76 |
print(ks_file_error) |
77 | 77 |
print("retry in a second") |
78 | 78 |
time.sleep(wait) |
... | ... |
@@ -84,11 +97,12 @@ class IsoConfig(object): |
84 | 84 |
raise Exception(err_msg) |
85 | 85 |
else: |
86 | 86 |
if path.startswith("cdrom:/"): |
87 |
- self.mount_RPMS_cd() |
|
87 |
+ self.mount_cd() |
|
88 | 88 |
path = os.path.join(self.cd_path, path.replace("cdrom:/", "", 1)) |
89 | 89 |
return (JsonWrapper(path)).read() |
90 | 90 |
|
91 |
- def mount_RPMS_cd(self): |
|
91 |
+ def mount_cd(self): |
|
92 |
+ """Mount the cd with RPMS""" |
|
92 | 93 |
# check if the cd is already mounted |
93 | 94 |
if self.cd_path: |
94 | 95 |
return |
... | ... |
@@ -98,7 +112,7 @@ class IsoConfig(object): |
98 | 98 |
retval = process.wait() |
99 | 99 |
|
100 | 100 |
# Retry mount the CD |
101 |
- for i in range(0, 3): |
|
101 |
+ for _ in range(0, 3): |
|
102 | 102 |
process = subprocess.Popen(['mount', '/dev/cdrom', '/mnt/cdrom']) |
103 | 103 |
retval = process.wait() |
104 | 104 |
if retval == 0: |
... | ... |
@@ -111,6 +125,8 @@ class IsoConfig(object): |
111 | 111 |
raise Exception("Can not mount the cd") |
112 | 112 |
|
113 | 113 |
def ks_config(self, options_file, ks_config): |
114 |
+ """Load configuration from file""" |
|
115 |
+ del options_file |
|
114 | 116 |
install_config = ks_config |
115 | 117 |
install_config['iso_system'] = False |
116 | 118 |
if self.is_vmware_virtualization() and 'install_linux_esx' not in install_config: |
... | ... |
@@ -123,7 +139,9 @@ class IsoConfig(object): |
123 | 123 |
base_path = os.path.dirname("build_install_options_all.json") |
124 | 124 |
package_list = [] |
125 | 125 |
|
126 |
- package_list = PackageSelector.get_packages_to_install(options_sorted, install_config['type'], base_path) |
|
126 |
+ package_list = PackageSelector.get_packages_to_install(options_sorted, |
|
127 |
+ install_config['type'], |
|
128 |
+ base_path) |
|
127 | 129 |
if 'additional_packages' in install_config: |
128 | 130 |
package_list.extend(install_config['additional_packages']) |
129 | 131 |
install_config['packages'] = package_list |
... | ... |
@@ -139,77 +157,103 @@ class IsoConfig(object): |
139 | 139 |
evalhostname = os.popen('printf ' + install_config["hostname"].strip(" ")).readlines() |
140 | 140 |
install_config['hostname'] = evalhostname[0] |
141 | 141 |
if "hostname" not in install_config or install_config['hostname'] == "": |
142 |
- random_id = '%12x' % random.randrange(16**12) |
|
143 |
- install_config['hostname'] = "photon-" + random_id.strip() |
|
142 |
+ install_config['hostname'] = "photon-" + self.random_id.strip() |
|
144 | 143 |
|
145 | 144 |
# crypt the password if needed |
146 | 145 |
if install_config['password']['crypted']: |
147 | 146 |
install_config['password'] = install_config['password']['text'] |
148 | 147 |
else: |
149 |
- install_config['password'] = crypt.crypt(install_config['password']['text'], |
|
150 |
- "$6$" + "".join([random.choice(string.ascii_letters + string.digits) for _ in range(16)])) |
|
148 |
+ install_config['password'] = crypt.crypt( |
|
149 |
+ install_config['password']['text'], |
|
150 |
+ "$6$" + "".join([random.choice( |
|
151 |
+ string.ascii_letters + string.digits) for _ in range(16)])) |
|
151 | 152 |
return install_config |
152 | 153 |
|
153 |
- def validate_hostname(self, hostname): |
|
154 |
+ @staticmethod |
|
155 |
+ def validate_hostname(hostname): |
|
156 |
+ """A valid hostname must start with a letter""" |
|
154 | 157 |
error_empty = "Empty hostname or domain is not allowed" |
155 | 158 |
error_dash = "Hostname or domain should not start or end with '-'" |
156 | 159 |
error_hostname = "Hostname should start with alpha char and <= 64 chars" |
157 | 160 |
|
158 |
- if hostname is None or len(hostname) == 0: |
|
161 |
+ if hostname is None or not hostname: |
|
159 | 162 |
return False, error_empty |
160 | 163 |
|
161 | 164 |
fields = hostname.split('.') |
162 | 165 |
for field in fields: |
163 |
- if len(field) == 0: |
|
166 |
+ if not field: |
|
164 | 167 |
return False, error_empty |
165 | 168 |
if field[0] == '-' or field[-1] == '-': |
166 | 169 |
return False, error_dash |
167 | 170 |
|
168 | 171 |
machinename = fields[0] |
169 |
- return (len(machinename) <= 64) and (ord(machinename[0]) in self.alpha_chars), error_hostname |
|
172 |
+ return (len(machinename) <= 64 and |
|
173 |
+ machinename[0].isalpha(), error_hostname) |
|
170 | 174 |
|
171 | 175 |
@staticmethod |
172 | 176 |
def validate_password(text): |
177 |
+ """Validate password with cracklib""" |
|
173 | 178 |
try: |
174 |
- p = cracklib.VeryFascistCheck(text) |
|
179 |
+ password = cracklib.VeryFascistCheck(text) |
|
175 | 180 |
except ValueError as message: |
176 |
- p = str(message) |
|
177 |
- return p == text, "Error: " + p |
|
181 |
+ password = str(message) |
|
182 |
+ return password == text, "Error: " + password |
|
178 | 183 |
|
179 | 184 |
@staticmethod |
180 | 185 |
def generate_password_hash(password): |
181 |
- shadow_password = crypt.crypt(password, "$6$" + "".join([random.choice(string.ascii_letters + string.digits) for _ in range(16)])) |
|
186 |
+ """Generate hash for the password""" |
|
187 |
+ shadow_password = crypt.crypt( |
|
188 |
+ password, "$6$" + "".join( |
|
189 |
+ [random.choice( |
|
190 |
+ string.ascii_letters + string.digits) for _ in range(16)])) |
|
182 | 191 |
return shadow_password |
183 | 192 |
|
184 | 193 |
def ui_config(self, options_file, maxy, maxx): |
185 |
- # This represents the installer screen, the bool indicated if I can go back to this window or not |
|
186 |
- items = [] |
|
187 |
- random_id = '%12x' % random.randrange(16**12) |
|
188 |
- random_hostname = "photon-" + random_id.strip() |
|
194 |
+ """Configuration through UI""" |
|
195 |
+ # This represents the installer screen, the bool indicated if |
|
196 |
+ # I can go back to this window or not |
|
189 | 197 |
install_config = {'iso_system': False} |
190 | 198 |
install_config['ui_install'] = True |
199 |
+ items, select_linux_index = self.add_ui_pages(options_file, maxy, maxx, |
|
200 |
+ install_config) |
|
201 |
+ index = 0 |
|
202 |
+ while True: |
|
203 |
+ result = items[index][0](None) |
|
204 |
+ if result.success: |
|
205 |
+ index += 1 |
|
206 |
+ if index == len(items): |
|
207 |
+ break |
|
208 |
+ #Skip linux select screen for ostree installation. |
|
209 |
+ if index == select_linux_index: |
|
210 |
+ if install_config['type'] == 'ostree_server': |
|
211 |
+ index += 1 |
|
212 |
+ else: |
|
213 |
+ index -= 1 |
|
214 |
+ while index >= 0 and items[index][1] is False: |
|
215 |
+ index -= 1 |
|
216 |
+ if index < 0: |
|
217 |
+ index = 0 |
|
218 |
+ #Skip linux select screen for ostree installation. |
|
219 |
+ if index == select_linux_index: |
|
220 |
+ if install_config['type'] == 'ostree_server': |
|
221 |
+ index -= 1 |
|
222 |
+ return install_config |
|
223 |
+ def add_ui_pages(self, options_file, maxy, maxx, install_config): |
|
224 |
+ items = [] |
|
191 | 225 |
license_agreement = License(maxy, maxx) |
192 | 226 |
select_disk = SelectDisk(maxy, maxx, install_config) |
193 | 227 |
select_partition = PartitionISO(maxy, maxx, install_config) |
194 | 228 |
package_selector = PackageSelector(maxy, maxx, install_config, options_file) |
195 |
- self.alpha_chars = list(range(65, 91)) |
|
196 |
- self.alpha_chars.extend(range(97, 123)) |
|
197 |
- hostname_accepted_chars = self.alpha_chars |
|
198 |
- # Adding the numeric chars |
|
199 |
- hostname_accepted_chars.extend(range(48, 58)) |
|
200 |
- # Adding the . and - |
|
201 |
- hostname_accepted_chars.extend([ord('.'), ord('-')]) |
|
202 |
- |
|
203 | 229 |
hostname_reader = WindowStringReader( |
204 | 230 |
maxy, maxx, 10, 70, |
205 | 231 |
'hostname', |
206 | 232 |
None, # confirmation error msg if it's a confirmation text |
207 | 233 |
None, # echo char |
208 |
- hostname_accepted_chars, # set of accepted chars |
|
209 |
- self.validate_hostname, # validation function of the input |
|
234 |
+ self.hostname_accepted_chars, # set of accepted chars |
|
235 |
+ IsoConfig.validate_hostname, # validation function of the input |
|
210 | 236 |
None, # post processing of the input field |
211 | 237 |
'Choose the hostname for your system', 'Hostname:', 2, install_config, |
212 |
- random_hostname, |
|
238 |
+ self.random_hostname, |
|
213 | 239 |
True) |
214 | 240 |
root_password_reader = WindowStringReader( |
215 | 241 |
maxy, maxx, 10, 70, |
... | ... |
@@ -223,7 +267,8 @@ class IsoConfig(object): |
223 | 223 |
confirm_password_reader = WindowStringReader( |
224 | 224 |
maxy, maxx, 10, 70, |
225 | 225 |
'password', |
226 |
- "Passwords don't match, please try again.", # confirmation error msg if it's a confirmation text |
|
226 |
+ # confirmation error msg if it's a confirmation text |
|
227 |
+ "Passwords don't match, please try again.", |
|
227 | 228 |
'*', # echo char |
228 | 229 |
None, # set of accepted chars |
229 | 230 |
None, # validation function of the input |
... | ... |
@@ -243,27 +288,4 @@ class IsoConfig(object): |
243 | 243 |
items.append((hostname_reader.get_user_string, True)) |
244 | 244 |
items.append((root_password_reader.get_user_string, True)) |
245 | 245 |
items.append((confirm_password_reader.get_user_string, False)) |
246 |
- index = 0 |
|
247 |
- params = None |
|
248 |
- while True: |
|
249 |
- result = items[index][0](params) |
|
250 |
- if result.success: |
|
251 |
- index += 1 |
|
252 |
- params = result.result |
|
253 |
- if index == len(items): |
|
254 |
- break |
|
255 |
- #Skip linux select screen for ostree installation. |
|
256 |
- if index == select_linux_index: |
|
257 |
- if install_config['type'] == 'ostree_server': |
|
258 |
- index += 1 |
|
259 |
- else: |
|
260 |
- index -= 1 |
|
261 |
- while index >= 0 and items[index][1] is False: |
|
262 |
- index -= 1 |
|
263 |
- if index < 0: |
|
264 |
- index = 0 |
|
265 |
- #Skip linux select screen for ostree installation. |
|
266 |
- if index == select_linux_index: |
|
267 |
- if install_config['type'] == 'ostree_server': |
|
268 |
- index -= 1 |
|
269 |
- return install_config |
|
246 |
+ return items, select_linux_index |
... | ... |
@@ -6,8 +6,8 @@ PRE_INSTALL = "pre-install" |
6 | 6 |
POST_INSTALL = "post-install" |
7 | 7 |
|
8 | 8 |
LOG_LEVEL_DESC = ["emerg", "alert", "crit", "err", "warning", "notice", "info", "debug"] |
9 |
-LOG_FILE_NAME = "/var/log/installer.log" |
|
10 |
-TDNF_LOG_FILE_NAME = "/var/log/tdnf.log" |
|
9 |
+LOG_FILE_NAME = "/var/log/installer.log" |
|
10 |
+TDNF_LOG_FILE_NAME = "/var/log/tdnf.log" |
|
11 | 11 |
TDNF_CMDLINE_FILE_NAME = "/var/log/tdnf.cmdline" |
12 | 12 |
KS_POST_INSTALL_LOG_FILE_NAME = "/var/log/installer-kickstart.log" |
13 | 13 |
SIGNATURE = "localhost echo" |
... | ... |
@@ -21,8 +21,8 @@ LOG_INFO = 6 |
21 | 21 |
LOG_DEBUG = 7 |
22 | 22 |
|
23 | 23 |
default_partitions = [ |
24 |
- {"mountpoint": "/", "size": 0, "filesystem": "ext4"}, |
|
25 |
- ] |
|
24 |
+ {"mountpoint": "/", "size": 0, "filesystem": "ext4"}, |
|
25 |
+ ] |
|
26 | 26 |
|
27 | 27 |
def partition_compare(p): |
28 | 28 |
if 'mountpoint' in p: |
... | ... |
@@ -36,7 +36,7 @@ def partition_disk(disk, partitions): |
36 | 36 |
output = open(os.devnull, 'w') |
37 | 37 |
|
38 | 38 |
# Clear the disk |
39 |
- process = subprocess.Popen(['sgdisk', '-o', '-g', disk], stderr = output, stdout = output) |
|
39 |
+ process = subprocess.Popen(['sgdisk', '-o', '-g', disk], stderr=output, stdout=output) |
|
40 | 40 |
retval = process.wait() |
41 | 41 |
if retval != 0: |
42 | 42 |
log(LOG_ERROR, "Failed clearing disk {0}".format(disk)) |
... | ... |
@@ -58,7 +58,7 @@ def partition_disk(disk, partitions): |
58 | 58 |
# Adding the known size partitions |
59 | 59 |
for partition in partitions: |
60 | 60 |
if partition['size'] == 0: |
61 |
- # Can not have more than 1 extensible partition |
|
61 |
+ # Can not have more than 1 extensible partition |
|
62 | 62 |
if extensible_partition != None: |
63 | 63 |
log(LOG_ERROR, "Can not have more than 1 extensible partition") |
64 | 64 |
return None |
... | ... |
@@ -80,13 +80,13 @@ def partition_disk(disk, partitions): |
80 | 80 |
partition_cmd.extend(['-p', disk]) |
81 | 81 |
|
82 | 82 |
# Run the partitioning command |
83 |
- process = subprocess.Popen(partition_cmd, stderr = output, stdout = output) |
|
83 |
+ process = subprocess.Popen(partition_cmd, stderr=output, stdout=output) |
|
84 | 84 |
retval = process.wait() |
85 | 85 |
if retval != 0: |
86 | 86 |
log(LOG_ERROR, "Faild partition disk, command: {0}". format(partition_cmd)) |
87 | 87 |
return None |
88 | 88 |
|
89 |
- process = subprocess.Popen(['sgdisk', '-t1' + grub_flag, disk], stderr = output, stdout = output) |
|
89 |
+ process = subprocess.Popen(['sgdisk', '-t1' + grub_flag, disk], stderr=output, stdout=output) |
|
90 | 90 |
retval = process.wait() |
91 | 91 |
if retval != 0: |
92 | 92 |
log(LOG_ERROR, "Failed to setup grub partition") |
... | ... |
@@ -103,25 +103,27 @@ def partition_disk(disk, partitions): |
103 | 103 |
partitions_data['boot_partition_number'] = partition['partition_number'] |
104 | 104 |
partitions_data['bootdirectory'] = '/' |
105 | 105 |
if partition['filesystem'] == "swap": |
106 |
- process = subprocess.Popen(['mkswap', partition['path']], stderr = output, stdout = output) |
|
106 |
+ process = subprocess.Popen(['mkswap', partition['path']], stderr=output, stdout=output) |
|
107 | 107 |
retval = process.wait() |
108 | 108 |
if retval != 0: |
109 | 109 |
log(LOG_ERROR, "Failed to create swap partition @ {}".format(partition['path'])) |
110 | 110 |
return None |
111 | 111 |
else: |
112 | 112 |
mkfs_cmd = ['mkfs', '-t', partition['filesystem'], partition['path']] |
113 |
- process = subprocess.Popen(mkfs_cmd, stderr = output, stdout = output) |
|
113 |
+ process = subprocess.Popen(mkfs_cmd, stderr=output, stdout=output) |
|
114 | 114 |
retval = process.wait() |
115 | 115 |
if retval != 0: |
116 |
- log(LOG_ERROR, "Failed to format {} partition @ {}".format(partition['filesystem'], partition['path'])) |
|
116 |
+ log(LOG_ERROR, |
|
117 |
+ "Failed to format {} partition @ {}".format(partition['filesystem'], |
|
118 |
+ partition['path'])) |
|
117 | 119 |
return None |
118 | 120 |
|
119 | 121 |
# Check if there is no root partition |
120 |
- if not 'root' in partitions_data: |
|
122 |
+ if 'root' not in partitions_data: |
|
121 | 123 |
log(LOG_ERROR, "There is no partition assigned to root '/'") |
122 | 124 |
return None |
123 | 125 |
|
124 |
- if not 'boot' in partitions_data: |
|
126 |
+ if 'boot' not in partitions_data: |
|
125 | 127 |
partitions_data['boot'] = partitions_data['root'] |
126 | 128 |
partitions_data['boot_partition_number'] = partitions_data['root_partition_number'] |
127 | 129 |
partitions_data['bootdirectory'] = '/boot/' |
... | ... |
@@ -130,9 +132,9 @@ def partition_disk(disk, partitions): |
130 | 130 |
|
131 | 131 |
return partitions_data |
132 | 132 |
|
133 |
-def replace_string_in_file(filename, search_string, replace_string): |
|
133 |
+def replace_string_in_file(filename, search_string, replace_string): |
|
134 | 134 |
with open(filename, "r") as source: |
135 |
- lines=source.readlines() |
|
135 |
+ lines = source.readlines() |
|
136 | 136 |
|
137 | 137 |
with open(filename, "w") as destination: |
138 | 138 |
for line in lines: |
... | ... |
@@ -145,7 +147,8 @@ def log(type, message): |
145 | 145 |
return retval |
146 | 146 |
|
147 | 147 |
def dump(type, filename): |
148 |
- command = "journalctl -p {0} | grep --line-buffered \"{1}\" > {2}".format(LOG_LEVEL_DESC[type], SIGNATURE, filename) |
|
148 |
+ command = ("journalctl -p {0} | grep --line-buffered \"{1}\" > {2}" |
|
149 |
+ .format(LOG_LEVEL_DESC[type], SIGNATURE, filename)) |
|
149 | 150 |
process = subprocess.Popen([command], shell=True) |
150 | 151 |
retval = process.wait() |
151 | 152 |
return retval |
... | ... |
@@ -153,5 +156,5 @@ def dump(type, filename): |
153 | 153 |
def dump(filename): |
154 | 154 |
command = "journalctl | grep --line-buffered \"{0}\" > {1}".format(SIGNATURE, filename) |
155 | 155 |
process = subprocess.Popen([command], shell=True) |
156 |
- retval = process.wait() |
|
156 |
+ retval = process.wait() |
|
157 | 157 |
return retval |
... | ... |
@@ -5,7 +5,7 @@ import commons |
5 | 5 |
install_phase = commons.POST_INSTALL |
6 | 6 |
enabled = True |
7 | 7 |
|
8 |
-def execute(name, config, root): |
|
8 |
+def execute(config, root): |
|
9 | 9 |
if 'postinstall' not in config: |
10 | 10 |
return |
11 | 11 |
# run the script in the chroot environment |
... | ... |
@@ -13,14 +13,15 @@ def execute(name, config, root): |
13 | 13 |
|
14 | 14 |
script_file = os.path.join(root, 'etc/tmpfiles.d/postinstall.sh') |
15 | 15 |
|
16 |
- with open(script_file, 'wb') as outfile: |
|
16 |
+ with open(script_file, 'wb') as outfile: |
|
17 | 17 |
outfile.write("\n".join(script).encode()) |
18 | 18 |
|
19 |
- os.chmod(script_file, 0o700); |
|
20 |
- with open(commons.KS_POST_INSTALL_LOG_FILE_NAME,"w") as logfile: |
|
21 |
- process = subprocess.Popen(["./mk-run-chroot.sh", '-w', root, "/etc/tmpfiles.d/postinstall.sh"], |
|
22 |
- stdout=logfile,stderr=logfile) |
|
19 |
+ os.chmod(script_file, 0o700) |
|
20 |
+ with open(commons.KS_POST_INSTALL_LOG_FILE_NAME, "w") as logfile: |
|
21 |
+ process = subprocess.Popen(["./mk-run-chroot.sh", '-w', root, |
|
22 |
+ "/etc/tmpfiles.d/postinstall.sh"], |
|
23 |
+ stdout=logfile, stderr=logfile) |
|
23 | 24 |
retval = process.wait() |
24 |
- if retval==0: |
|
25 |
+ if retval == 0: |
|
25 | 26 |
return True |
26 | 27 |
return False |
... | ... |
@@ -1,11 +1,11 @@ |
1 | 1 |
import os |
2 |
-import commons |
|
3 | 2 |
import random |
3 |
+import commons |
|
4 | 4 |
|
5 | 5 |
install_phase = commons.POST_INSTALL |
6 | 6 |
enabled = True |
7 | 7 |
|
8 |
-def execute(name, config, root): |
|
8 |
+def execute(config, root): |
|
9 | 9 |
hostname = config['hostname'] |
10 | 10 |
|
11 | 11 |
hostname_file = os.path.join(root, 'etc/hostname') |
... | ... |
@@ -1,26 +1,25 @@ |
1 | 1 |
import os |
2 |
-import commons |
|
3 | 2 |
import crypt |
4 | 3 |
import random |
5 | 4 |
import string |
5 |
+import commons |
|
6 | 6 |
|
7 | 7 |
install_phase = commons.POST_INSTALL |
8 | 8 |
enabled = True |
9 | 9 |
|
10 |
-def execute(name, config, root): |
|
10 |
+def execute(config, root): |
|
11 | 11 |
shadow_password = config['password'] |
12 | 12 |
|
13 | 13 |
passwd_filename = os.path.join(root, 'etc/passwd') |
14 | 14 |
shadow_filename = os.path.join(root, 'etc/shadow') |
15 |
- |
|
15 |
+ |
|
16 | 16 |
#replace root blank password in passwd file to point to shadow file |
17 |
- commons.replace_string_in_file(passwd_filename, "root::", "root:x:") |
|
17 |
+ commons.replace_string_in_file(passwd_filename, "root::", "root:x:") |
|
18 | 18 |
|
19 | 19 |
if os.path.isfile(shadow_filename) == False: |
20 | 20 |
with open(shadow_filename, "w") as destination: |
21 |
- destination.write("root:"+shadow_password+":") |
|
21 |
+ destination.write("root:" + shadow_password + ":") |
|
22 | 22 |
else: |
23 | 23 |
#add password hash in shadow file |
24 | 24 |
commons.replace_string_in_file(shadow_filename, "root::", "root:"+shadow_password+":") |
25 | 25 |
commons.replace_string_in_file(shadow_filename, "root:x:", "root:"+shadow_password+":") |
26 |
- |
... | ... |
@@ -5,7 +5,7 @@ import commons |
5 | 5 |
install_phase = commons.POST_INSTALL |
6 | 6 |
enabled = True |
7 | 7 |
|
8 |
-def execute(name, config, root): |
|
8 |
+def execute(config, root): |
|
9 | 9 |
if 'public_key' not in config: |
10 | 10 |
return |
11 | 11 |
|
... | ... |
@@ -21,5 +21,6 @@ def execute(name, config, root): |
21 | 21 |
os.chmod(authorized_keys_filename, 0o600) |
22 | 22 |
|
23 | 23 |
# Change the sshd config to allow root login |
24 |
- process = subprocess.Popen(["sed", "-i", "s/^\\s*PermitRootLogin\s\+no/PermitRootLogin yes/", sshd_config_filename]) |
|
24 |
+ process = subprocess.Popen(["sed", "-i", "s/^\\s*PermitRootLogin\s\+no/PermitRootLogin yes/", |
|
25 |
+ sshd_config_filename]) |
|
25 | 26 |
return process.wait() |