github.com/leonlxy/hyperledger@v1.0.0-alpha.0.20170427033203-34922035d248/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 self.output.write(env.get_template("html/header.html").render(text="Configuration", level=4)) 141 self.output.write(env.get_template("html/cli.html").render(command=configAsYaml)) 142 #Inject the graph 143 self.output.write(env.get_template("html/header.html").render(text="Network Graph", level=4)) 144 self.output.write(env.get_template("html/graph.html").render()) 145 return result 146 147 def _addLinkToFile(self, fileName ,linkText): 148 import ntpath 149 baseName = ntpath.basename(fileName) 150 # self.markdownWriter.addLink(linkUrl="./{0}".format(baseName), linkText=linkText, linkTitle=baseName) 151 152 def _getLinkInfoForFile(self, fileName): 153 import ntpath 154 return "./{0}".format(ntpath.basename(fileName)) 155 156 def registerOrgAdvice(self, joinpoint): 157 orgName = joinpoint.kwargs['orgName'] 158 newlyRegisteredOrg = joinpoint.proceed() 159 orgCert = newlyRegisteredOrg.getCertAsPEM() 160 #Write out key material 161 (fileName, fileExists) = self.contextHelper.getTmpPathForName(name="dir-org-{0}-cert".format(orgName), extension="pem") 162 with open(fileName, 'w') as f: 163 f.write(orgCert) 164 self._addLinkToFile(fileName=fileName, linkText="Public cert for Organization") 165 #Now the jinja output 166 self.output.write(env.get_template("html/org.html").render(org=newlyRegisteredOrg, cert_href=self._getLinkInfoForFile(fileName), path_to_cert=fileName)) 167 return newlyRegisteredOrg 168 169 def registerUserAdvice(self, joinpoint): 170 userName = joinpoint.kwargs['userName'] 171 newlyRegisteredUser = joinpoint.proceed() 172 #Write out key material 173 privateKeyAsPem = newlyRegisteredUser.getPrivateKeyAsPEM() 174 (fileName, fileExists) = self.contextHelper.getTmpPathForName(name="dir-user-{0}-privatekey".format(userName), extension="pem") 175 with open(fileName, 'w') as f: 176 f.write(privateKeyAsPem) 177 #Weave into user tags setting 178 weave(target=newlyRegisteredUser.setTagValue, advices=self.userSetTagValueAdvice) 179 #Now the jinja output 180 self.output.write(env.get_template("html/user.html").render(user=newlyRegisteredUser, private_key_href=self._getLinkInfoForFile(fileName))) 181 return newlyRegisteredUser 182 183 def afterScenarioAdvice(self, joinpoint): 184 scenario = joinpoint.kwargs['scenario'] 185 #Render with jinja 186 header = env.get_template("html/scenario.html").render(scenario=scenario, steps=scenario.steps) 187 main = env.get_template("html/main.html").render(header=header, body=self.output.getvalue()) 188 (fileName, fileExists) = self.contextHelper.getTmpPathForName("scenario", extension="html") 189 with open(fileName, 'w') as f: 190 f.write(main.encode("utf-8")) 191 self._writeNetworkJson() 192 return joinpoint.proceed() 193 194 def registerNamedNodeAdminTupleAdvice(self, joinpoint): 195 namedNodeAdminTuple = joinpoint.proceed() 196 directory = joinpoint.kwargs['self'] 197 #jinja 198 newCertAsPEM = directory.getCertAsPEM(namedNodeAdminTuple) 199 self.output.write(env.get_template("html/header.html").render(text="Created new named node admin tuple: {0}".format(namedNodeAdminTuple), level=4)) 200 self.output.write(env.get_template("html/cli.html").render(command=newCertAsPEM)) 201 #Write cert out 202 fileNameTocheck = "dir-user-{0}-cert-{1}-{2}".format(namedNodeAdminTuple.user, namedNodeAdminTuple.nodeName, namedNodeAdminTuple.organization) 203 (fileName, fileExists) = self.contextHelper.getTmpPathForName(fileNameTocheck, extension="pem") 204 with open(fileName, 'w') as f: 205 f.write(newCertAsPEM) 206 return namedNodeAdminTuple 207 208 def bootstrapHelperSignConfigItemAdvice(self, joinpoint): 209 configItem = joinpoint.kwargs['configItem'] 210 #jinja 211 self.output.write(env.get_template("html/header.html").render(text="Dumping signed config item...", level=4)) 212 self.output.write(env.get_template("html/protobuf.html").render(msg=configItem, msgLength=len(str(configItem)))) 213 214 signedConfigItem = joinpoint.proceed() 215 return signedConfigItem 216 217 def getBootstrapHelperAdvice(self, joinpoint): 218 bootstrapHelper = joinpoint.proceed() 219 weave(target=bootstrapHelper.signConfigItem, advices=self.bootstrapHelperSignConfigItemAdvice) 220 return bootstrapHelper 221 222 def _isProtobufMessage(self, target): 223 return isinstance(target, Message) 224 225 def _isListOfProtobufMessages(self, target): 226 result = False 227 if isinstance(target, list): 228 messageList = [item for item in target if self._isProtobufMessage(item)] 229 result = len(messageList) == len(target) 230 return result 231 232 def _isDictOfProtobufMessages(self, target): 233 result = False 234 if isinstance(target, dict): 235 messageList = [item for item in target.values() if self._isProtobufMessage(item)] 236 result = len(messageList) == len(target) 237 return result 238 239 def _writeProtobuf(self, fileName, msg): 240 import ntpath 241 baseName = ntpath.basename(fileName) 242 dataToWrite = msg.SerializeToString() 243 with open("{0}".format(fileName), 'wb') as f: 244 f.write(dataToWrite) 245 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)) 246 247 248 def userSetTagValueAdvice(self, joinpoint): 249 result = joinpoint.proceed() 250 user = joinpoint.kwargs['self'] 251 tagKey = joinpoint.kwargs['tagKey'] 252 tagValue = joinpoint.kwargs['tagValue'] 253 254 #jinja invoke 255 self.output.write(env.get_template("html/tag.html").render(user=user, tag_key=tagKey)) 256 257 # If protobuf message, write out in binary form 258 if self._isProtobufMessage(tagValue): 259 import ntpath 260 (fileName, fileExists) = self.contextHelper.getTmpPathForName("{0}-{1}".format(user.getUserName(), tagKey), extension="protobuf") 261 self._writeProtobuf(fileName=fileName, msg=tagValue) 262 # If protobuf message, write out in binary form 263 elif self._isListOfProtobufMessages(tagValue): 264 index = 0 265 for msg in tagValue: 266 (fileName, fileExists) = self.contextHelper.getTmpPathForName("{0}-{1}-{2:0>4}".format(user.getUserName(), tagKey, index), extension="protobuf") 267 self._writeProtobuf(fileName=fileName, msg=msg) 268 index += 1 269 elif self._isDictOfProtobufMessages(tagValue): 270 for key,msg in tagValue.iteritems(): 271 (fileName, fileExists) = self.contextHelper.getTmpPathForName("{0}-{1}-{2}".format(user.getUserName(), tagKey, key), extension="protobuf") 272 self._writeProtobuf(fileName=fileName, msg=msg) 273 else: 274 self.output.write(env.get_template("html/cli.html").render(command=str(tagValue))) 275 return result