-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
397 lines (315 loc) · 10.5 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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
const chalk = require('chalk')
const _ = require('lodash')
const { print, debug, error, spinner, writeJSON } = require('./src/util')
/**
* GITLAB specific functions
*/
const {
getNewUploadUrl,
getGitlabUsers,
searchGitlabProjects,
uploadBinaryToGitlab,
postGitlabIssue,
postGitlabNote
} = require('./src/gitlab')
/**
* JIRA specific functions
*/
const {
getJiraIssues,
getJiraAttachements,
getJiraAttachementBinary,
getBinaryLocalFilename
} = require('./src/jira')
/**
* Transformations
*/
const {
originalAuthor,
jiraToGitlabIssue,
attachmentLine
} = require('./src/transform')
const { getConfig } = require('./src/config')
/**
* GETTING BASIC INSTANCE DATA
*
* Using Promise.all we'll parallel download
*
* - Gitlab Users
* - Gitlab projects with similar name to given project
* - All Jira issues for the given JIRA project
*
*/
const getInstanceData = async () => {
const { config } = global
let [ gitlabUsers, gitlabProjects, jiraIssuesResp ] = await Promise.all([
await getGitlabUsers(config.gitlab),
await searchGitlabProjects(config.gitlab),
(await getJiraIssues(config.jira)) || {}
])
let jiraIssues = jiraIssuesResp.issues
if (gitlabProjects.message) {
throw new Error(`Unable to get Gitlab projects, got "${gitlabProjects.message}" instead`)
}
// Find the config.gitlab.project ID
let gitlabProject = gitlabProjects.find(
proj => proj.path_with_namespace === config.gitlab.project
)
if (!gitlabProject) {
throw new Error(`Couldn't find project "${config.gitlab.project}" on Gitlab instance`)
} else {
debug(
'Gitlab project ' + chalk.cyan(config.gitlab.project) +
' has id: ' + chalk.bold.cyan(gitlabProject.id)
)
config.gitlab.projectId = gitlabProject.id
}
// No issues for given project, die
if (!jiraIssues) {
debug('Jira issues response: ' + JSON.stringify(jiraIssuesResp, null, 2))
throw new Error(`Couldn't find issues for "${config.jira.project}" on JIRA instance.`)
} else {
debug(
'JIRA project ' + chalk.cyan(config.jira.project) +
' has: ' + chalk.bold.cyan(jiraIssues.length) + ' issues'
)
}
// Update spinner with some data stats
spinner.text = chalk.yellow(' Getting base Gitlab and JIRA instance data.. ') +
chalk.cyan(jiraIssues.length + ' issues')
return { gitlabUsers, gitlabProjects, jiraIssues }
}
/**
* GETTING JIRA ISSUE ATTACHMENTS
*
* Using Promise.all we'll parallel download
*
* - Issue attachment and comment metadata
* - All Jira attachment binaries, and store them in the filesystem
*
*/
const getJiraAttachments = async (jiraIssues, gitlabUsers) => {
const { config } = global
let attComm = []
let atts = 0
let procAtts = 0
let curKey = ''
// spinner updating local closure
let updAtts = (key) => {
if (key) curKey = key
spinner.text = (
chalk.yellow(' Getting Jira Issue attachments.. Processing ') +
chalk.magenta(curKey + ': ') +
chalk.cyan(procAtts + '/' + atts) +
chalk.yellow(' attachments')
)
}
let gitlabIssues = await Promise.all(jiraIssues.map(
async jiraIssue => {
let { fields } = await getJiraAttachements(config.jira, jiraIssue)
updAtts(jiraIssue.key)
if (!fields) {
error(`Couldn't find fields for ${jiraIssue.key} on JIRA instance`)
attComm.push({issue: jiraIssue.key, attachments: [], comments: []})
return jiraToGitlabIssue(
jiraIssue, [], [], gitlabUsers, config.gitlab.sudo,
config.gitlab.emailMap, config.settings.matchByUserName
)
} else {
let jiraAttachments = fields.attachment
let jiraComments = fields.comment.comments
atts += jiraAttachments.length
updAtts()
debug(
'JIRA issue ' + chalk.cyan(jiraIssue.key) +
' has: ' + chalk.bold.cyan(jiraAttachments.length) + ' attachments and ' +
chalk.bold.green(jiraComments.length) + ' comments.'
)
attComm.push({issue: jiraIssue.key, attachments: jiraAttachments, comments: jiraComments})
let binaries = await Promise.all(jiraAttachments.map(
jiraAttachment => {
debug('Downloading ' + chalk.cyan(jiraAttachment.content))
return getJiraAttachementBinary(config.jira, jiraIssue, jiraAttachment)
}
))
debug(chalk.green('Downloaded ') + chalk.magenta(binaries.length) + ' binaries')
procAtts += binaries.length
updAtts()
return jiraToGitlabIssue(
jiraIssue, jiraAttachments, jiraComments, gitlabUsers,
config.gitlab.sudo, config.gitlab.emailMap,
config.settings.matchByUserName
)
}
}
))
// No issues, something is very wrong now, die
if (!gitlabIssues) {
throw new Error(
`Couldn't transform issues for "${config.jira.project}" or download ` +
`binaries from the instance.`)
} else {
debug(
'Downloaded ' + chalk.bold.cyan(atts) + ' attachments from project: ' +
chalk.cyan(config.jira.project)
)
}
return { attComm, gitlabIssues }
}
// async main
const main = async () => {
print(
chalk.bold('\njira2gitlab v0.6.0\n\n') +
'Imports JIRA issues into Gitlab\n\n' +
chalk.bold.cyan(' .. use at your own peril .. \n')
)
// Getting configuration
spinner.start(chalk.yellow(' Getting configuration..'))
const config = await getConfig()
global.config = config
spinner.succeed()
// getting instance data
spinner.start(chalk.yellow(' Getting base Gitlab and JIRA instance data..'))
let { gitlabUsers, gitlabProjects, jiraIssues } = await getInstanceData()
spinner.succeed()
// Getting JIRA issue attachments, comments and attached binaries
spinner.start(chalk.yellow(' Getting Jira Issue attachments..'))
let { attComm, gitlabIssues } = await getJiraAttachments(jiraIssues, gitlabUsers)
spinner.succeed()
/**
* SAVING DOWNLOADED DATA AS JSON FILES TO DISK
*
* Using Promise.all we'll parallel save all the data so far to disk
*
*/
spinner.start(
chalk.yellow(' Storing downloaded data to ') +
chalk.cyan('./payloads/')
)
await Promise.all([
writeJSON('payloads/gitlab-projects.json', gitlabProjects),
writeJSON('payloads/gitlab-users.json', gitlabUsers),
writeJSON('payloads/jira-issues.json', jiraIssues),
writeJSON('payloads/interim-issues.json', gitlabIssues),
writeJSON('payloads/att-comm.json', attComm)
])
spinner.succeed()
/**
* POSTING ATTACHMENTS AND ISSUES
*
* Using await and sync iteration we'll serially
*
* - Upload issue attachment binaries as project binaries
* - Rebind transformed Github issues to new binaries as attachments
* - Post issues to Gitlab
*
* If no 'go' CLI parameter, we'll just end here
*
*/
let atts = 0
let procAtts = 0
let comms = 0
let procComms = 0
let curKey = ''
let counter = 0
let issueCounter = 0
let gitlabPosts = []
let postedComments = []
// if 'go' CLI parameter was given, post issues to Gitlab
try {
if (process.argv[2] === 'go') {
spinner.start(chalk.yellow(' Posting issues to Gitlab..'))
// spinner updating local closure
let updGlab = (key) => {
if (key) curKey = key
spinner.text = (
chalk.yellow(' Posting issues to Gitlab.. Key: ') +
chalk.magenta(curKey) + ' ' +
chalk.cyan(issueCounter + '/' + gitlabIssues.length) +
chalk.yellow(' issues ') +
chalk.cyan(procAtts + '/' + atts) +
chalk.yellow(' atts ') +
chalk.cyan(procComms + '/' + comms) +
chalk.yellow(' comms.')
)
}
// I had to do it synchronously for the logic to hold
for (let issue of gitlabIssues) {
if (issue.done && config.settings.ignoreDone) continue
let jiraKey = issue.jira_key
let newIssue = _.cloneDeep(issue)
delete newIssue.jira_key
atts += issue.attachments.length
let attachments = []
let tailDescription = ''
if (issue.attachments.length > 0) {
tailDescription += '\n\n' +
'### Attachments\n\n\n' +
'|Filename|Uploader|Attachment|\n' +
'|---|---|---|\n'
for (let attachment of issue.attachments) {
let attach = _.cloneDeep(attachment)
let filename = getBinaryLocalFilename(jiraKey, attachment)
let upload
upload = await uploadBinaryToGitlab(config.gitlab, filename, attach.mimeType)
// in case of an upload error we want to end
if (upload.error) {
throw new Error(upload.error)
}
counter += 1
debug('Counter: ' + counter)
let newUrl = getNewUploadUrl(config.gitlab, upload)
debug('new upload url: ' + chalk.cyan(newUrl))
attach.content = newUrl
procAtts++
updGlab()
if (upload.markdown) tailDescription += attachmentLine(config.gitlab, upload, attachment)
attachments.push(attach)
}
}
delete newIssue.attachments
let comments = newIssue.comments
delete newIssue.comments
comms += (comments && comments instanceof Array) ? comments.length : 0
updGlab(jiraKey)
newIssue.description += tailDescription
let issueResp = await postGitlabIssue(config.gitlab, newIssue, config.sudo)
debug(JSON.stringify(issueResp, null, 2))
if (issueResp.error || issueResp.message) {
throw new Error(issueResp.error)
} else issueCounter += 1
gitlabPosts.push(newIssue)
let commentsUrl = issueResp['_links']['notes']
for (let comment of comments) {
comment.body = (config.sudo ? originalAuthor(comment.author) : '') + comment.body
let resp = await postGitlabNote(config.gitlab, commentsUrl, comment)
procComms += 1
if (!resp.error) postedComments.push(resp)
}
}
await writeJSON('payloads/gitlab-issues.json', gitlabPosts)
await writeJSON('payloads/gitlab-notes.json', postedComments)
spinner.succeed()
}
} catch (err) {
// record what we got so far
await writeJSON('payloads/gitlab-issues-error.json', gitlabPosts)
// rethrow
throw err
}
}
if (
(process.env.NODE_ENV && process.env.NODE_ENV.includes('devel')) ||
(process.env.DEBUG && Number(process.env.DEBUG) > 0)
) {
main()
.then()
} else {
main()
.then()
.catch(err => {
print()
error(err)
process.exit(1)
})
}