Skip to content

Commit 729eb07

Browse files
authored
Add script to upload packages to web server (#13545)
- Why I did it The flow of SONiC reproducible build assumes that web packages (retrieved by curl and wget) are located in a single web file. server. Currently there is no way to easily download the packages from the various file servers where they are currently deployed. In this change we offer a utility to download all relevant packages and upload them to a specific file server. - How I did it We implemented python script that parse the various version files (generated by SONIC reproducible build compilation) and identify all relevant packages. Later the script downloads the file and then upload them to the destination server pointed by the user. - How to verify it Script is running and was verified manually Which release branch to backport (provide reason below if selected) Feature will be added to master only Signed-off-by: oreiss <[email protected]>
1 parent d15f520 commit 729eb07

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed

scripts/populate_file_web_server.py

+295
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import os
5+
import time
6+
import argparse
7+
from http import HTTPStatus
8+
try:
9+
import requests
10+
except ImportError:
11+
print("requests module is not installed. script will fail to execute")
12+
13+
#debug print level
14+
PRINT_LEVEL_ERROR = "err"
15+
PRINT_LEVEL_WARN = "warn"
16+
PRINT_LEVEL_INFO = "info"
17+
PRINT_LEVEL_VERBOSE = "verbose"
18+
19+
PRINT_LEVEL_LUT = {PRINT_LEVEL_ERROR : 1,
20+
PRINT_LEVEL_WARN : 2,
21+
PRINT_LEVEL_INFO : 3,
22+
PRINT_LEVEL_VERBOSE : 4 }
23+
24+
#return code
25+
RET_CODE_SUCCESS = 0
26+
RET_CODE_CANNOT_CREATE_FILE = -1
27+
RET_CODE_CANNOT_OPEN_FILE = -2
28+
RET_CODE_HTTP_SERVER_ERROR = -3
29+
RET_CODE_CANNOT_WRITE_FILE = -4
30+
31+
#constants
32+
RESOURCES_FILE_NAME = 'versions-web'
33+
EXCLUDE_DIRECTORES = ['fsroot', 'target']
34+
HASH_SEPARATOR = '-'
35+
DEFAULT_INVALID_INPUT = 'none'
36+
37+
# global variables
38+
g_current_print_level = PRINT_LEVEL_INFO
39+
40+
#Script debug features (disabled by default)
41+
g_delete_resources_in_cache = True
42+
43+
44+
# global Classes
45+
class Resource:
46+
def __init__(self, line, file):
47+
self.file = file
48+
temp=line.split("==")
49+
assert(2==len(temp))
50+
self.url=temp[0].strip()
51+
self.hash=temp[1].strip()
52+
temp=self.url.split("/")
53+
assert(len(temp)>0)
54+
self.name=temp[len(temp)-1]
55+
#handle special scenarios
56+
if 0 != self.name.count('?') == True:
57+
temp = self.name.split("?")
58+
self.name = temp[0]
59+
60+
def get_unique_name(self):
61+
return self.name + HASH_SEPARATOR + self.hash
62+
63+
def get_url(self):
64+
return self.url
65+
66+
def __str__(self):
67+
ret_val = "Resource name: " + self.name + "\n"
68+
ret_val += "File: " + self.file + "\n"
69+
ret_val += "Hash: " + self.hash + "\n"
70+
ret_val += "Full URL: " + self.url
71+
return ret_val
72+
73+
# Helper functions
74+
75+
def print_msg(print_level, msg, print_in_place=False):
76+
if PRINT_LEVEL_LUT[g_current_print_level] >= PRINT_LEVEL_LUT[print_level]:
77+
if True == print_in_place:
78+
print(msg, end='\r')
79+
else:
80+
print(msg)
81+
82+
def create_dir_if_not_exist(dir):
83+
if not os.path.exists(dir):
84+
try:
85+
os.makedirs(dir)
86+
except:
87+
print_msg(PRINT_LEVEL_WARN, "Cannot create directory " + dir)
88+
89+
def delete_file_if_exist(file):
90+
if os.path.exists(file):
91+
try:
92+
os.remove(file)
93+
except:
94+
print_msg(PRINT_LEVEL_WARN, "Cannot delete " + file)
95+
96+
# Logic functions
97+
98+
def generate_output_file(resources, dest_url_valid, dest_url, output_file_name):
99+
try:
100+
with open(output_file_name, 'w') as f:
101+
for unique_name in resources.keys():
102+
resource = resources[unique_name]
103+
if True == dest_url_valid:
104+
line = dest_url
105+
else:
106+
line = resource.get_url()
107+
if line[-1] != '/':
108+
line += '/'
109+
line += resource.name + "==" + resource.hash
110+
f.write(line + '\n')
111+
except:
112+
print_msg(PRINT_LEVEL_WARN, output_file_name + " cannot be created")
113+
return RET_CODE_CANNOT_CREATE_FILE
114+
115+
return RET_CODE_SUCCESS
116+
117+
def upload_resource_to_server(resource_path, resource_name, user, key, server_url):
118+
url_full_path = server_url + "/" + resource_name
119+
120+
try:
121+
f = open(resource_path, 'rb')
122+
except:
123+
err_print("Cannot open " + resource_path)
124+
return RET_CODE_CANNOT_OPEN_FILE
125+
126+
headers = {'Content-type': 'application', 'Slug': resource_name}
127+
response = requests.put(url_full_path, data=f,
128+
headers=headers, auth=(user, key))
129+
130+
f.close()
131+
132+
if response.status_code != HTTPStatus.CREATED.value:
133+
err_print(f"HTTP request returned status code {response.status_code}, expected {HTTPStatus.CREATED.value}")
134+
return RET_CODE_HTTP_SERVER_ERROR
135+
136+
# JSON response empty only when status code is 204
137+
reported_md5 = response.json().get('checksums', {}).get('md5')
138+
file_md5 = resource_name.split(HASH_SEPARATOR)[-1]
139+
140+
# Check if server reports checksum, if so compare reported sum and the one
141+
# specified in filename
142+
if reported_md5 != None and reported_md5 != file_md5:
143+
print_msg(PRINT_LEVEL_WARN, f"Server reported file's chsum {reported_md5}, expected {file_md5}")
144+
145+
146+
return RET_CODE_SUCCESS
147+
148+
def download_external_resouce(resource, cache_path):
149+
resource_path_in_cache = cache_path + os.sep + resource.get_unique_name()
150+
151+
r = requests.get(resource.get_url(), allow_redirects=True)
152+
153+
try:
154+
f = open(resource_path_in_cache, 'wb')
155+
f.write(r.content)
156+
f.close()
157+
except:
158+
print_msg(PRINT_LEVEL_ERROR, "Cannot write " + resource_path_in_cache + " to cache")
159+
resource_path_in_cache = "" #report error
160+
161+
return resource_path_in_cache
162+
163+
def get_resources_list(resource_files_list):
164+
resource_list = list()
165+
166+
for file_name in resource_files_list:
167+
try:
168+
with open(file_name, 'r') as f:
169+
for line in f:
170+
resource_list.append(Resource(line, file_name))
171+
except:
172+
print_msg(PRINT_LEVEL_WARN, file_name + " cannot be opened")
173+
174+
return resource_list
175+
176+
def filter_out_dir(subdir):
177+
ret_val = False
178+
179+
for exclude in EXCLUDE_DIRECTORES:
180+
if exclude in subdir.split(os.sep):
181+
ret_val = True
182+
break
183+
184+
return ret_val
185+
186+
def get_resource_files_list(serach_path):
187+
resource_files_list = list()
188+
189+
for subdir, dirs, files in os.walk(serach_path):
190+
for file in files:
191+
if False == filter_out_dir(subdir) and RESOURCES_FILE_NAME == file:
192+
file_full_path = os.path.join(subdir, file)
193+
print_msg(PRINT_LEVEL_VERBOSE, "Found resource file :" + file_full_path)
194+
resource_files_list.append(file_full_path)
195+
196+
return resource_files_list
197+
198+
def parse_args():
199+
parser = argparse.ArgumentParser(description='Various pre-steps for build compilation')
200+
201+
parser.add_argument('-s', '--source', default=".",
202+
help='Search path for ' + RESOURCES_FILE_NAME + ' files')
203+
204+
parser.add_argument('-c', '--cache', default="." + os.sep + "tmp",
205+
help='Path to cache for storing content before uploading to server')
206+
207+
parser.add_argument('-p', '--print', default=PRINT_LEVEL_INFO,
208+
choices=[PRINT_LEVEL_ERROR, PRINT_LEVEL_WARN, PRINT_LEVEL_INFO, PRINT_LEVEL_VERBOSE],
209+
help='Print level verbosity')
210+
211+
parser.add_argument('-o', '--output', default=DEFAULT_INVALID_INPUT,
212+
help='Output file name to hold the list of packages')
213+
214+
parser.add_argument('-u', '--user', default=DEFAULT_INVALID_INPUT,
215+
help='User for server authentication')
216+
217+
parser.add_argument('-k', '--key', default=DEFAULT_INVALID_INPUT,
218+
help='API key server authentication')
219+
220+
parser.add_argument('-d', '--dest', default=DEFAULT_INVALID_INPUT,
221+
help='URL for destination web file server')
222+
223+
return parser.parse_args()
224+
225+
def main():
226+
global g_current_print_level
227+
ret_val = RET_CODE_SUCCESS
228+
resource_counter = 0.0
229+
resource_dict = dict()
230+
231+
args = parse_args()
232+
233+
g_current_print_level = args.print
234+
235+
resource_files_list = get_resource_files_list(args.source)
236+
237+
resource_list = get_resources_list(resource_files_list)
238+
239+
#remove duplications
240+
for resource in resource_list:
241+
unique_name = resource.get_unique_name()
242+
if not unique_name in resource_dict.keys():
243+
resource_dict[unique_name] = resource
244+
245+
print_msg(PRINT_LEVEL_INFO, "Found " + str(len(resource_files_list)) + " version files and " + str(len(resource_dict.keys())) + " unique resources")
246+
247+
if args.dest != DEFAULT_INVALID_INPUT:
248+
upload_files_to_server = True
249+
print_msg(PRINT_LEVEL_INFO, "Upload files to URL - " + args.dest)
250+
else:
251+
upload_files_to_server = False
252+
print_msg(PRINT_LEVEL_INFO, "Skipping files upload to server")
253+
254+
#create cache directory if not exist
255+
create_dir_if_not_exist(args.cache)
256+
257+
#download content to cache and then upload to web server
258+
for unique_name in resource_dict.keys():
259+
260+
resource = resource_dict[unique_name]
261+
262+
print_msg(PRINT_LEVEL_VERBOSE, resource)
263+
264+
resource_counter += 1.0
265+
266+
#download content to cache
267+
file_in_cache = download_external_resouce(resource, args.cache)
268+
269+
if "" == file_in_cache:
270+
return RET_CODE_CANNOT_WRITE_FILE
271+
272+
if True == upload_files_to_server:
273+
#upload content to web server
274+
ret_val = upload_resource_to_server(file_in_cache, unique_name, args.user, args.key, args.dest)
275+
if ret_val != RET_CODE_SUCCESS:
276+
return ret_val
277+
278+
if True == g_delete_resources_in_cache:
279+
delete_file_if_exist(file_in_cache)
280+
281+
print_msg(PRINT_LEVEL_INFO, "Downloading Data. Progress " + str(int(100.0*resource_counter/len(resource_dict.keys()))) + "%", True) #print progress bar
282+
283+
# generate version output file as needed
284+
if args.output != DEFAULT_INVALID_INPUT:
285+
ret_val = generate_output_file(resource_dict, upload_files_to_server, args.dest, args.output)
286+
print_msg(PRINT_LEVEL_INFO, "Generate output file " + args.output)
287+
288+
return ret_val
289+
290+
# Entry function
291+
if __name__ == '__main__':
292+
293+
ret_val = main()
294+
295+
sys.exit(ret_val)

0 commit comments

Comments
 (0)