Browse code

Adding http repo support for the installation

* Allowing RPMs to be served in a yum repo over http server in the
installation.

Mahmoud Bassiouny authored on 2015/07/08 03:55:53
Showing 5 changed files
... ...
@@ -13,7 +13,8 @@
13 13
                 "elfutils-libelf", "sqlite-autoconf", "nspr", "nss", "popt", "lua", "rpm",
14 14
                 "gptfdisk", "tar", "gzip", "openssl", "python2", "python2-libs", "python-requests",
15 15
                 "pcre", "glib", "parted", "libsigc++", "XML-Parser", "glibmm", "dparted",
16
-                "libgsystem", "ostree","device-mapper","device-mapper-libs","libsepol","libselinux"]
16
+                "libgsystem", "ostree","device-mapper","device-mapper-libs","libsepol","libselinux", 
17
+                "db", "libsolv", "hawkey", "python-hawkey"]
17 18
 }
18 19
 
19 20
 
... ...
@@ -16,7 +16,9 @@ import fnmatch
16 16
 import signal
17 17
 import sys
18 18
 import glob
19
+import urllib
19 20
 import modules.commons
21
+import xml.etree.ElementTree as ET
20 22
 from jsonwrapper import JsonWrapper
21 23
 from progressbar import ProgressBar
22 24
 from window import Window
... ...
@@ -52,7 +54,6 @@ class Installer(object):
52 52
         else:
53 53
             self.output = None
54 54
 
55
-        self.install_factor = 3
56 55
         if self.iso_installer:
57 56
             #initializing windows
58 57
             self.maxy = maxy
... ...
@@ -105,7 +106,7 @@ class Installer(object):
105 105
                 continue
106 106
             if self.iso_installer:
107 107
                 self.progress_bar.update_message('Installing {0}...'.format(rpm['package']))
108
-            return_value = self.install_package(rpm['package'])
108
+            return_value = self.install_package(rpm['filename'])
109 109
             if return_value != 0:
110 110
                 self.exit_gracefully(None, None)
111 111
             if self.iso_installer:
... ...
@@ -135,14 +136,80 @@ class Installer(object):
135 135
 
136 136
         return ActionResult(True, None)
137 137
 
138
+    def download_file(self, url, directory):
139
+        # TODO: Add errors handling
140
+        urlopener = urllib.URLopener()
141
+        urlopener.retrieve(url, os.path.join(directory, os.path.basename(url)))
142
+
143
+    def download_rpms(self):
144
+        repodata_dir = os.path.join(self.photon_root, 'RPMS/repodata')
145
+        process = subprocess.Popen(['mkdir', '-p', repodata_dir], stdout=self.output)
146
+        retval = process.wait()
147
+
148
+        import hawkey
149
+        self.install_factor = 1
150
+        # Load the repo data
151
+        sack = hawkey.Sack()
152
+        
153
+        repomd_filename = "repomd.xml"
154
+        repomd_url = os.path.join(self.rpm_path, "repodata/repomd.xml")
155
+
156
+        self.download_file(repomd_url, repodata_dir)
157
+
158
+        # parse to the xml to get the primary and files list
159
+        tree = ET.parse(os.path.join(repodata_dir, repomd_filename))
160
+        # TODO: Get the namespace dynamically from the xml file
161
+        ns = {'ns': 'http://linux.duke.edu/metadata/repo'}
162
+
163
+        primary_location = tree.find("./ns:data[@type='primary']/ns:location", ns).get("href");
164
+        filelists_location = tree.find("./ns:data[@type='filelists']/ns:location", ns).get("href");
165
+        primary_filename = os.path.basename(primary_location);
166
+        filelists_filename = os.path.basename(filelists_location);
167
+
168
+        self.download_file(os.path.join(self.rpm_path, primary_location), repodata_dir)
169
+        self.download_file(os.path.join(self.rpm_path, filelists_location), repodata_dir)
170
+        
171
+        repo = hawkey.Repo("installrepo")
172
+        repo.repomd_fn = os.path.join(repodata_dir, repomd_filename)
173
+        repo.primary_fn = os.path.join(repodata_dir, primary_filename)
174
+        repo.filelists_fn = os.path.join(repodata_dir, filelists_filename)
175
+        
176
+        sack.load_yum_repo(repo, load_filelists=True)
177
+
178
+        progressbar_num_items = 0
179
+        self.rpms_tobeinstalled = []
180
+        selected_packages = self.install_config['packages']
181
+        for package in selected_packages:
182
+            # Locate the package
183
+            q = hawkey.Query(sack).filter(name=package)
184
+            if (len(q) > 0):
185
+                progressbar_num_items +=  q[0].size + q[0].size * self.install_factor
186
+                self.rpms_tobeinstalled.append({'package': package, 'size': q[0].size, 'location': q[0].location, 'filename': os.path.basename(q[0].location)})
187
+            else:
188
+                print >> sys.stderr, "Package %s not found in the repo" % package
189
+                #self.exit_gracefully(None, None)
190
+
191
+        self.progress_bar.update_num_items(progressbar_num_items)
192
+
193
+        # Download the rpms
194
+        for rpm in self.rpms_tobeinstalled:
195
+            message = 'Downloading {0}...'.format(rpm['filename'])
196
+            self.progress_bar.update_message(message)
197
+            self.download_file(os.path.join(self.rpm_path, rpm['location']), os.path.join(self.photon_root, "RPMS"))
198
+            self.progress_bar.increment(rpm['size'])
199
+        
200
+        # update the rpms path
201
+        self.rpm_path = os.path.join(self.photon_root, "RPMS")
202
+
138 203
     def copy_rpms(self):
