[SDC-29] Amdocs OnBoard 1707 initial commit.
[sdc.git] / openecomp-be / tools / scripts / generate-manifest.py
1 #!/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 ASDC/AT&T 1.0
35 ### Date: 13 July 2016
36 ###
37 ##############################################################################
38
39 # import os,sys,getopt,json,re
40 import os, sys, getopt, re
41 from collections import OrderedDict
42 from json import JSONEncoder
43 import json
44
45 VERSION = "1.4"
46 ENV_EXT = ".env"
47 SHELL_EXT = ".sh"
48 YAML_EXT = [".yaml", ".yml"]
49 # VERSION_DELIMITER_PATTERN='_v\d{*}.\d{*}'
50 # VERSION_DELIMITER_PATTERN='_v*.*'
51 #v1.0
52 VERSION_DELIMITER_PATTERN = '_v\d+.\d+'
53 #07_12_2016
54 VERSION_DELIMITER_PATTERN2 = '_\d+-\d+-\d+'
55
56 # types
57 HEAT = "HEAT"
58 HEAT_BASE = "HEAT_BASE"
59 HEAT_NET = "HEAT_NET"
60 HEAT_VOL = "HEAT_VOL"
61 HEAT_ENV = "HEAT_ENV"
62 SHELL = "SHELL"
63 OTHER = "OTHER"
64
65 globalVolumeVal = "VOLUME"
66 globalNetworkVal = "NETWORK"
67 globalBaseVal = "BASE"
68
69
70 def version():
71     return VERSION
72
73
74 def usage():
75     print ('usage: ' + sys.argv[0] + ' [-f|--folder] vendor-heat-directory [-n|--name] manifest-name [-d|--description] manifet-description' )
76
77
78 def header():
79     print ("\nASDC Vendor manifest file generator, version " + version() + "\n")
80
81
82 def 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
91 def 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
133 def 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
150 def 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
228 def 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
255 def jdefault(obj):
256     if hasattr(obj, '__json__'):
257         return obj.__json__()
258     else:
259         return obj.__dict__
260
261
262 class 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
332 class 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
346 class 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
370 class 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
385 def enum(**named_values):
386     return type('Enum', (), named_values)
387
388
389 ################
390
391 def 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
431 if __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:])