github.com/kchristidis/fabric@v1.0.4-0.20171028114726-837acd08cde1/bddtests/steps/docgen.py (about) 1 # Copyright IBM Corp. 2016 All Rights Reserved. 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 # 15 16 from StringIO import StringIO 17 from itertools import chain 18 from google.protobuf.message import Message 19 20 from b3j0f.aop import weave, unweave, is_intercepted, weave_on 21 22 from jinja2 import Environment, PackageLoader, select_autoescape, FileSystemLoader, Template 23 env = Environment( 24 loader=FileSystemLoader(searchpath="templates"), 25 autoescape=select_autoescape(['html', 'xml']), 26 trim_blocks=True, 27 lstrip_blocks=True 28 ) 29 30 from bootstrap_util import getDirectory 31 32 class DocumentGenerator: 33 34 35 def __init__(self, contextHelper, scenario): 36 self.contextHelper = contextHelper 37 self.directory = getDirectory(contextHelper.context) 38 self.output = StringIO() 39 self.currentStep = 0 40 self.composition = None 41 42 #Weave advices into contextHelper 43 weave(target=self.contextHelper.before_step, advices=self.beforeStepAdvice) 44 weave(target=self.contextHelper.after_step, advices=self.afterStepAdvice) 45 weave(target=self.contextHelper.after_scenario, advices=self.afterScenarioAdvice) 46 weave(target=self.contextHelper.getBootrapHelper, advices=self.getBootstrapHelperAdvice) 47 weave(target=self.contextHelper.registerComposition, advices=self.registerCompositionAdvice) 48 49 # Weave advices into Directory 50 weave(target=self.directory._registerOrg, advices=self.registerOrgAdvice) 51 weave(target=self.directory._registerUser, advices=self.registerUserAdvice) 52 weave(target=self.directory.registerOrdererAdminTuple, advices=self.registerNamedNodeAdminTupleAdvice) 53 54 def beforeStepAdvice(self, joinpoint): 55 self.currentStep += 1 56 step = joinpoint.kwargs['step'] 57 # Now the jinja template 58 self.output.write(env.get_template("html/step.html").render(step_id="Step {0}".format(self.currentStep), step=step)) 59 return joinpoint.proceed() 60 61 def afterStepAdvice(self, joinpoint): 62 step = joinpoint.kwargs['step'] 63 # Now the jinja template 64 if step.status=="failed": 65 self.output.write(env.get_template("html/error.html").render(err=step.error_message)) 66 return joinpoint.proceed() 67 68 69 def compositionCallCLIAdvice(self, joinpoint): 70 'This advice is called around the compositions usage of the cli' 71 result = joinpoint.proceed() 72 # Create table for environment 73 composition = joinpoint.kwargs['self'] 74 envAdditions = composition.getEnvAdditions() 75 keys = envAdditions.keys() 76 keys.sort() 77 envPreamble = " ".join(["{0}={1}".format(key,envAdditions[key]) for key in keys]) 78 args= " ".join(joinpoint.kwargs['argList']) 79 self.output.write(env.get_template("html/cli.html").render(command="{0} {1}".format(envPreamble, args))) 80 return result 81 82 def _getNetworkGroup(self, serviceName): 83 groups = {"peer" : 1, "orderer" : 2, "kafka" : 7, "zookeeper" : 8, "couchdb" : 9} 84 groupId = 0 85 for group, id in groups.iteritems(): 86 if serviceName.lower().startswith(group): 87 groupId = id 88 return groupId 89 90 def _getNetworkForConfig(self, configAsYaml): 91 import yaml 92 config = yaml.load(configAsYaml) 93 assert "services" in config, "Expected config from docker-compose config to have services key at top level: \n{0}".format(config) 94 network = {"nodes": [], "links" : []} 95 for serviceName in config['services'].keys(): 96 network['nodes'].append({"id" : serviceName, "group" : self._getNetworkGroup(serviceName), "type" : "node"}) 97 # Now get links 98 if "depends_on" in config['services'][serviceName]: 99 for dependedOnServiceName in config['services'][serviceName]['depends_on']: 100 network['links'].append({"source": serviceName, "target": dependedOnServiceName, "value" : 1}) 101 return network 102 103 def _getNetworkForDirectory(self): 104 network = {"nodes":[], "links": []} 105 for orgName, org in self.directory.getOrganizations().iteritems(): 106 network['nodes'].append({"id" : orgName, "group" : 3, "type" : "org"}) 107 for userName, user in self.directory.getUsers().iteritems(): 108 network['nodes'].append({"id" : userName, "group" : 4, "type" : "user"}) 109 # Now get links 110 for nct, cert in self.directory.getNamedCtxTuples().iteritems(): 111 nctId = "{0}-{1}-{2}".format(nct.user, nct.nodeName, nct.organization) 112 network['nodes'].append({"id" : nctId, "group" : 5, "type" : "cert"}) 113 network['links'].append({"source": nctId, "target": nct.organization, "value" : 1}) 114 network['links'].append({"source": nctId, "target": nct.user, "value" : 1}) 115 # Only add the context link if it is a compose service, else the target may not exist. 116 if nct.nodeName in self.composition.getServiceNames(): 117 network['links'].append({"source": nctId, "target": nct.nodeName, "value" : 1}) 118 return network 119 120 def _writeNetworkJson(self): 121 if self.composition: 122 import json 123 configNetwork = self._getNetworkForConfig(configAsYaml=self.composition.getConfig()) 124 directoryNetwork = self._getNetworkForDirectory() 125 # Join the network info together 126 fullNetwork = dict(chain([(key, configNetwork[key] + directoryNetwork[key]) for key in configNetwork.keys()])) 127 (fileName, fileExists) = self.contextHelper.getTmpPathForName("network", extension="json") 128 with open(fileName, "w") as f: 129 f.write(json.dumps(fullNetwork)) 130 131 132 def registerCompositionAdvice(self, joinpoint): 133 composition = joinpoint.kwargs['composition'] 134 weave(target=composition._callCLI, advices=self.compositionCallCLIAdvice) 135 result = joinpoint.proceed() 136 if composition: 137 #Now get the config for the composition and dump out. 138 self.composition = composition 139 configAsYaml = composition.getConfig() 140 (dokerComposeYmlFileName, fileExists) = self.contextHelper.getTmpPathForName(name="docker-compose", extension="yml") 141 with open(dokerComposeYmlFileName, 'w') as f: 142 f.write(configAsYaml) 143 self.output.write(env.get_template("html/composition-py.html").render(compose_project_name= self.composition.projectName,docker_compose_yml_file=dokerComposeYmlFileName)) 144 self.output.write(env.get_template("html/header.html").render(text="Configuration", level=4)) 145 self.output.write(env.get_template("html/cli.html").render(command=configAsYaml)) 146 #Inject the graph 147 self.output.write(env.get_template("html/header.html").render(text="Network Graph", level=4)) 148 self.output.write(env.get_template("html/graph.html").render()) 149 return result 150 151 def _addLinkToFile(self, fileName ,linkText): 152 import ntpath 153 baseName = ntpath.basename(fileName) 154 # self.markdownWriter.addLink(linkUrl="./{0}".format(baseName), linkText=linkText, linkTitle=baseName) 155 156 def _getLinkInfoForFile(self, fileName): 157 import ntpath 158 return "./{0}".format(ntpath.basename(fileName)) 159 160 def registerOrgAdvice(self, joinpoint): 161 orgName = joinpoint.kwargs['orgName'] 162 newlyRegisteredOrg = joinpoint.proceed() 163 orgCert = newlyRegisteredOrg.getCertAsPEM() 164 #Write out key material 165 (fileName, fileExists) = self.contextHelper.getTmpPathForName(name="dir-org-{0}-cert".format(orgName), extension="pem") 166 with open(fileName, 'w') as f: 167 f.write(orgCert) 168 self._addLinkToFile(fileName=fileName, linkText="Public cert for Organization") 169 #Now the jinja output 170 self.output.write(env.get_template("html/org.html").render(org=newlyRegisteredOrg, cert_href=self._getLinkInfoForFile(fileName), path_to_cert=fileName)) 171 return newlyRegisteredOrg 172 173 def registerUserAdvice(self, joinpoint): 174 userName = joinpoint.kwargs['userName'] 175 newlyRegisteredUser = joinpoint.proceed() 176 #Write out key material 177 privateKeyAsPem = newlyRegisteredUser.getPrivateKeyAsPEM() 178 (fileName, fileExists) = self.contextHelper.getTmpPathForName(name="dir-user-{0}-privatekey".format(userName), extension="pem") 179 with open(fileName, 'w') as f: 180 f.write(privateKeyAsPem) 181 #Weave into user tags setting 182 weave(target=newlyRegisteredUser.setTagValue, advices=self.userSetTagValueAdvice) 183 #Now the jinja output 184 self.output.write(env.get_template("html/user.html").render(user=newlyRegisteredUser, private_key_href=self._getLinkInfoForFile(fileName))) 185 return newlyRegisteredUser 186 187 def _dump_context(self): 188 (dirPickleFileName, fileExists) = self.contextHelper.getTmpPathForName("dir", extension="pickle") 189 with open(dirPickleFileName, 'w') as f: 190 self.directory.dump(f) 191 #Now the jinja output 192 self.output.write(env.get_template("html/directory.html").render(directory=self.directory, path_to_pickle=dirPickleFileName)) 193 if self.composition: 194 (dokerComposeYmlFileName, fileExists) = self.contextHelper.getTmpPathForName(name="docker-compose", extension="yml") 195 self.output.write(env.get_template("html/appendix-py.html").render(directory=self.directory, 196 path_to_pickle=dirPickleFileName, 197 compose_project_name=self.composition.projectName, 198 docker_compose_yml_file=dokerComposeYmlFileName)) 199 200 201 def afterScenarioAdvice(self, joinpoint): 202 scenario = joinpoint.kwargs['scenario'] 203 self._dump_context() 204 #Render with jinja 205 header = env.get_template("html/scenario.html").render(scenario=scenario, steps=scenario.steps) 206 main = env.get_template("html/main.html").render(header=header, body=self.output.getvalue()) 207 (fileName, fileExists) = self.contextHelper.getTmpPathForName("scenario", extension="html") 208 with open(fileName, 'w') as f: 209 f.write(main.encode("utf-8")) 210 self._writeNetworkJson() 211 return joinpoint.proceed() 212 213 def registerNamedNodeAdminTupleAdvice(self, joinpoint): 214 namedNodeAdminTuple = joinpoint.proceed() 215 directory = joinpoint.kwargs['self'] 216 #jinja 217 newCertAsPEM = directory.getCertAsPEM(namedNodeAdminTuple) 218 self.output.write(env.get_template("html/header.html").render(text="Created new named node admin tuple: {0}".format(namedNodeAdminTuple), level=4)) 219 self.output.write(env.get_template("html/cli.html").render(command=newCertAsPEM)) 220 #Write cert out 221 fileNameTocheck = "dir-user-{0}-cert-{1}-{2}".format(namedNodeAdminTuple.user, namedNodeAdminTuple.nodeName, namedNodeAdminTuple.organization) 222 (fileName, fileExists) = self.contextHelper.getTmpPathForName(fileNameTocheck, extension="pem") 223 with open(fileName, 'w') as f: 224 f.write(newCertAsPEM) 225 return namedNodeAdminTuple 226 227 def bootstrapHelperSignConfigItemAdvice(self, joinpoint): 228 configItem = joinpoint.kwargs['configItem'] 229 #jinja 230 self.output.write(env.get_template("html/header.html").render(text="Dumping signed config item...", level=4)) 231 self.output.write(env.get_template("html/protobuf.html").render(msg=configItem, msgLength=len(str(configItem)))) 232 233 signedConfigItem = joinpoint.proceed() 234 return signedConfigItem 235 236 def getBootstrapHelperAdvice(self, joinpoint): 237 bootstrapHelper = joinpoint.proceed() 238 weave(target=bootstrapHelper.signConfigItem, advices=self.bootstrapHelperSignConfigItemAdvice) 239 return bootstrapHelper 240 241 def _isProtobufMessage(self, target): 242 return isinstance(target, Message) 243 244 def _isListOfProtobufMessages(self, target): 245 result = False 246 if isinstance(target, list): 247 messageList = [item for item in target if self._isProtobufMessage(item)] 248 result = len(messageList) == len(target) 249 return result 250 251 def _isDictOfProtobufMessages(self, target): 252 result = False 253 if isinstance(target, dict): 254 messageList = [item for item in target.values() if self._isProtobufMessage(item)] 255 result = len(messageList) == len(target) 256 return result 257 258 def _writeProtobuf(self, fileName, msg): 259 import ntpath 260 baseName = ntpath.basename(fileName) 261 dataToWrite = msg.SerializeToString() 262 with open("{0}".format(fileName), 'wb') as f: 263 f.write(dataToWrite) 264 self.output.write(env.get_template("html/protobuf.html").render(id=baseName, msg=msg, path_to_protobuf=fileName, msgLength=len(dataToWrite),linkUrl="./{0}".format(baseName), linkText="Protobuf message in binary form", linkTitle=baseName)) 265 266 267 def userSetTagValueAdvice(self, joinpoint): 268 result = joinpoint.proceed() 269 user = joinpoint.kwargs['self'] 270 tagKey = joinpoint.kwargs['tagKey'] 271 tagValue = joinpoint.kwargs['tagValue'] 272 273 #jinja invoke 274 self.output.write(env.get_template("html/tag.html").render(user=user, tag_key=tagKey)) 275 276 # If protobuf message, write out in binary form 277 if self._isProtobufMessage(tagValue): 278 import ntpath 279 (fileName, fileExists) = self.contextHelper.getTmpPathForName("{0}-{1}".format(user.getUserName(), tagKey), extension="protobuf") 280 self._writeProtobuf(fileName=fileName, msg=tagValue) 281 # If protobuf message, write out in binary form 282 elif self._isListOfProtobufMessages(tagValue): 283 index = 0 284 for msg in tagValue: 285 (fileName, fileExists) = self.contextHelper.getTmpPathForName("{0}-{1}-{2:0>4}".format(user.getUserName(), tagKey, index), extension="protobuf") 286 self._writeProtobuf(fileName=fileName, msg=msg) 287 index += 1 288 elif self._isDictOfProtobufMessages(tagValue): 289 for key,msg in tagValue.iteritems(): 290 (fileName, fileExists) = self.contextHelper.getTmpPathForName("{0}-{1}-{2}".format(user.getUserName(), tagKey, key), extension="protobuf") 291 self._writeProtobuf(fileName=fileName, msg=msg) 292 else: 293 self.output.write(env.get_template("html/cli.html").render(command=str(tagValue))) 294 return result