139 204
         # prepare the RPMs list
205
+        self.install_factor = 3
140 206
         rpms = []
141 207
         for root, dirs, files in os.walk(self.rpm_path):
142 208
             for name in files:
143 209
                 file = os.path.join(root, name)
144 210
                 size = os.path.getsize(file)
145
-                rpms.append({'name': name, 'path': file, 'size': size})
211
+                rpms.append({'filename': name, 'path': file, 'size': size})
146 212
 
147 213
         progressbar_num_items = 0
148 214
         self.rpms_tobeinstalled = []
... ...
@@ -150,20 +217,20 @@ class Installer(object):
150 150
         for package in selected_packages:
151 151
             pattern = package + '-[0-9]*.rpm'
152 152
             for rpm in rpms:
153
-                if fnmatch.fnmatch(rpm['name'], pattern):
153
+                if fnmatch.fnmatch(rpm['filename'], pattern):
154 154
                     rpm['package'] = package
155 155
                     self.rpms_tobeinstalled.append(rpm)
156 156
                     progressbar_num_items += rpm['size'] + rpm['size'] * self.install_factor
157 157
                     break
158 158
 
159
-        process = subprocess.Popen(['mkdir', '-p', self.photon_root + '/RPMS'], stdout=self.output)
160
-        retval = process.wait()
161
-
162 159
         if self.iso_installer:
163 160
             self.progress_bar.update_num_items(progressbar_num_items)
164 161
 
165 162
         # Copy the rpms
166 163
         for rpm in self.rpms_tobeinstalled:
164
+            if self.iso_installer:
165
+                message = 'Copying {0}...'.format(rpm['filename'])
166
+                self.progress_bar.update_message(message)
167 167
             shutil.copy(rpm['path'], self.photon_root + '/RPMS/')
168 168
             if self.iso_installer:
169 169
                 self.progress_bar.increment(rpm['size'])
... ...
@@ -177,7 +244,14 @@ class Installer(object):
177 177
         process = subprocess.Popen(['cp', '-r', "../installer", self.photon_root], stdout=self.output)
178 178
         retval = process.wait()
179 179
 
180
-        self.copy_rpms()
180
+        # Create the rpms directory
181
+        process = subprocess.Popen(['mkdir', '-p', self.photon_root + '/RPMS'], stdout=self.output)
182
+        retval = process.wait()
183
+
184
+        if self.rpm_path.startswith("http://"):
185
+            self.download_rpms()
186
+        else:
187
+            self.copy_rpms()
181 188
 
182 189
     def initialize_system(self):
183 190
         #Setup the disk
... ...
@@ -25,8 +25,8 @@ from license import License
25 25
 
26 26
 class IsoInstaller(object):
27 27
 
28
-    def get_config(self, path, cd_path):
29
-        if path.startswith("http"):
28
+    def get_config(self, path):
29
+        if path.startswith("http://"):
30 30
             # Do 3 trials to get the kick start
31 31
             # TODO: make sure the installer run after network is up
32 32
             for x in range(0,3):
... ...
@@ -48,10 +48,15 @@ class IsoInstaller(object):
48 48
             raise Exception(err_msg)
49 49
         else:
50 50
             if path.startswith("cdrom:/"):
51
-                path = os.path.join(cd_path, path.replace("cdrom:/", "", 1))
51
+                self.mount_RPMS_cd()
52
+                path = os.path.join(self.cd_path, path.replace("cdrom:/", "", 1))
52 53
             return (JsonWrapper(path)).read();
53 54
 
54 55
     def mount_RPMS_cd(self):
56
+        # check if the cd is already mounted
57
+        if self.cd_path:
58
+            return
59
+
55 60
         # Mount the cd to get the RPMS
56 61
         process = subprocess.Popen(['mkdir', '-p', '/mnt/cdrom'])
57 62
         retval = process.wait()
... ...
@@ -61,7 +66,8 @@ class IsoInstaller(object):
61 61
             process = subprocess.Popen(['mount', '/dev/cdrom', '/mnt/cdrom'])
62 62
             retval = process.wait()
63 63
             if retval == 0:
64
-                return "/mnt/cdrom"
64
+                self.cd_path = "/mnt/cdrom"
65
+                return
65 66
             print "Failed to mount the cd, retry in a second"
66 67
             time.sleep(1)
