-
Notifications
You must be signed in to change notification settings - Fork 35
/
use.js
427 lines (380 loc) · 16.3 KB
/
use.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
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
const BaseCommand = require('../../BaseCommand')
const { CONSOLE_CONFIG_KEY, getProjectCredentialType } = require('../../lib/import-helper')
const { importConsoleConfig, downloadConsoleConfigToBuffer } = require('../../lib/import')
const { Flags, Args } = require('@oclif/core')
const inquirer = require('inquirer')
const config = require('@adobe/aio-lib-core-config')
const { EOL } = require('os')
const { warnIfOverwriteServicesInProductionWorkspace } = require('../../lib/app-helper')
const path = require('path')
const { ENTP_INT_CERTS_FOLDER } = require('../../lib/defaults')
const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:use', { provider: 'debug' })
const chalk = require('chalk')
const LibConsoleCLI = require('@adobe/aio-cli-lib-console')
class Use extends BaseCommand {
async run () {
const { flags, args } = await this.parse(Use)
aioLogger.debug(`args: ${JSON.stringify(args, null, 2)}, flags: ${JSON.stringify(flags, null, 2)}`)
// some additional checks and updates of flags and args on top of what oclif provides
this.additionalArgsFlagsProcessing(args, flags)
aioLogger.debug(`After processing - args: ${JSON.stringify(args, null, 2)}, flags: ${JSON.stringify(flags, null, 2)}`)
// make sure to prompt on stderr
const prompt = inquirer.createPromptModule({ output: process.stderr })
// load local config
const currentConfig = this.loadCurrentConfiguration()
const currentConfigString = this.configString(currentConfig)
const currentConfigIsComplete = this.isCompleteConfig(currentConfig)
this.log(`You are currently in:${EOL}${currentConfigString}${EOL}`)
if (args.config_file_path) {
const consoleConfig = await importConsoleConfig(args.config_file_path, flags)
this.finalLogMessage(consoleConfig)
return
}
// init console CLI sdk consoleCLI
// NOTE: the user must be able to login now
const consoleCLI = await this.getLibConsoleCLI()
// load global console config
const globalConfig = this.loadGlobalConfiguration()
const globalConfigString = this.configString(globalConfig, 4)
// load from global configuration or select workspace ?
const globalOperationFromFlag = flags.global ? 'global' : null
const workspaceOperationFromFlag = flags.workspace ? 'workspace' : null
// did the user specify --global or --workspace
// Note: global workspace(-name) flags are exclusive (see oclif flags options)
let useOperation = globalOperationFromFlag || workspaceOperationFromFlag
// if operation was not specified via flags we need to prompt the user for it
if (!useOperation) {
useOperation = await this.promptForUseOperation(prompt, globalConfigString)
}
// load the new workspace, project, org config
let newConfig
if (useOperation === 'global') {
if (!this.isCompleteConfig(globalConfig)) {
const message = `Your global Console configuration is incomplete.${EOL}` +
'Use the `aio console` commands to select your Organization, Project, and Workspace.'
this.error(message)
}
newConfig = globalConfig
} else {
// useOperation = 'workspace'
if (!currentConfigIsComplete) {
this.error(
'Incomplete .aio configuration. Cannot select a new Workspace in same Project.' + EOL +
'Please import a valid Adobe Developer Console configuration file via `aio app use <config>.json`.'
)
}
const workspace = await this.selectTargetWorkspaceInProject(
consoleCLI,
currentConfig,
flags
)
newConfig = {
...currentConfig,
workspace
}
}
// get supported org services
const supportedServices = await consoleCLI.getEnabledServicesForOrg(newConfig.org.id)
// only sync services if the current configuration is complete
if (currentConfigIsComplete) {
// get project credential type
const projectCredentialType = getProjectCredentialType(currentConfig, flags)
await this.syncServicesToTargetWorkspace(consoleCLI, prompt, currentConfig, newConfig, supportedServices, flags, projectCredentialType)
}
// download the console configuration for the newly selected org, project, workspace
const buffer = await downloadConsoleConfigToBuffer(consoleCLI, newConfig, supportedServices)
const consoleConfig = await importConsoleConfig(buffer, flags)
this.finalLogMessage(consoleConfig)
}
additionalArgsFlagsProcessing (args, flags) {
if (args.config_file_path &&
(flags.workspace || flags.global)
) {
this.error('Flags \'--workspace\' and \'--global\' cannot be used together with arg \'config_file_path\'.')
}
if (flags['no-input']) {
if (!args.config_file_path && !flags.workspace && !flags.global) {
this.error('Flag \'--no-input\', requires one of: arg \'config_file_path\', flag \'--workspace\' or flag \'--global\'')
}
flags['no-service-sync'] = !flags['confirm-service-sync']
flags.merge = !flags.overwrite
}
}
loadCurrentConfiguration () {
const projectConfig = config.get('project') || {}
const org = (projectConfig.org && { id: projectConfig.org.id, name: projectConfig.org.name }) || {}
const project = { name: projectConfig.name, id: projectConfig.id }
const workspace = (projectConfig.workspace && { ...projectConfig.workspace }) || {}
return { org, project, workspace }
}
loadGlobalConfiguration () {
return config.get(CONSOLE_CONFIG_KEY) || {}
}
configString (config, spaces = 0) {
const { org = {}, project = {}, workspace = {} } = config
const list = [
`1. Org: ${org.name || '<no org selected>'}`,
`2. Project: ${project.name || '<no project selected>'}`,
`3. Workspace: ${workspace.name || '<no workspace selected>'}`
]
return list
.map(line => ' '.repeat(spaces) + line)
.join(EOL)
}
async promptForUseOperation (prompt, globalConfigString) {
const op = await prompt([
{
type: 'list',
name: 'res',
message: 'Switch to a new Adobe Developer Console configuration:',
choices: [
{ name: `A. Use the global Org / Project / Workspace configuration:${EOL}${globalConfigString}`, value: 'global' },
{ name: 'B. Switch to another Workspace in the current Project', value: 'workspace' }
]
}
])
return op.res
}
isCompleteConfig (config) {
return config &&
config.org && config.org.id && config.org.name &&
config.project && config.project.id && config.project.name &&
config.workspace && config.workspace.id && config.workspace.name
}
/**
* @param {LibConsoleCLI} consoleCLI lib console config
* @param {object} config local configuration
* @param {object} flags input flags
* @returns {Buffer} the Adobe Developer Console configuration file for the workspace
*/
async selectTargetWorkspaceInProject (consoleCLI, config, flags) {
const { project, org } = config
const workspaceNameOrId = flags.workspace
// retrieve all workspaces
const workspaces = await consoleCLI.getWorkspaces(
org.id,
project.id
)
let workspace
let workspaceData = { name: workspaceNameOrId }
// does not prompt if workspaceNameOrId is defined via the flag
workspace = await consoleCLI.promptForSelectWorkspace(workspaces, {
workspaceId: workspaceNameOrId,
workspaceName: workspaceNameOrId
}, {
allowCreate: true
})
if (!workspace) {
aioLogger.debug(`--workspace=${workspaceNameOrId} was not found in the current Project ${project.name}`)
if (workspaceNameOrId) {
if (flags['confirm-new-workspace']) {
const shouldNewWorkspace = await consoleCLI.prompt.promptConfirm(`Workspace '${workspaceNameOrId}' does not exist \n > Do you wish to create a new workspace?`)
if (!shouldNewWorkspace) {
this.error('Workspace creation aborted')
}
} else {
// this is not an error, if we end up here we just use `workspaceData` later
}
} else {
workspaceData = await consoleCLI.promptForCreateWorkspaceDetails()
}
aioLogger.debug(`Creating workspace: ${workspaceData.name}`)
workspace = await consoleCLI.createWorkspace(org.id, project.id, workspaceData)
}
return {
name: workspace.name,
id: workspace.id
}
}
/**
* @param {LibConsoleCLI} consoleCLI lib console config
* @private
*/
async syncServicesToTargetWorkspace (consoleCLI, prompt, currentConfig, newConfig, supportedServices, flags, projectCredentialType) {
if (flags['no-service-sync']) {
console.error('Skipping Services sync as \'--no-service-sync=true\'')
console.error('Please verify Service subscriptions manually for the new Org/Project/Workspace configuration.')
return
}
const currentServiceProperties = await consoleCLI.getServicePropertiesFromWorkspaceWithCredentialType({
orgId: currentConfig.org.id,
projectId: currentConfig.project.id,
workspace: currentConfig.workspace,
supportedServices,
credentialType: projectCredentialType
})
const serviceProperties = await consoleCLI.getServicePropertiesFromWorkspaceWithCredentialType({
orgId: newConfig.org.id,
projectId: newConfig.project.id,
workspace: newConfig.workspace,
supportedServices,
credentialType: projectCredentialType
})
// service subscriptions are same
if (this.equalSets(
new Set(currentServiceProperties.map(s => s.sdkCode)),
new Set(serviceProperties.map(s => s.sdkCode))
)) {
return
}
// Note: this does not handle different product profiles for same service subscriptions yet
const newWorkspaceName = newConfig.workspace.name
const newProjectName = newConfig.project.name
const currentProjectName = currentConfig.project.name
// service subscriptions are different
console.error(
chalk.yellow('⚠ Services attached to the target Workspace do not match Service subscriptions in the current Workspace.')
)
// if org is different, sync is more complex as we would need to check if the target
// org supports the services attached in the current workspace, for now defer to
// manual selection
if (currentConfig.org.id !== newConfig.org.id) {
console.error(chalk.yellow(
`⚠ Target Project '${newProjectName}' is in a different Org than the current Project '${currentProjectName}.'`
))
console.error(chalk.yellow(
'⚠ Services cannot be synced across Orgs, please make sure to subscribe' +
' to missing Services manually in the Adobe Developer Console.'
))
return
}
// go on with sync, ensure user is aware of what where are doing
console.error(`The '${newWorkspaceName}' Workspace in Project '${newProjectName}' subscribes to the following Services:`)
console.error(JSON.stringify(serviceProperties.map(s => s.name), null, 2))
console.error(
'Your project requires the following Services based on your current Project / Workspace configuration:' +
`${EOL}${JSON.stringify(currentServiceProperties.map(s => s.name), null, 2)}`
)
if (!flags['confirm-service-sync']) {
// ask for confirmation, overwriting service subscriptions is a destructive
// operation, especially if done in Production
warnIfOverwriteServicesInProductionWorkspace(newProjectName, newWorkspaceName)
const confirm = await prompt([{
type: 'confirm',
name: 'res',
message:
`${EOL}Do you want to sync and update Services for Workspace '${newWorkspaceName}' in Project '${newProjectName}' now ?`
}])
if (!confirm.res) {
// abort service sync
console.error('Service will not be synced, make sure to manually add missing Services from the Developer Console.')
return
}
}
await consoleCLI.subscribeToServicesWithCredentialType({
orgId: newConfig.org.id,
project: newConfig.project,
workspace: newConfig.workspace,
certDir: path.join(this.config.dataDir, ENTP_INT_CERTS_FOLDER),
serviceProperties: currentServiceProperties,
credentialType: projectCredentialType
})
console.error(`✔ Successfully updated Services in Project ${newConfig.project.name} and Workspace ${newConfig.workspace.name}.`)
}
async finalLogMessage (consoleConfig) {
const config = { org: consoleConfig.project.org, project: consoleConfig.project, workspace: consoleConfig.project.workspace }
const configString = this.configString(config)
this.log(chalk.green(chalk.bold(
`${EOL}✔ Successfully imported configuration for:${EOL}${configString}.`
)))
}
equalSets (setA, setB) {
if (setA.size !== setB.size) {
return false
}
for (const a of setA) {
if (!setB.has(a)) {
return false
}
}
return true
}
}
Use.description = `Import an Adobe Developer Console configuration file.
If the optional configuration file is not set, this command will retrieve the console org, project, and workspace settings from the global config.
To set these global config values, see the help text for 'aio console --help'.
To download the configuration file for your project, select the 'Download' button in the toolbar of your project's page in https://developer.adobe.com/console/
`
Use.flags = {
...BaseCommand.flags,
overwrite: Flags.boolean({
description: 'Overwrite any .aio and .env files during import of the Adobe Developer Console configuration file',
default: false,
exclusive: ['merge']
}),
merge: Flags.boolean({
description: 'Merge any .aio and .env files during import of the Adobe Developer Console configuration file',
default: false,
char: 'm',
exclusive: ['overwrite']
}),
global: Flags.boolean({
description: 'Use the global Adobe Developer Console Org / Project / Workspace configuration, which can be set via `aio console` commands',
default: false,
char: 'g',
exclusive: ['workspace']
}),
workspace: Flags.string({
description: 'Specify the Adobe Developer Console Workspace name or Workspace id to import the configuration from',
default: '',
char: 'w',
exclusive: ['global', 'workspace-name']
}),
'confirm-new-workspace': Flags.boolean({
description: 'Prompt to confirm before creating a new workspace',
default: true,
allowNo: true,
relationships: [
// if prompt is false, then the workspace flag MUST be specified
{
type: 'all',
flags: [{
name: 'workspace',
when: async (flags) => flags['confirm-new-workspace'] === false
}]
}
]
}),
// 'workspace-name': Flags.string({
// description: '[DEPRECATED]: please use --workspace instead',
// default: '',
// char: 'w',
// exclusive: ['global', 'workspace']
// }),
'no-service-sync': Flags.boolean({
description: 'Skip the Service sync prompt and do not attach current Service subscriptions to the new Workspace',
default: false,
exclusive: ['confirm-service-sync']
}),
'confirm-service-sync': Flags.boolean({
description: 'Skip the Service sync prompt and overwrite Service subscriptions in the new Workspace with current subscriptions',
default: false,
exclusive: ['no-service-sync']
}),
'no-input': Flags.boolean({
description: 'Skip user prompts by setting --no-service-sync and --merge. Requires one of config_file_path or --global or --workspace',
default: false
}),
'use-jwt': Flags.boolean({
description: 'if the config has both jwt and OAuth Server to Server Credentials (while migrating), prefer the JWT credentials',
default: false
})
}
Use.args =
{
config_file_path: Args.string({
description: 'path to an Adobe I/O Developer Console configuration file',
required: false
})
}
module.exports = Use