@itentialopensource/adapter-openstack_cinder 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +5 -0
- package/.eslintrc.js +18 -0
- package/.jshintrc +3 -0
- package/AUTH.md +43 -0
- package/BROKER.md +199 -0
- package/CALLS.md +1118 -0
- package/CHANGELOG.md +9 -0
- package/CODE_OF_CONDUCT.md +43 -0
- package/CONTRIBUTING.md +172 -0
- package/ENHANCE.md +69 -0
- package/LICENSE +201 -0
- package/PROPERTIES.md +641 -0
- package/README.md +337 -0
- package/SUMMARY.md +9 -0
- package/SYSTEMINFO.md +11 -0
- package/TROUBLESHOOT.md +47 -0
- package/adapter.js +20288 -0
- package/adapterBase.js +1788 -0
- package/entities/.generic/action.json +214 -0
- package/entities/.generic/schema.json +28 -0
- package/entities/.system/action.json +50 -0
- package/entities/.system/mockdatafiles/getToken-default.json +242 -0
- package/entities/.system/mockdatafiles/healthcheck-default.json +3 -0
- package/entities/.system/schema.json +19 -0
- package/entities/.system/schemaTokenReq.json +53 -0
- package/entities/.system/schemaTokenResp.json +49 -0
- package/entities/APIExtensionsExtensions/action.json +25 -0
- package/entities/APIExtensionsExtensions/schema.json +30 -0
- package/entities/APIVersionDetails/action.json +25 -0
- package/entities/APIVersionDetails/schema.json +19 -0
- package/entities/APIVersions/action.json +25 -0
- package/entities/APIVersions/schema.json +19 -0
- package/entities/AttachmentsAttachments/action.json +147 -0
- package/entities/AttachmentsAttachments/schema.json +58 -0
- package/entities/BackEndStoragePools/action.json +25 -0
- package/entities/BackEndStoragePools/schema.json +30 -0
- package/entities/BackupActionsBackupsAction/action.json +24 -0
- package/entities/BackupActionsBackupsAction/schema.json +41 -0
- package/entities/BackupsBackups/action.json +188 -0
- package/entities/BackupsBackups/schema.json +71 -0
- package/entities/CapabilitiesForStorageBackEndsCapabilities/action.json +25 -0
- package/entities/CapabilitiesForStorageBackEndsCapabilities/schema.json +30 -0
- package/entities/ConsistencyGroupSnapshotsDEPRECATED/action.json +107 -0
- package/entities/ConsistencyGroupSnapshotsDEPRECATED/schema.json +56 -0
- package/entities/ConsistencyGroupsDEPRECATED/action.json +147 -0
- package/entities/ConsistencyGroupsDEPRECATED/schema.json +58 -0
- package/entities/DefaultVolumeTypesDefaultTypes/action.json +86 -0
- package/entities/DefaultVolumeTypesDefaultTypes/schema.json +33 -0
- package/entities/GenericVolumeGroupsGroups/action.json +147 -0
- package/entities/GenericVolumeGroupsGroups/schema.json +69 -0
- package/entities/GroupSnapshotsGroupSnapshots/action.json +127 -0
- package/entities/GroupSnapshotsGroupSnapshots/schema.json +79 -0
- package/entities/GroupTypeSpecsGroupTypesGroupSpecs/action.json +106 -0
- package/entities/GroupTypeSpecsGroupTypesGroupSpecs/schema.json +56 -0
- package/entities/GroupTypesGroupTypes/action.json +127 -0
- package/entities/GroupTypesGroupTypes/schema.json +46 -0
- package/entities/HostsExtensionOsHosts/action.json +46 -0
- package/entities/HostsExtensionOsHosts/schema.json +42 -0
- package/entities/LimitsLimits/action.json +25 -0
- package/entities/LimitsLimits/schema.json +30 -0
- package/entities/MessagesMessages/action.json +66 -0
- package/entities/MessagesMessages/schema.json +43 -0
- package/entities/QualityOfServiceQoSSpecificationsQosSpecs/action.json +210 -0
- package/entities/QualityOfServiceQoSSpecificationsQosSpecs/schema.json +61 -0
- package/entities/QuotaClassSetExtensionOsQuotaClassSets/action.json +45 -0
- package/entities/QuotaClassSetExtensionOsQuotaClassSets/schema.json +42 -0
- package/entities/QuotaSetsExtensionOsQuotaSets/action.json +86 -0
- package/entities/QuotaSetsExtensionOsQuotaSets/schema.json +44 -0
- package/entities/ResourceFiltersResourceFilters/action.json +25 -0
- package/entities/ResourceFiltersResourceFilters/schema.json +30 -0
- package/entities/ServicesOsServices/action.json +185 -0
- package/entities/ServicesOsServices/schema.json +38 -0
- package/entities/SnapshotActionsSnapshotsAction/action.json +24 -0
- package/entities/SnapshotActionsSnapshotsAction/schema.json +41 -0
- package/entities/SnapshotManageExtensionManageableSnapshots/action.json +66 -0
- package/entities/SnapshotManageExtensionManageableSnapshots/schema.json +32 -0
- package/entities/VolumeActionsVolumesAction/action.json +24 -0
- package/entities/VolumeActionsVolumesAction/schema.json +41 -0
- package/entities/VolumeManageExtensionManageableVolumes/action.json +66 -0
- package/entities/VolumeManageExtensionManageableVolumes/schema.json +32 -0
- package/entities/VolumeSnapshotsSnapshots/action.json +249 -0
- package/entities/VolumeSnapshotsSnapshots/schema.json +85 -0
- package/entities/VolumeTransfer/action.json +127 -0
- package/entities/VolumeTransfer/schema.json +57 -0
- package/entities/VolumeTransfersVolumeTransfers355OrLater/action.json +127 -0
- package/entities/VolumeTransfersVolumeTransfers355OrLater/schema.json +79 -0
- package/entities/VolumeTypeAccessTypesActionTypesOsVolumeTypeAccess/action.json +45 -0
- package/entities/VolumeTypeAccessTypesActionTypesOsVolumeTypeAccess/schema.json +42 -0
- package/entities/VolumeTypesTypes/action.json +331 -0
- package/entities/VolumeTypesTypes/schema.json +78 -0
- package/entities/VolumesVolumes/action.json +270 -0
- package/entities/VolumesVolumes/schema.json +108 -0
- package/entities/WorkersWorkers/action.json +24 -0
- package/entities/WorkersWorkers/schema.json +30 -0
- package/error.json +190 -0
- package/package.json +87 -0
- package/pronghorn.json +10093 -0
- package/propertiesDecorators.json +14 -0
- package/propertiesSchema.json +1284 -0
- package/refs?service=git-upload-pack +0 -0
- package/report/adapterInfo.json +10 -0
- package/report/cinder-openapi.json +8589 -0
- package/report/creationReport.json +1495 -0
- package/sampleProperties.json +198 -0
- package/swagger/openstack_API_docs/BlockStorageAPIV3-cinder-documentation.html +32674 -0
- package/swagger/openstack_api_cinder.js +602 -0
- package/test/integration/adapterTestBasicGet.js +85 -0
- package/test/integration/adapterTestConnectivity.js +93 -0
- package/test/integration/adapterTestIntegration.js +4799 -0
- package/test/unit/adapterBaseTestUnit.js +952 -0
- package/test/unit/adapterTestUnit.js +7633 -0
- package/utils/adapterInfo.js +206 -0
- package/utils/addAuth.js +94 -0
- package/utils/artifactize.js +146 -0
- package/utils/basicGet.js +50 -0
- package/utils/checkMigrate.js +63 -0
- package/utils/entitiesToDB.js +179 -0
- package/utils/findPath.js +74 -0
- package/utils/methodDocumentor.js +225 -0
- package/utils/modify.js +154 -0
- package/utils/packModificationScript.js +35 -0
- package/utils/patches2bundledDeps.js +90 -0
- package/utils/pre-commit.sh +32 -0
- package/utils/removeHooks.js +20 -0
- package/utils/setup.js +33 -0
- package/utils/tbScript.js +184 -0
- package/utils/tbUtils.js +469 -0
- package/utils/testRunner.js +298 -0
- package/utils/troubleshootingAdapter.js +190 -0
- package/workflows/README.md +3 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const util = require('util');
|
|
4
|
+
const parse5 = require('parse5');
|
|
5
|
+
const htmlparser2 = require("htmlparser2");
|
|
6
|
+
const domutils = require("domutils");
|
|
7
|
+
const { Parser } = require("htmlparser2");
|
|
8
|
+
const { DomHandler } = require("domhandler");
|
|
9
|
+
const toJsonSchema = require('to-json-schema');
|
|
10
|
+
const { stringify } = require('yaml')
|
|
11
|
+
|
|
12
|
+
const inspect = obj => util.inspect(obj, { depth: Infinity });
|
|
13
|
+
|
|
14
|
+
const htmlFile = fs.readFileSync('/home/bota/IAP/apps/node_practice/openstack_API_docs/BlockStorageAPIV3-cinder-documentation.html', { encoding: 'utf-8' })
|
|
15
|
+
const outputFileName = 'cinder-openapi.json'
|
|
16
|
+
|
|
17
|
+
const changeToCamelCase = (s) => {
|
|
18
|
+
let operationId = "";
|
|
19
|
+
s.split(" ").forEach((word, index) => {
|
|
20
|
+
let firstLetter;
|
|
21
|
+
if (index === 0) {
|
|
22
|
+
firstLetter = word[0].toLowerCase()
|
|
23
|
+
} else {
|
|
24
|
+
firstLetter = word[0].toUpperCase()
|
|
25
|
+
}
|
|
26
|
+
operationId += firstLetter + word.slice(1);
|
|
27
|
+
})
|
|
28
|
+
return operationId;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const getTag = (node) => {
|
|
32
|
+
const siblings = domutils.getSiblings(node);
|
|
33
|
+
const h2 = domutils.filter(elem => elem.name === 'h2', siblings);
|
|
34
|
+
let tag = '';
|
|
35
|
+
if (h2.length !== 1) {
|
|
36
|
+
console.error('Can not obtain tag')
|
|
37
|
+
return tag;
|
|
38
|
+
}
|
|
39
|
+
const tagNode = h2[0];
|
|
40
|
+
const tagNodetext = domutils.getChildren(tagNode).filter(elem => elem.type === 'text', tagNode);
|
|
41
|
+
tag = domutils.innerText(tagNodetext).trim();
|
|
42
|
+
return tag;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const getRestMethod = (node) => {
|
|
46
|
+
const n = domutils.findOne((e => e.name === 'span' && e.attribs.class.split(' ').find(item => item === 'label')), [node]);
|
|
47
|
+
const restMethod = domutils.innerText(n).trim().toLocaleLowerCase();
|
|
48
|
+
return restMethod;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const getPath = (node) => {
|
|
52
|
+
const n = domutils.findOne((e => e.name === 'div' && e.attribs.class === 'endpoint-container'), [node]);
|
|
53
|
+
const childs = domutils.getElementsByTagName('div', domutils.getChildren(n), false);
|
|
54
|
+
const pathNode = childs[0];
|
|
55
|
+
const path = domutils.innerText(pathNode).trim();
|
|
56
|
+
return path;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const getDescription = (node) => {
|
|
60
|
+
const n = domutils.findOne((e => e.name === 'div' && e.attribs.class === 'endpoint-container'), [node]);
|
|
61
|
+
const childs = domutils.getElementsByTagName('div', domutils.getChildren(n), false);
|
|
62
|
+
const descriptionNode = childs[1];
|
|
63
|
+
const path = domutils.innerText(descriptionNode).trim();
|
|
64
|
+
return path;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const getDetailsLink = (node) => {
|
|
68
|
+
const n = domutils.findOne((e => e.name === 'div' && e.attribs.class === 'operation'), [node]);
|
|
69
|
+
const childs = domutils.getElementsByTagName('a', domutils.getChildren(n), true);
|
|
70
|
+
if (childs.length !== 1) {
|
|
71
|
+
return 'No link found';
|
|
72
|
+
}
|
|
73
|
+
const detailsLinkNode = childs[0];
|
|
74
|
+
const detailsLink = detailsLinkNode.attribs.href;
|
|
75
|
+
return detailsLink;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const getRequestSchema = (section) => {
|
|
79
|
+
let requestSchema = {}
|
|
80
|
+
if(!section) return requestSchema;
|
|
81
|
+
let exampleNode = domutils.findOne((elem) => elem.name === 'div' && (elem.attribs.class && elem.attribs.class.startsWith('highlight-javascript')), [section], true);
|
|
82
|
+
if (exampleNode) {
|
|
83
|
+
// console.log('exampleNode 1', exampleNode, domutils.textContent(exampleNode).trim());
|
|
84
|
+
// console.log('exampleNode 1 CONTENT', domutils.textContent(exampleNode).trim());
|
|
85
|
+
const s = domutils.textContent(exampleNode).trim();
|
|
86
|
+
try {
|
|
87
|
+
let o = JSON.parse(s);
|
|
88
|
+
requestSchema = toJsonSchema(o);
|
|
89
|
+
} catch (err) { }
|
|
90
|
+
|
|
91
|
+
// const requestSchema = stringify(schema);
|
|
92
|
+
// console.log('YAML schema', requestSchema)
|
|
93
|
+
}
|
|
94
|
+
console.log('example', JSON.stringify(requestSchema));
|
|
95
|
+
return requestSchema;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const getRequest = (node) => {
|
|
99
|
+
const requestParameters = [];
|
|
100
|
+
let requestSchema = {}
|
|
101
|
+
const sections = domutils.getElementsByTagName('section', domutils.getChildren(node), false);
|
|
102
|
+
|
|
103
|
+
let requestSection = sections.find(s => {
|
|
104
|
+
let textNodes = domutils.getElementsByTagName('h4', domutils.getChildren(s), false);
|
|
105
|
+
return domutils.textContent(textNodes).includes('Request');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
let requestExampleSection = sections.find(s => {
|
|
109
|
+
let textNodes = domutils.getElementsByTagName('h4', domutils.getChildren(s), false);
|
|
110
|
+
return domutils.textContent(textNodes).includes('Request Example');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!requestSection && !requestExampleSection) return {requestParameters, requestSchema};
|
|
114
|
+
|
|
115
|
+
requestSchema = getRequestSchema(requestExampleSection);
|
|
116
|
+
|
|
117
|
+
const subSections = domutils.getElementsByTagName('section', domutils.getChildren(requestSection), false);
|
|
118
|
+
let requestParamtersSection = subSections.find(s => {
|
|
119
|
+
let textNodes = domutils.getElementsByTagName('h5', domutils.getChildren(s), false);
|
|
120
|
+
return domutils.textContent(textNodes).includes('Parameters');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// TODO Add support for multiple requests/responses
|
|
124
|
+
const table = domutils.getElementsByTagName('tbody', requestParamtersSection || requestSection);
|
|
125
|
+
const allRows = domutils.getElementsByTagName('tr', table);
|
|
126
|
+
|
|
127
|
+
allRows.forEach((row) => {
|
|
128
|
+
let parameter = {};
|
|
129
|
+
let columns = domutils.getElementsByTagName('p', row);
|
|
130
|
+
columns.forEach((c, index) => {
|
|
131
|
+
let t = domutils.innerText(c).replace(/[\r\n]\s+/g, ' ').trim();
|
|
132
|
+
switch (index) {
|
|
133
|
+
case 0:
|
|
134
|
+
parameter.name = t;
|
|
135
|
+
break;
|
|
136
|
+
case 1:
|
|
137
|
+
parameter.in = t;
|
|
138
|
+
break;
|
|
139
|
+
case 2:
|
|
140
|
+
parameter.type = t;
|
|
141
|
+
break;
|
|
142
|
+
case 3:
|
|
143
|
+
parameter.description = t;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
requestParameters.push(parameter);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return { requestParameters, requestSchema };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const getResponse = (node) => {
|
|
154
|
+
let response = [];
|
|
155
|
+
const sections = domutils.getElementsByTagName('section', domutils.getChildren(node), false);
|
|
156
|
+
|
|
157
|
+
let responseSection = sections.find(s => {
|
|
158
|
+
let textNodes = domutils.getElementsByTagName('h4', domutils.getChildren(s), false);
|
|
159
|
+
return domutils.textContent(textNodes).includes('Response');
|
|
160
|
+
});
|
|
161
|
+
if (!responseSection) return response;
|
|
162
|
+
|
|
163
|
+
const subSections = domutils.getElementsByTagName('section', domutils.getChildren(responseSection), false);
|
|
164
|
+
let responseParamtersSection = subSections.find(s => {
|
|
165
|
+
let textNodes = domutils.getElementsByTagName('h4', domutils.getChildren(s), false);
|
|
166
|
+
return domutils.textContent(textNodes).includes('Parameters');
|
|
167
|
+
});
|
|
168
|
+
if (!responseParamtersSection) return response
|
|
169
|
+
// TODO Add support for multiple requests/responses
|
|
170
|
+
const table = domutils.getElementsByTagName('tbody', responseParamtersSection);
|
|
171
|
+
const allRows = domutils.getElementsByTagName('tr', table);
|
|
172
|
+
|
|
173
|
+
allRows.forEach((row) => {
|
|
174
|
+
let parameter = {};
|
|
175
|
+
let columns = domutils.getElementsByTagName('p', row);
|
|
176
|
+
columns.forEach((c, index) => {
|
|
177
|
+
let t = domutils.innerText(c).replace(/[\r\n]\s+/g, ' ').trim();
|
|
178
|
+
switch (index) {
|
|
179
|
+
case 0:
|
|
180
|
+
parameter.name = t;
|
|
181
|
+
break;
|
|
182
|
+
case 1:
|
|
183
|
+
parameter.in = t;
|
|
184
|
+
break;
|
|
185
|
+
case 2:
|
|
186
|
+
parameter.type = t;
|
|
187
|
+
break;
|
|
188
|
+
case 3:
|
|
189
|
+
parameter.description = t;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
response.push(parameter);
|
|
194
|
+
});
|
|
195
|
+
return response;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const getStatusCodes = (node) => {
|
|
199
|
+
let statusCodes = [];
|
|
200
|
+
const sections = domutils.getElementsByTagName('section', domutils.getChildren(node), true);
|
|
201
|
+
|
|
202
|
+
let responseSection = sections.find(s => {
|
|
203
|
+
let textNodes = domutils.getElementsByTagName('h4', domutils.getChildren(s), false);
|
|
204
|
+
return domutils.textContent(textNodes).includes('Response');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
let statusCodesSection = sections.find(s => {
|
|
208
|
+
let textNodes = domutils.getElementsByTagName('h5', domutils.getChildren(s), false);
|
|
209
|
+
return domutils.textContent(textNodes).includes('Status Codes');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
let statusCodesSuccessSection = sections.find(s => {
|
|
213
|
+
let textNodes = domutils.getElementsByTagName('h5', domutils.getChildren(s), false);
|
|
214
|
+
return domutils.textContent(textNodes).includes('Success');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
let statusCodesErrorSection = sections.find(s => {
|
|
218
|
+
let textNodes = domutils.getElementsByTagName('h5', domutils.getChildren(s), false);
|
|
219
|
+
return domutils.textContent(textNodes).includes('Error');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (statusCodesSuccessSection) {
|
|
223
|
+
const rows = domutils.getElementsByTagName('td', domutils.getChildren(statusCodesSuccessSection));
|
|
224
|
+
rows.forEach((r, index) => {
|
|
225
|
+
if (index % 2 === 0) {
|
|
226
|
+
statusCodes.push({ code: domutils.innerText(r).trim(), reason: domutils.innerText(rows[index + 1]).trim() })
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
if (statusCodesErrorSection) {
|
|
231
|
+
const rows = domutils.getElementsByTagName('td', domutils.getChildren(statusCodesErrorSection));
|
|
232
|
+
rows.forEach((r, index) => {
|
|
233
|
+
if (index % 2 === 0) {
|
|
234
|
+
statusCodes.push({ code: domutils.innerText(r).trim(), reason: domutils.innerText(rows[index + 1]).trim() })
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (statusCodesSection && !statusCodesSuccessSection && !statusCodesErrorSection) {
|
|
240
|
+
const rows = domutils.getElementsByTagName('td', domutils.getChildren(statusCodesSection));
|
|
241
|
+
rows.forEach((r, index) => {
|
|
242
|
+
if (index % 2 === 0) {
|
|
243
|
+
statusCodes.push({ code: domutils.innerText(r).trim(), reason: domutils.innerText(rows[index + 1]).trim() })
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (statusCodes.length === 0) {
|
|
249
|
+
statusCodes.push({ code: "200 - OK", reason: "Request was successful." })
|
|
250
|
+
}
|
|
251
|
+
return statusCodes;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const extractData = (detailControlNodes, apiDetailControlNodes) => {
|
|
255
|
+
const N = detailControlNodes.length;
|
|
256
|
+
let data = [];
|
|
257
|
+
console.log("Operations found:");
|
|
258
|
+
for (let i = 0; i < N; i += 1) {
|
|
259
|
+
console.log('\n======= OPERATION ========\n');
|
|
260
|
+
const node = detailControlNodes[i];
|
|
261
|
+
const detailsNode = apiDetailControlNodes[i];
|
|
262
|
+
|
|
263
|
+
const tag = getTag(node);
|
|
264
|
+
const restMethod = getRestMethod(node);
|
|
265
|
+
const path = getPath(node);
|
|
266
|
+
const description = getDescription(node);
|
|
267
|
+
const detailsLink = getDetailsLink(node);
|
|
268
|
+
console.log(`> Operation:\n| ${restMethod} | ${path} | ${description} | ${detailsLink} |`);
|
|
269
|
+
const { requestParameters, requestSchema } = getRequest(detailsNode);
|
|
270
|
+
console.log(`> Request parameters:\n|${inspect(requestParameters)}|${inspect(requestSchema)}`);
|
|
271
|
+
const response = getResponse(detailsNode);
|
|
272
|
+
console.log(`> Response parameters:\n|${inspect(response)}|`);
|
|
273
|
+
const statusCodes = getStatusCodes(detailsNode);
|
|
274
|
+
console.log(`> Response status codes:\n|${inspect(statusCodes)}|`);
|
|
275
|
+
data.push({ restMethod, path, description, detailsLink, response, requestParameters, requestSchema, statusCodes, tag });
|
|
276
|
+
}
|
|
277
|
+
return data;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const saveToFile = (obj) => {
|
|
281
|
+
fs.writeFileSync(outputFileName, JSON.stringify(obj));
|
|
282
|
+
console.log(`File '${outputFileName}' saved.`)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const getBaseOpenApiObj = () => {
|
|
286
|
+
const openApiObj = {
|
|
287
|
+
openapi: "3.0.0",
|
|
288
|
+
info: {
|
|
289
|
+
version: "1.0",
|
|
290
|
+
title: "Openstack Cinder API"
|
|
291
|
+
},
|
|
292
|
+
servers: [
|
|
293
|
+
{
|
|
294
|
+
url: 'http://{host}:{port}',
|
|
295
|
+
description: 'Block Storage service (cinder) administrative endpoint',
|
|
296
|
+
variables: {
|
|
297
|
+
host: {
|
|
298
|
+
default: "localhost"
|
|
299
|
+
},
|
|
300
|
+
port: {
|
|
301
|
+
default: "8776"
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
],
|
|
306
|
+
paths: {
|
|
307
|
+
},
|
|
308
|
+
components: { schemas: {}, responses: {}, parameters: {}, examples: {}, requestBodies: {}, headers: {}, securitySchemes: {}, links: {}, callbacks: {} },
|
|
309
|
+
security: [],
|
|
310
|
+
tags: [],
|
|
311
|
+
externalDocs: {
|
|
312
|
+
description: 'Openstack Cinder Block Storage API Operations',
|
|
313
|
+
url: 'https://docs.openstack.org/api-ref/block-storage/v3/index.html?expanded=#'
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
openApiObj.components.securitySchemes = {
|
|
318
|
+
token: {
|
|
319
|
+
type: "apiKey",
|
|
320
|
+
description: 'Token authentication',
|
|
321
|
+
name: "X-Auth-Token",
|
|
322
|
+
in: "header"
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
openApiObj.security = [{ "token": [] }];
|
|
327
|
+
|
|
328
|
+
return openApiObj;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const buildQueryParameters = (operation) => {
|
|
332
|
+
let params = []
|
|
333
|
+
let queryParams = operation.filter((item) => item.in === 'query')
|
|
334
|
+
queryParams.forEach((p) => {
|
|
335
|
+
isOptional = false
|
|
336
|
+
if (p.name.indexOf('Optional') !== -1) {
|
|
337
|
+
isOptional = true;
|
|
338
|
+
p.name = p.name.split(' ')[0].trim();
|
|
339
|
+
}
|
|
340
|
+
if (p.type.includes('key-only')) {
|
|
341
|
+
params.push({
|
|
342
|
+
"name": p.name,
|
|
343
|
+
"in": p.in,
|
|
344
|
+
"required": !isOptional,
|
|
345
|
+
"schema": {
|
|
346
|
+
"type": 'boolean',
|
|
347
|
+
"description": p.description
|
|
348
|
+
},
|
|
349
|
+
"allowEmptyValue": true
|
|
350
|
+
})
|
|
351
|
+
} else {
|
|
352
|
+
if (p.type.includes('bool')) p.type = 'boolean'
|
|
353
|
+
params.push({
|
|
354
|
+
"name": p.name,
|
|
355
|
+
"in": p.in,
|
|
356
|
+
"required": !isOptional,
|
|
357
|
+
"schema": {
|
|
358
|
+
"type": p.type,
|
|
359
|
+
"description": p.description
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
return params;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const buildHeaderParameters = (operation) => {
|
|
368
|
+
let params = []
|
|
369
|
+
let headerParams = operation.filter((item) => item.in === 'header')
|
|
370
|
+
|
|
371
|
+
headerParams.forEach((p) => {
|
|
372
|
+
isOptional = false
|
|
373
|
+
if (p.name.indexOf('Optional') !== -1) {
|
|
374
|
+
isOptional = true;
|
|
375
|
+
p.name = p.name.split(' ')[0].trim();
|
|
376
|
+
}
|
|
377
|
+
params.push({
|
|
378
|
+
"name": p.name,
|
|
379
|
+
"in": p.in,
|
|
380
|
+
"required": !isOptional,
|
|
381
|
+
"schema": {
|
|
382
|
+
"type": p.type,
|
|
383
|
+
"description": p.description
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
});
|
|
387
|
+
return params;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const buildPathParameters = (operation) => {
|
|
391
|
+
let params = []
|
|
392
|
+
let pathParams = operation.filter((item) => item.in === 'path')
|
|
393
|
+
pathParams.forEach((p) => {
|
|
394
|
+
isOptional = false
|
|
395
|
+
if (p.name.indexOf('Optional') !== -1) {
|
|
396
|
+
isOptional = true;
|
|
397
|
+
p.name = p.name.split(' ')[0].trim();
|
|
398
|
+
}
|
|
399
|
+
params.push({
|
|
400
|
+
"name": p.name,
|
|
401
|
+
"in": p.in,
|
|
402
|
+
"required": !isOptional,
|
|
403
|
+
"schema": {
|
|
404
|
+
"type": p.type,
|
|
405
|
+
"description": p.description
|
|
406
|
+
}
|
|
407
|
+
})
|
|
408
|
+
});
|
|
409
|
+
return params;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const buildRequestBody = (request, requestSchema) => {
|
|
413
|
+
// let bodyParams = request.filter((item) => item.in === 'body')
|
|
414
|
+
let requestBody = {
|
|
415
|
+
"content": {
|
|
416
|
+
"application/json": {
|
|
417
|
+
"schema": requestSchema
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
isBodyPresent = Object.keys(requestSchema).length > 0;
|
|
423
|
+
// required = [];
|
|
424
|
+
// bodyParams.forEach((bodyParam) => {
|
|
425
|
+
// let propertyName = bodyParam.name.split(' ')[0];
|
|
426
|
+
// if (!bodyParam.name.includes('Optional')) {
|
|
427
|
+
// required.push(propertyName);
|
|
428
|
+
// }
|
|
429
|
+
// requestBody.content["application/json"].schema.properties[propertyName] = {};
|
|
430
|
+
// if (bodyParam.type === 'list') {
|
|
431
|
+
// bodyParam.type = 'array';
|
|
432
|
+
// }
|
|
433
|
+
// requestBody.content["application/json"].schema.properties[propertyName].type = bodyParam.type
|
|
434
|
+
// if (bodyParam.type === 'array') {
|
|
435
|
+
// let type = '';
|
|
436
|
+
// if (bodyParam.description.includes('object')) {
|
|
437
|
+
// type = 'object';
|
|
438
|
+
// } else if (bodyParam.description.includes('string')) {
|
|
439
|
+
// type = 'string';
|
|
440
|
+
// } else {
|
|
441
|
+
// type = 'string';
|
|
442
|
+
// }
|
|
443
|
+
// requestBody.content["application/json"].schema.properties[propertyName].items = {
|
|
444
|
+
// type
|
|
445
|
+
// }
|
|
446
|
+
// }
|
|
447
|
+
// requestBody.content["application/json"].schema.properties[propertyName].description = bodyParam.description
|
|
448
|
+
// });
|
|
449
|
+
|
|
450
|
+
// if (required.length > 0) {
|
|
451
|
+
// requestBody.content["application/json"].schema.required = required;
|
|
452
|
+
// }
|
|
453
|
+
|
|
454
|
+
return { isBodyPresent, requestBody };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const buildResponseBody = (operation) => {
|
|
458
|
+
const responseBody = {
|
|
459
|
+
"headers": {},
|
|
460
|
+
"content": {
|
|
461
|
+
"application/json": {
|
|
462
|
+
"schema": {
|
|
463
|
+
"type": "object",
|
|
464
|
+
"properties": {}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
let headerParams = operation.filter((item) => item.in === 'header');
|
|
471
|
+
headerParams.forEach((headerParam) => {
|
|
472
|
+
responseBody.headers[headerParam.name] = {
|
|
473
|
+
"description": headerParam.description,
|
|
474
|
+
"schema": {
|
|
475
|
+
"type": headerParam.type
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
let bodyParams = operation.filter((item) => item.in === 'body')
|
|
481
|
+
isBodyPresent = bodyParams.length > 0;
|
|
482
|
+
const required = new Set();
|
|
483
|
+
bodyParams.forEach((bodyParam) => {
|
|
484
|
+
let propertyName = bodyParam.name.split(' ')[0];
|
|
485
|
+
if (!bodyParam.name.includes('Optional')) {
|
|
486
|
+
required.add(propertyName);
|
|
487
|
+
}
|
|
488
|
+
responseBody.content["application/json"].schema.properties[propertyName] = {};
|
|
489
|
+
if (bodyParam.type === 'list') {
|
|
490
|
+
bodyParam.type = 'array';
|
|
491
|
+
}
|
|
492
|
+
responseBody.content["application/json"].schema.properties[propertyName].type = bodyParam.type
|
|
493
|
+
if (bodyParam.type === 'array') {
|
|
494
|
+
let type = '';
|
|
495
|
+
if (bodyParam.description.includes('object')) {
|
|
496
|
+
type = 'object';
|
|
497
|
+
} else if (bodyParam.description.includes('string')) {
|
|
498
|
+
type = 'string';
|
|
499
|
+
} else {
|
|
500
|
+
type = 'string';
|
|
501
|
+
}
|
|
502
|
+
responseBody.content["application/json"].schema.properties[propertyName].items = {
|
|
503
|
+
type
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
responseBody.content["application/json"].schema.properties[propertyName].description = bodyParam.description
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
if (required.size > 0) {
|
|
510
|
+
responseBody.content["application/json"].schema.required = [...required];
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return { isBodyPresent, responseBody };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const buildResponses = (statusCodes, response) => {
|
|
517
|
+
const responses = {}
|
|
518
|
+
statusCodes.forEach((item) => {
|
|
519
|
+
let code = item.code.split(' ')[0];
|
|
520
|
+
responses[code] = { "description": item.reason }
|
|
521
|
+
});
|
|
522
|
+
// const { isBodyPresent, responseBody } = buildResponseBody(response);
|
|
523
|
+
// const successfullResponse = Object.keys(responses).find(k => k.startsWith('2'));
|
|
524
|
+
// console.log('successfullResponse', successfullResponse);
|
|
525
|
+
// if (isBodyPresent) {
|
|
526
|
+
// console.log('responseBody', responseBody);
|
|
527
|
+
// Object.assign(responses[successfullResponse], responseBody);
|
|
528
|
+
// }
|
|
529
|
+
return responses;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const createOpenApiFile = (data) => {
|
|
533
|
+
const openApiObj = getBaseOpenApiObj();
|
|
534
|
+
|
|
535
|
+
const allEndpoints = new Set()
|
|
536
|
+
data.forEach((item) => allEndpoints.add(item.path))
|
|
537
|
+
console.log('\nFound:', allEndpoints.size, 'endpoints\n');
|
|
538
|
+
|
|
539
|
+
allEndpoints.forEach(endpoint => {
|
|
540
|
+
console.log('Processing endpoint', endpoint);
|
|
541
|
+
|
|
542
|
+
allEndpointOperations = data.filter(item => item.path === endpoint)
|
|
543
|
+
console.log(`Found ${allEndpointOperations.length} operations\n`);
|
|
544
|
+
|
|
545
|
+
openApiObj.paths[endpoint] = {};
|
|
546
|
+
|
|
547
|
+
let pathOpenAPI = openApiObj.paths[endpoint];
|
|
548
|
+
|
|
549
|
+
allEndpointOperations.forEach(op => {
|
|
550
|
+
if (pathOpenAPI[op.restMethod]) {
|
|
551
|
+
console.error('More then 1 operation for same (URIpath, restMethod) set, skipping...');
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
pathOpenAPI[op.restMethod] = { operationId: changeToCamelCase(op.description) };
|
|
555
|
+
pathOpenAPI[op.restMethod].summary = op.description;
|
|
556
|
+
pathOpenAPI[op.restMethod].externalDocs = { url: op.detailsLink }
|
|
557
|
+
|
|
558
|
+
let queryParams = buildQueryParameters(op.requestParameters);
|
|
559
|
+
let headerParams = buildHeaderParameters(op.requestParameters);
|
|
560
|
+
let pathParams = buildPathParameters(op.requestParameters);
|
|
561
|
+
if (queryParams.length + headerParams.length + pathParams.length > 0) {
|
|
562
|
+
pathOpenAPI[op.restMethod].parameters = queryParams.concat(headerParams).concat(pathParams);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
let { isBodyPresent, requestBody } = buildRequestBody(op.requestParameters, op.requestSchema);
|
|
566
|
+
if (isBodyPresent) {
|
|
567
|
+
pathOpenAPI[op.restMethod].requestBody = requestBody;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
pathOpenAPI[op.restMethod].responses = buildResponses(op.statusCodes, op.response);
|
|
571
|
+
|
|
572
|
+
if (op.tag) {
|
|
573
|
+
if (!pathOpenAPI[op.restMethod].tags) {
|
|
574
|
+
pathOpenAPI[op.restMethod].tags = [];
|
|
575
|
+
}
|
|
576
|
+
pathOpenAPI[op.restMethod].tags.push(op.tag);
|
|
577
|
+
}
|
|
578
|
+
})
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
saveToFile(openApiObj);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const handler = new DomHandler((error, dom) => {
|
|
585
|
+
if (error) {
|
|
586
|
+
} else {
|
|
587
|
+
let detailControlNodes = domutils.findAll((element) => element.name === "section" && element.attribs.class === 'detail-control', dom);
|
|
588
|
+
let apiDetailControlNodes = domutils.findAll((element) => element.name === "section" && element.attribs.class === 'api-detail collapse', dom);
|
|
589
|
+
|
|
590
|
+
console.log('Nodes count:', detailControlNodes.length, apiDetailControlNodes.length);
|
|
591
|
+
if (detailControlNodes.length !== apiDetailControlNodes.length) {
|
|
592
|
+
console.error('Count mismatch');
|
|
593
|
+
throw new Error();
|
|
594
|
+
}
|
|
595
|
+
const data = extractData(detailControlNodes, apiDetailControlNodes);
|
|
596
|
+
createOpenApiFile(data);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
const parser = new Parser(handler);
|
|
601
|
+
parser.write(htmlFile);
|
|
602
|
+
parser.end();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* @copyright Itential, LLC 2020 */
|
|
2
|
+
|
|
3
|
+
/* global describe context before after */
|
|
4
|
+
/* eslint global-require: warn */
|
|
5
|
+
/* eslint no-unused-vars: warn */
|
|
6
|
+
/* eslint import/no-extraneous-dependencies: warn */
|
|
7
|
+
/* eslint import/no-dynamic-require: warn */
|
|
8
|
+
/* eslint import/no-unresolved: warn */
|
|
9
|
+
|
|
10
|
+
const mocha = require('mocha');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const assert = require('assert');
|
|
13
|
+
const itParam = require('mocha-param');
|
|
14
|
+
|
|
15
|
+
const utils = require('../../utils/tbUtils');
|
|
16
|
+
const basicGet = require('../../utils/basicGet');
|
|
17
|
+
const { name } = require('../../package.json');
|
|
18
|
+
const { methods } = require('../../pronghorn.json');
|
|
19
|
+
|
|
20
|
+
const getPronghornProps = (iapDir) => {
|
|
21
|
+
const { Discovery } = require('@itential/itential-utils');
|
|
22
|
+
console.log('Retrieving properties.json file...');
|
|
23
|
+
const rawProps = require(path.join(iapDir, 'properties.json'));
|
|
24
|
+
console.log('Decrypting properties...');
|
|
25
|
+
const discovery = new Discovery();
|
|
26
|
+
const pronghornProps = utils.decryptProperties(rawProps, path.join(__dirname, '..'), discovery);
|
|
27
|
+
console.log('Found properties.\n');
|
|
28
|
+
return pronghornProps;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
let a;
|
|
32
|
+
|
|
33
|
+
describe('[integration] Adapter BasicGET Test', () => {
|
|
34
|
+
context('Testing GET calls without query parameters', () => {
|
|
35
|
+
before(async () => {
|
|
36
|
+
const iapDir = path.join(__dirname, '../../../../../');
|
|
37
|
+
if (!utils.areWeUnderIAPinstallationDirectory()) {
|
|
38
|
+
const sampleProperties = require('../../sampleProperties.json');
|
|
39
|
+
const adapter = { properties: sampleProperties };
|
|
40
|
+
a = basicGet.getAdapterInstance(adapter);
|
|
41
|
+
} else {
|
|
42
|
+
const pronghornProps = getPronghornProps(iapDir);
|
|
43
|
+
console.log('Connecting to Database...');
|
|
44
|
+
const database = await basicGet.connect(pronghornProps);
|
|
45
|
+
console.log('Connection established.');
|
|
46
|
+
const adapter = await database.collection(utils.SERVICE_CONFIGS_COLLECTION).findOne(
|
|
47
|
+
{ model: name }
|
|
48
|
+
);
|
|
49
|
+
a = basicGet.getAdapterInstance(adapter);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
after((done) => {
|
|
54
|
+
done();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const basicGets = methods.filter((method) => (method.route.verb === 'GET' && method.input.length === 0));
|
|
58
|
+
if (basicGets.length === 0) {
|
|
59
|
+
console.log('No non-parameter GET calls found.');
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
const functionNames = basicGets.map((g) => g.name);
|
|
63
|
+
const request = function request(f, ad) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const getRespCode = (resp) => {
|
|
66
|
+
if (resp) {
|
|
67
|
+
if (resp.metrics.code !== 200) {
|
|
68
|
+
console.log('\x1b[31m', `Testing ${f} \nResponseCode: ${resp.metrics.code}`);
|
|
69
|
+
}
|
|
70
|
+
resolve(resp.metrics.code);
|
|
71
|
+
} else {
|
|
72
|
+
console.log('\x1b[31m', `call ${f} results in failure`);
|
|
73
|
+
reject(new Error(`${f} failed`));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
ad[f](getRespCode, console.log);
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
itParam('GET call should return 200', functionNames, (fname) => {
|
|
81
|
+
console.log(`\t ${fname}`);
|
|
82
|
+
return request(fname, a).then((result) => assert.equal(result, 200));
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|