67 68
         print "Failed to mount the cd, exiting the installer, check the logs for more details"
... ...
@@ -83,32 +89,40 @@ class IsoInstaller(object):
83 83
 
84 84
         curses.curs_set(0)
85 85
 
86
-        self.install_config = {'iso_system': False}
86
+        self.cd_path = None;
87 87
 
88
-        # Mount the cd for the RPM, tools, and may be the ks
89
-        cd_path = self.mount_RPMS_cd()
88
+        self.install_config = {'iso_system': False}
90 89
         
91
-        # check the kickstart params
92
-        ks_config = None
93 90
         kernel_params = subprocess.check_output(['cat', '/proc/cmdline'])
91
+
92
+        # check the kickstart param
93
+        ks_config = None
94 94
         m = re.match(r".*ks=(\S+)\s*.*\s*", kernel_params)
95 95
         if m != None:
96
-            ks_config = self.get_config(m.group(1), cd_path)
97
-
98
-        license_agreement = License(self.maxy, self.maxx)
99
-        select_disk = SelectDisk(self.maxy, self.maxx, self.install_config)
100
-        package_selector = PackageSelector(self.maxy, self.maxx, self.install_config, options_file)
101
-        hostname_reader = WindowStringReader(self.maxy, self.maxx, 10, 70, False,  'Choose the hostname for your system',
102
-            'Hostname:', 
103
-            2, self.install_config)
104
-        root_password_reader = WindowStringReader(self.maxy, self.maxx, 10, 70, True,  'Set up root password',
105
-            'Root password:', 
106
-            2, self.install_config)
107
-        installer = Installer(self.install_config, self.maxy, self.maxx, True, rpm_path=os.path.join(cd_path, "RPMS"), log_path="/var/log", ks_config=ks_config)
96
+            ks_config = self.get_config(m.group(1))
97
+
98
+        # check for the repo param
99
+        m = re.match(r".*repo=(\S+)\s*.*\s*", kernel_params)
100
+        if m != None:
101
+            rpm_path = m.group(1)
102
+        else:
103
+            # the rpms should be in the cd
104
+            self.mount_RPMS_cd()
105
+            rpm_path=os.path.join(self.cd_path, "RPMS")
108 106
 
109 107
         # This represents the installer screen, the bool indicated if I can go back to this window or not
110 108
         items = []
111 109
         if not ks_config:
110
+            license_agreement = License(self.maxy, self.maxx)
111
+            select_disk = SelectDisk(self.maxy, self.maxx, self.install_config)
112
+            package_selector = PackageSelector(self.maxy, self.maxx, self.install_config, options_file)
113
+            hostname_reader = WindowStringReader(self.maxy, self.maxx, 10, 70, False,  'Choose the hostname for your system',
114
+                'Hostname:', 
115
+                2, self.install_config)
116
+            root_password_reader = WindowStringReader(self.maxy, self.maxx, 10, 70, True,  'Set up root password',
117
+                'Root password:', 
118
+                2, self.install_config)
119
+            
112 120
             items = items + [
113 121
                     (license_agreement.display, False),
114 122
                     (select_disk.display, True),
... ...
@@ -116,6 +130,7 @@ class IsoInstaller(object):
116 116
                     (hostname_reader.get_user_string, True),
117 117
                     (root_password_reader.get_user_string, True),
118 118
                  ]
119
+        installer = Installer(self.install_config, self.maxy, self.maxx, True, rpm_path=rpm_path, log_path="/var/log", ks_config=ks_config)
119 120
         items = items + [(installer.install, False)]
120 121
 
121 122
         index = 0
... ...
@@ -27,7 +27,7 @@ fi
27 27
 [ ${EUID} -eq 0 ] 	|| fail "${PRGNAME}: Need to be root user: FAILURE"
28 28
 
29 29
 RPMPKG=""
30
-RPMPKG=$(find ${RPMROOT} -name "$1-[0-9]*.rpm" -print)
30
+RPMPKG=$(find ${RPMROOT} -name "$1" -print)
31 31
 # TODO: sometimes we catch several items into RPMPKG.
32 32
 # In case we have several releases in rpm cache. Need to handle that.
33 33
 [ -z $RPMPKG ] && fail "installation error: rpm package not found\n"
... ...
@@ -8,9 +8,9 @@ enabled = True
8 8
 def execute(name, ks_config, config, root):
9 9
 
10 10
     if ks_config:
11
-        package_list_micro = JsonWrapper("packages_micro.json").read()
12
-        package_list_minimal = JsonWrapper("packages_minimal.json").read()
13
-        package_list_full = JsonWrapper("packages_full.json").read()
11
+        package_list_micro = JsonWrapper("data/packages_micro.json").read()
12
+        package_list_minimal = JsonWrapper("data/packages_minimal.json").read()
13
+        package_list_full = JsonWrapper("data/packages_full.json").read()
14 14
 
15 15
         if ks_config['type'] == 'micro':
16 16
             packages = package_list_micro["packages"]