1# This file is part of Buildbot. Buildbot is free software: you can 2# redistribute it and/or modify it under the terms of the GNU General Public 3# License as published by the Free Software Foundation, version 2. 4# 5# This program is distributed in the hope that it will be useful, but WITHOUT 6# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 7# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 8# details. 9# 10# You should have received a copy of the GNU General Public License along with 11# this program; if not, write to the Free Software Foundation, Inc., 51 12# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 13# 14# Copyright Buildbot Team Members 15 16import copy 17import json 18import os 19 20import yaml 21 22try: 23 from collections import OrderedDict 24except ImportError: # pragma: no cover 25 from ordereddict import OrderedDict 26 27 28# minimalistic raml loader. Support !include tags, and mapping as OrderedDict 29class RamlLoader(yaml.SafeLoader): 30 pass 31 32 33def construct_include(loader, node): 34 path = os.path.join(os.path.dirname(loader.stream.name), node.value) 35 with open(path) as f: 36 return yaml.load(f, Loader=RamlLoader) 37 38 39def construct_mapping(loader, node): 40 loader.flatten_mapping(node) 41 return OrderedDict(loader.construct_pairs(node)) 42 43 44RamlLoader.add_constructor( 45 yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, 46 construct_mapping) 47RamlLoader.add_constructor('!include', construct_include) 48 49 50class RamlSpec: 51 52 """ 53 This class loads the raml specification, and expose useful 54 aspects of the spec 55 56 Main usage for now is for the doc, but it can be extended to make sure 57 raml spec matches other spec implemented in the tests 58 """ 59 60 def __init__(self): 61 fn = os.path.join(os.path.dirname(__file__), 62 os.pardir, 'spec', 'api.raml') 63 with open(fn) as f: 64 self.api = yaml.load(f, Loader=RamlLoader) 65 66 with open(fn) as f: 67 self.rawraml = f.read() 68 69 endpoints = {} 70 self.endpoints_by_type = {} 71 self.rawendpoints = {} 72 self.endpoints = self.parse_endpoints(endpoints, "", self.api) 73 self.types = self.parse_types() 74 75 def parse_endpoints(self, endpoints, base, api, uriParameters=None): 76 if uriParameters is None: 77 uriParameters = OrderedDict() 78 79 for k, v in api.items(): 80 if k.startswith("/"): 81 ep = base + k 82 p = copy.deepcopy(uriParameters) 83 if v is not None: 84 p.update(v.get("uriParameters", {})) 85 v["uriParameters"] = p 86 endpoints[ep] = v 87 self.parse_endpoints(endpoints, ep, v, p) 88 elif k in ['get', 'post']: 89 if 'is' not in v: 90 continue 91 92 for _is in v['is']: 93 if not isinstance(_is, dict): 94 raise Exception('Unexpected "is" target {}: {}'.format(type(_is), _is)) 95 96 if 'bbget' in _is: 97 try: 98 v['eptype'] = _is['bbget']['bbtype'] 99 except TypeError as e: 100 raise Exception('Unexpected "is" target {}'.format(_is['bbget'])) from e 101 102 self.endpoints_by_type.setdefault(v['eptype'], {}) 103 self.endpoints_by_type[v['eptype']][base] = api 104 105 if 'bbgetraw' in _is: 106 self.rawendpoints.setdefault(base, {}) 107 self.rawendpoints[base] = api 108 return endpoints 109 110 def reindent(self, s, indent): 111 return s.replace("\n", "\n" + " " * indent) 112 113 def format_json(self, j, indent): 114 j = json.dumps(j, indent=4).replace(", \n", ",\n") 115 return self.reindent(j, indent) 116 117 def parse_types(self): 118 types = self.api['types'] 119 return types 120 121 def iter_actions(self, endpoint): 122 ACTIONS_MAGIC = '/actions/' 123 for k, v in endpoint.items(): 124 if k.startswith(ACTIONS_MAGIC): 125 k = k[len(ACTIONS_MAGIC):] 126 v = v['post'] 127 # simplify the raml tree for easier processing 128 v['body'] = v['body']['application/json'].get('properties', {}) 129 yield (k, v) 130