generated from solacecommunity/template-repo
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
279 lines (235 loc) · 9.1 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#!/usr/bin/env node
const fs = require('fs');
const commander = require('commander');
const packagejson = require('./package.json')
const AsyncAPI = require('./util/asyncAPI');
const EventPortal = require("@solace-community/eventportal");
const { parse } = require('@asyncapi/parser')
async function main() {
// Parse command line args
commander
.name(`npx ${packagejson.name}`)
.description(`${packagejson.description}`)
.version(`${packagejson.version}`, '-v, --version')
.usage('[OPTIONS]...')
.requiredOption('-f, --file <value>', 'Required: Path to AsyncAPI spec file')
.option('-d, --domain <value>', 'Application Domain Name. If not passed, name extracted from x-domain-name in spec file')
.option('-dID, --domainID <value>', 'Application Domain ID. If not passed, ID extracted from x-domain-id in spec file')
.parse(process.argv);
const options = commander.opts()
var asyncAPIFile = fs.readFileSync(options.file).toString()
const ap = new AsyncAPI(asyncAPIFile)
const ep = new EventPortal()
// Validate and Dereference the AsyncAPI spec file
let doc = await parse(asyncAPIFile)
// Quit if Application version state is Released on EP
const applicationName = doc.info().title()
const applicationVersion = doc.info().version()
const applicationDescription = doc.info().description()
let EPApplicationID = ap.getApplicationID(doc) || await ep.getApplicationIDs(applicationName)
const EPApplicationState = EPApplicationID.length != 0 ? await ep.getApplicationState(EPApplicationID, applicationVersion) : null
if (EPApplicationState == "RELEASED") {
throw new Error(`Application ${applicationName} version ${applicationVersion} is already released!`)
}
// Get the application domain name from either the AsyncAPI spec or input param
let domainName = options.domain || await ep.getApplicationDomainName(options.domainID) || ap.getApplicationDomainName(doc) || await ep.getApplicationDomainName(ap.getApplicationDomainID(doc))
if (!domainName) {
throw new Error("No Application Domain defined! Define one of the following: \n\
- x-application-domain-name in AsyncAPI Spec file \n\
- x-application-domain-id in AsyncAPI Spec file \n\
- Set the -d or --domain flag \n\
- Set the -dID or --domainID flag"
)
}
// Create Application Domain given the application domain name. Return ID if domain already exists
let domainID = await ep.createApplicationDomain({
name: domainName,
description: applicationDescription,
uniqueTopicAddressEnforcementEnabled: true,
topicDomainEnforcementEnabled: false,
type: "ApplicationDomain"
})
console.log("\n...Adding Schemas...")
// Add schemas from spec file to EP
// Note: This assumes components section is present in spec file and used to ref to schemas
const DEFAULT_SCHEMA_VERSION = "0.0.1"
let schemas = []
for (var [schemaName, content] of Object.entries(doc.components().schemas())) {
console.log("===========")
console.log(`Adding Schema: ${schemaName}`)
// Create Schema object
let schemaID = await ep.createSchemaObject({
applicationDomainId: domainID,
name: schemaName,
shared: false,
contentType: "json",
schemaType: "jsonSchema"
})
let schemaVersion = content.json()['x-schema-version'] || DEFAULT_SCHEMA_VERSION
// Create Schema Version. If Schema already exists and in:
// DRAFT state --> Override
// Release || Deprecated || Retired state --> throw error
let schemaVersionID = await ep.createSchemaVersion({
schemaID: schemaID,
description: content.description(),
version: schemaVersion,
displayName: schemaName,
content: JSON.stringify(content.json()),
stateID: content.json()['x-schema-state'] || "1"
}, overwrite = true)
schemas.push({
name: schemaName,
schemaID: schemaID,
versionID: schemaVersionID,
})
}
// console.log(schemas)
console.log("\n...Adding Events...")
// Add events from spec file to EP and associate schemas to them
// Note: This assumes components section is present in spec file and used to ref to messages as events
const DEFAULT_EVENT_VERSION = "0.0.1"
let events = []
for (var channelName of doc.channelNames()) {
let channel = doc.channel(channelName)
let operation_object = channel.hasPublish() ? channel.publish() : channel.subscribe()
let message = operation_object.message()
let description = message.description()
let operation = channel.hasPublish() ? "publish" : "subscribe"
let eventName = message.json()['x-parser-message-name'] || message.json().name
let schemaName = message.json().payload ? message.json().payload['x-parser-schema-id'] : null
let eventVersion = message.json()['x-event-version'] || DEFAULT_EVENT_VERSION
console.log("==========")
console.log(`Adding event "${eventName}" with topic ${channelName}`)
// Create event object given event name
let eventID = await ep.createEventObject({
applicationDomainId: domainID,
name: eventName,
shared: false
})
// Construct address
let addressLevels = constructAddressLevels(channelName)
let schemaVersionID = null
if (schemaName.includes("anonymous")) {
// If we are here, it means that the Event has an inline payload definition (i.e. no $ref to names schema)
// Create schema + version on EP with name {eventName}_Payload
// Create Schema object
let schemaID = await ep.createSchemaObject({
applicationDomainId: domainID,
name: `${eventName}_payload`,
shared: false,
contentType: "json",
schemaType: "jsonSchema"
})
let schemaVersion = eventVersion
schemaVersionID = await ep.createSchemaVersion({
schemaID: schemaID,
description: description,
version: schemaVersion,
displayName: `${eventName}_payload`,
content: JSON.stringify(message.json().payload),
stateID: message.json()['x-event-state'] || "1"
}, overwrite = true)
schemas.push({
name: `${eventName}_payload`,
schemaID: schemaID,
versionID: schemaVersionID,
})
} else {
// Get Schema Version ID
schemaVersionID = schemas.filter(schema => {
return schema.name === schemaName;
}).map(schema => {
return schema.versionID;
});
schemaVersionID = schemaVersionID[0]
}
// Create Event version and associate schema version id to event
let eventVersionID = await ep.createEventVersion({
eventID: eventID,
displayName: eventName,
description: description,
version: eventVersion,
schemaVersionId: schemaVersionID,
deliveryDescriptor:{
brokerType: "solace",
address:{
addressLevels
},
stateID: message.json()['x-event-state'] || "1"
}
}, overwrite = true)
events.push({
name: eventName,
eventID: eventID,
versionID: eventVersionID,
operation: operation
})
}
// console.log(events)
let applicationID = null
console.log("\n...Adding Application...")
// Create a new Application Version or update application with details
if (EPApplicationID == "" || EPApplicationID == null || EPApplicationID == undefined) {
applicationID = await ep.createApplicationObject({
applicationDomainId: domainID,
name: applicationName,
applicationType: "standard",
})
} else {
applicationID = EPApplicationID
}
let producedEvents = []
let consumedEvents = []
// Populate produced events
// Note: We take into account that AsyncAPI defines the API from the perspective of the application's client
// To describe what the application does, we have to reverse the verbs
events.filter(event => {
return event.operation === "subscribe";
}).map(event => {
producedEvents.push(event.versionID)
});
// Populate consumed events
events.filter(event => {
return event.operation === "publish";
}).map(event => {
consumedEvents.push(event.versionID)
});
let applicationVersionID = await ep.createApplicationVersion({
applicationID: applicationID,
displayName: applicationName,
description: applicationDescription,
version: applicationVersion,
declaredProducedEventVersionIds: producedEvents.flat(),
declaredConsumedEventVersionIds: consumedEvents.flat(),
type: "application"
}, overwrite = true)
console.log(`\n\nImporting done! Imported the following to Application Domain "${domainName}"`)
console.log(`✅ Application: ${applicationName}`)
console.log(`✅ Events: `)
events.map(event =>{
console.log(` - ${event.name}`)
})
console.log(`✅ Schemas: `)
schemas.map(schema =>{
console.log(` - ${schema.name}`)
})
}
if (require.main === module) {
main();
}
// Helper functions
function constructAddressLevels(topic){
addressLevels = []
topic.split("/").map(level => {
let type = "literal"
if (level.includes("{")) {
level = level.replace('}','').replace('{','')
type = "variable"
}
addressLevels.push({
name: level,
addressLevelType: type
})
})
return addressLevels
}