blob: 87968e0f89b29f24accef56ad5e79594ee1ca764 [file] [log] [blame]
Michael Landof5f13c42017-02-19 12:35:04 +02001#!/usr/bin/python
2
3##############################################################################
4###
5### generate-manifest.py
6###
7### A Vendor utility to generate a valid heat zip manifest file for the AT&T onboarding.
8###
9### Usage:
10###
11### generate-manifest.py [-f|--folder] vendor-heat-directory [-n|--name] manifest-name [-d|--description] manifet-description
12###
13### For example:
14###
15### ./generate-manifest.py --folder ./vota --name vOTA --description "HOT template to create vOTA server"
16###
17### Help:
18### The script is doing the following:
19### 1) Split the files into different types
20### a. .env files
21### b. Network files (anything containing the string network)
22### c. Volume files (anything containing the string volume)
23### d. Top level Heat files
24### e. Other types
25### 2) Match env files to heat files – looking for same name ignoring suffix and extension
26### 3) Match Network childs
27### a. Look for Top level heats which name is a substring of the name of the Network heat name.
28### 4) Match Volume childs
29### a. Look for Top level heats which name is a substring of the name of the Volume heat name.
30### 5) Generate the JSON file from the above
31###
32###
33### Author: Avi Ziv
34### Version 1.4 for OPENECOMP 1.0
35### Date: 13 July 2016 (c) OPENECOMP
36###
37##############################################################################
38
39# import os,sys,getopt,json,re
40import os, sys, getopt, re
41from collections import OrderedDict
42from json import JSONEncoder
43import json
44
45VERSION = "1.4"
46ENV_EXT = ".env"
47SHELL_EXT = ".sh"
48YAML_EXT = [".yaml", ".yml"]
49# VERSION_DELIMITER_PATTERN='_v\d{*}.\d{*}'
50# VERSION_DELIMITER_PATTERN='_v*.*'
51#v1.0
52VERSION_DELIMITER_PATTERN = '_v\d+.\d+'
53#07_12_2016
54VERSION_DELIMITER_PATTERN2 = '_\d+-\d+-\d+'
55
56# types
57HEAT = "HEAT"
58HEAT_BASE = "HEAT_BASE"
59HEAT_NET = "HEAT_NET"
60HEAT_VOL = "HEAT_VOL"
61HEAT_ENV = "HEAT_ENV"
62SHELL = "SHELL"
63OTHER = "OTHER"
64
65globalVolumeVal = "VOLUME"
66globalNetworkVal = "NETWORK"
67globalBaseVal = "BASE"
68
69
70def version():
71 return VERSION
72
73
74def usage():
75 print ('usage: ' + sys.argv[0] + ' [-f|--folder] vendor-heat-directory [-n|--name] manifest-name [-d|--description] manifet-description' )
76
77
78def header():
79 print ("\nASDC Vendor manifest file generator, version " + version() + "\n")
80
81
82def getEnvVariables(value, defaultValue):
83 try:
84 eVal = os.environ[value]
85 return eVal
86 except KeyError:
87 print ("Missing ${" + value + "} envirunment variable. Using default value: " + defaultValue)
88 return defaultValue
89
90
91def getF(listFiles):
92 print ("Analyzing files ...")
93 foundABase = False
94 files = listFiles
95 jsons = {}
96 lOfEnvs = {}
97 lOfVolumes = {}
98 lOfNetworks = {}
99 lOfHeats = {}
100 lOfShels = {}
101 lOfArtifacts = {}
102
103 for f in files:
104 fullFilename = f[1]
105 fObj = ManifestFileInfo(fullFilename)
106 if fObj.isEnv():
107 lOfEnvs[fObj.file_name] = fObj
108 elif fObj.isShell():
109 lOfShels[fObj.file_name] = fObj
110 elif fObj.isVolume():
111 lOfVolumes[fObj.file_name] = fObj
112 elif fObj.isNetwork():
113 lOfNetworks[fObj.file_name] = fObj
114 elif (fObj.isYaml() and not fObj.isBase()):
115 lOfHeats[fObj.file_name] = fObj
116 elif fObj.isArtifact():
117 lOfArtifacts[fObj.file_name] = fObj
118 elif (fObj.isBase() and fObj.isYaml()):
119 foundABase = True
120 lOfHeats[fObj.file_name] = fObj
121
122 jsons['heats'] = lOfHeats
123 jsons['envs'] = lOfEnvs
124 jsons['shells'] = lOfShels
125 jsons['volumes'] = lOfVolumes
126 jsons['networks'] = lOfNetworks
127 jsons['artifacts'] = lOfArtifacts
128
129 if not foundABase:
130 print (">>> Warning: No Base was found")
131 return jsons
132
133def loadFilesToList(folder):
134 print ("Analyzing files in folder: << " + folder + " >>")
135 files = os.listdir(folder)
136 listOfFiles = []
137 for f in files:
138 if os.path.isdir(os.path.join(folder, f)):
139 ConsoleLogger.warning("Sub folders are ignored by this script, you may want to remove it before archiving")
140 continue
141
142 filename, file_extension = os.path.splitext(f)
143 if filename == 'MANIFEST':
144 ConsoleLogger.warning("Your folder already contains a manifest file that will be overridden")
145 continue
146 listOfFiles.append([filename, f])
147 return listOfFiles
148
149
150def make(files):
151 flist = []
152 dEnvs = {}
153 dEnvs = files['envs']
154 dHeats = files['heats']
155 dNetworks = files['networks']
156 dVolumes = files['volumes']
157 dArtifacts = files['artifacts']
158 dShells = files['shells']
159
160 env_items = dEnvs.items()
161 for fileItem in env_items:
162 env_name = fileItem[1].file_name
163 env_base = fileItem[1].base_file_name
164 if env_name in dHeats:
165 dHeats[env_name].add(fileItem[1])
166 continue
167
168 if env_name in dNetworks.items():
169 dNetworks[env_name].add(fileItem[1])
170 continue
171
172 if env_name in dVolumes.items():
173 dVolumes[env_name[0]].add(env_name[1])
174 continue
175
176 for fName in dHeats:
177 heat_base = dHeats[fName].base_file_name
178 if env_base in heat_base:
179 dHeats[fName].add(dEnvs[env_name])
180 break
181 else:
182 for fName in dNetworks:
183 net_base = dNetworks[fName].base_file_name
184 if env_base in net_base:
185 dNetworks[fName].add(dEnvs[env_name])
186 break
187 else:
188 for fName in dVolumes:
189 vol_base = dVolumes[fName].base_file_name
190 if env_base in vol_base:
191 dVolumes[fName].add(dEnvs[env_name])
192 break
193
194 else:
195 flist.append(dEnvs[env_name])
196
197 for fName in dVolumes:
198 vol_base = dVolumes[fName].base_file_name
199 for hfName in dHeats:
200 heat_base = dHeats[hfName].base_file_name
201 if heat_base in vol_base:
202 dHeats[hfName].add(dVolumes[fName])
203 break
204 else:
205 flist.append(dVolumes[fName])
206
207 for fName in dNetworks:
208 net_base = dNetworks[fName].base_file_name
209 for hfName in dHeats:
210 heat_base = dHeats[hfName].base_file_name
211 if heat_base in net_base:
212 dHeats[hfName].add(dNetworks[fName])
213 break
214 else:
215 flist.append(dNetworks[fName])
216
217 for fName in dHeats:
218 flist.append(dHeats[fName])
219 for fName in dShells:
220 flist.append(dShells[fName])
221 for fName in dArtifacts:
222 flist.append(dArtifacts[fName])
223
224 print ("\n------------------------------------------------------------\n")
225 return flist
226
227
228def generate(folder, name, description):
229 print ("Checking envirunment variables ...")
230 global globalVolumeVal
231 globalVolumeVal = getEnvVariables("VOLUME", globalVolumeVal)
232
233 global globalNetworkVal
234 globalNetworkVal = getEnvVariables("NETWORK", globalNetworkVal)
235
236 global globalBaseVal
237 globalBaseVal = getEnvVariables("BASE", globalBaseVal)
238
239 YamlTabCleaner(folder).cleanYamlTabs()
240
241 print ("Generating manifest file ...")
242 jsons = getF(loadFilesToList(folder))
243
244 lFiles = make(jsons)
245 manifest = Manifest(name, description, '1.0', lFiles)
246 output_json = json.dumps(manifest, default=jdefault, indent=4, sort_keys=False)
247
248 f = open(os.path.join(folder, 'MANIFEST.json'), 'w')
249 f.write(output_json)
250 print("MANIFEST file created")
251
252
253################
254
255def jdefault(obj):
256 if hasattr(obj, '__json__'):
257 return obj.__json__()
258 else:
259 return obj.__dict__
260
261
262class ManifestFileInfo(object):
263 def __init__(self, filename):
264 self.name = filename
265 self.base = 'false'
266 self.data = []
267 self.file_name, self.file_extension = os.path.splitext(filename)
268 self.base_file_name = re.sub(VERSION_DELIMITER_PATTERN, '', self.file_name)
269 self.base_file_name = re.sub(VERSION_DELIMITER_PATTERN2, '', self.base_file_name)
270
271 if self.isEnv():
272 self.heat_type = Types.ENV
273 elif self.isShell():
274 self.heat_type = Types.SHELL
275 elif self.isVolume():
276 self.heat_type = Types.VOL
277 elif self.isNetwork():
278 self.heat_type = Types.NET
279 elif self.isYaml() and not self.isBase():
280 self.heat_type = Types.HEAT
281 elif self.isArtifact():
282 self.heat_type = Types.OTHER
283 elif (self.isBase() and self.isYaml()):
284 self.heat_type = Types.HEAT
285 self.base = 'true'
286
287 def set(self, data):
288 self.data = data
289
290 def add(self, item):
291 self.data.append(item)
292
293 def isYaml(self):
294 return any(val in self.file_extension.lower() for val in YAML_EXT)
295
296 def isEnv(self):
297 return self.file_extension.lower() == ENV_EXT.lower()
298
299 def isShell(self):
300 return self.file_extension.lower() == SHELL_EXT.lower()
301
302 def isVolume(self):
303 res = globalVolumeVal.lower() in self.file_name.lower()
304 return res
305
306 def isNetwork(self):
307 res = globalNetworkVal.lower() in self.file_name.lower()
308 return res
309
310 def isBase(self):
311 res = globalBaseVal.lower() in self.file_name.lower()
312 return res
313
314 def isArtifact(self):
315 return (not self.isBase() and not self.isVolume() and not self.isNetwork() and not self.isEnv())
316
317 def isHEAT(self):
318 return ((self.heat_type == Types.HEAT) | (self.heat_type == Types.BASE) | (self.heat_type == Types.NET) | (
319 self.heat_type == Types.VOL))
320
321 def __json__(self):
322 dict = OrderedDict(
323 [('file', self.name), ('type', self.heat_type)])
324 if self.isHEAT():
325 dict['isBase'] = self.base
326 if self.data != []:
327 dict['data'] = self.data
328
329 return dict
330
331
332class Manifest(object):
333 def __init__(self, name, description, version, data):
334 self.name = name
335 self.description = description
336 self.version = version
337 self.data = data
338
339 def add(self, data):
340 self.data.append(data)
341
342 def __json__(self):
343 return OrderedDict([('name', self.name), ('description', self.description), ('data', self.data)])
344
345
346class YamlTabCleaner(object):
347 def __init__(self, folder):
348 self.folder = folder
349
350 def replaceTabs(self, sourceFile, targetFile):
351 with open(sourceFile, "rt") as fin:
352 if '\t' in fin.read():
353 print("\'tab\' character was found in the file: " + sourceFile + "\na clean version of the file can be found under \'clean\' folder")
354 target = os.path.dirname(targetFile)
355 if not os.path.exists(target):
356 os.makedirs(target)
357 fin.seek(0)
358 with open(targetFile, "wt") as fout:
359 for line in fin:
360 fout.write(line.replace('\t', ' '))
361
362 def cleanYamlTabs(self):
363 included_extenstions = ['yml', 'yaml']
364 files = [fn for fn in os.listdir(self.folder)
365 if any(fn.endswith(ext) for ext in included_extenstions)]
366 target = os.path.join(self.folder, "clean")
367 for file in files:
368 self.replaceTabs(os.path.join(self.folder, file), os.path.join(target, file))
369
370class ConsoleLogger(object):
371 @classmethod
372 def error(cls, message):
373 print(">>> Error: " + message)
374
375 @classmethod
376 def warning(cls, message):
377 print(">>> Warning: " + message)
378
379
380 @classmethod
381 def info(cls, message):
382 print(">>> Info: " + message)
383
384
385def enum(**named_values):
386 return type('Enum', (), named_values)
387
388
389################
390
391def main(argv):
392 action = ''
393 folderName = '.'
394 name = ''
395 description = ''
396 version = ''
397
398 try:
399 opts, args = getopt.getopt(argv, "h:f:n:d", ["folder=", "name=", "description=", ])
400 except getopt.GetoptError as err:
401 # print help information and exit:
402 print ('>>>>' + str(err))
403 usage()
404 sys.exit(2)
405 for opt, arg in opts:
406 if opt == '-h':
407 usage()
408 sys.exit()
409 elif opt in ('-f', '--folder'):
410 action = 'generate'
411 if not arg:
412 print ("Error: missing heat files directory")
413 usage()
414 sys.exit(2)
415 else:
416 folderName = arg
417 elif opt in ('-n', '--name'):
418 name = arg
419 elif opt in ('-d', '--description'):
420 description = arg
421 else:
422 usage()
423
424 if action == 'generate':
425 generate(folderName, name, description)
426 sys.exit()
427 else:
428 usage()
429
430
431if __name__ == "__main__":
432 header()
433 Types = enum(HEAT='HEAT', BASE='HEAT_BASE', NET='HEAT_NET', VOL='HEAT_VOL', ENV='HEAT_ENV', SHELL='SHELL',
434 OTHER='OTHER')
435 main(sys.argv[1:])