/
pull.ts
264 lines (235 loc) · 8.73 KB
/
pull.ts
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
import {ResourceSnapshotType} from '@coveo/platform-client';
import {
formatOrgId,
startSpinner,
stopSpinner,
} from '@coveo/cli-commons/utils/ux';
import {Flags} from '@oclif/core';
import {blueBright} from 'chalk';
import {readJsonSync} from 'fs-extra';
import {resolve} from 'path';
import {cwd} from 'process';
import dedent from 'ts-dedent';
import {Config} from '@coveo/cli-commons/config/config';
import {
HasNecessaryCoveoPrivileges,
IsAuthenticated,
Preconditions,
} from '@coveo/cli-commons/preconditions/index';
import {IsGitInstalled} from '../../../lib/decorators/preconditions/git';
import {writeSnapshotPrivilege} from '@coveo/cli-commons/preconditions/platformPrivilege';
import {Trackable} from '@coveo/cli-commons/preconditions/trackable';
import {SnapshotOperationTimeoutError} from '../../../lib/errors';
import {ProcessAbort} from '../../../lib/errors/processError';
import {snapshotId, wait} from '../../../lib/flags/snapshotCommonFlags';
import {Project} from '../../../lib/project/project';
import type {
SnapshotPullModel,
SnapshotPullModelResources,
} from '../../../lib/snapshot/pullModel/interfaces';
import {buildResourcesToExport} from '../../../lib/snapshot/pullModel/validation/model';
import {validateSnapshotPullModel} from '../../../lib/snapshot/pullModel/validation/validate';
import {Snapshot, WaitUntilDoneOptions} from '../../../lib/snapshot/snapshot';
import {cleanupProject} from '../../../lib/snapshot/snapshotCommon';
import {allowedResourceType} from '../../../lib/snapshot/snapshotConstant';
import {SnapshotFactory} from '../../../lib/snapshot/snapshotFactory';
import {confirmWithAnalytics} from '../../../lib/utils/cli';
import {spawnProcess} from '../../../lib/utils/process';
import {CLICommand} from '@coveo/cli-commons/command/cliCommand';
import {Example} from '@oclif/core/lib/interfaces';
import {organization} from '../../../lib/flags/platformCommonFlags';
import {getTargetOrg} from '../../../lib/utils/platform';
const PullCommandStrings = {
projectOverwriteQuestion: (
resourceFolderName: string
) => dedent`There is already a Coveo project with resources in it.
This command will delete the project in ${resourceFolderName} folder and start a new one, do you want to proceed? (y/n)`,
howToPullAfterTimeout: (targetOrgId: string, snapshotId: string) => dedent`
Once the snapshot is created, you can pull it with the following command:
${blueBright`coveo org:resources:pull -o ${targetOrgId} -s ${snapshotId}`}
`,
};
export default class Pull extends CLICommand {
public static description = 'Pull resources from an organization';
public static flags = {
...wait(),
...organization(
'The unique identifier of the organization from which to pull the resources'
),
...snapshotId(),
git: Flags.boolean({
char: 'g',
description:
'Whether to create a git repository when creating a new project.',
default: true,
allowNo: true,
}),
overwrite: Flags.boolean({
char: 'f',
description: 'Overwrite resources directory if it exists.',
default: false,
}),
resourceTypes: Flags.enum<ResourceSnapshotType>({
char: 'r',
helpValue: 'type1 type2',
description: 'The resources types to pull from the organization.',
multiple: true,
options: allowedResourceType,
default: allowedResourceType,
}),
model: Flags.custom<SnapshotPullModel>({
parse: (input: string): Promise<SnapshotPullModel> => {
const model = readJsonSync(resolve(input));
validateSnapshotPullModel(model);
return model;
},
})({
char: 'm',
helpValue: 'path/to/snapshot.json',
exclusive: ['snapshotId', 'resourceTypes', 'target'],
description:
'The path to a snapshot pull model. This flag is useful when you want to include only specific resource items in your snapshot (e.g., a subset of sources). Use the "org:resources:model:create" command to create a new Snapshot Pull Model',
}),
};
public static examples: Example[] = [
{
command: 'coveo org:resources:pull',
description:
'Pull all resources from the organization in which you are authenticated',
},
{
command: 'coveo org:resources:pull --organization myorgid --wait 0',
description:
'Pull all resources from the organization whose ID is "myorgid" and do not timeout',
},
{
command: 'coveo org:resources:pull --model my/snapshot/pull/model.json',
description:
'Pull only the resources specified in the snapshot pull model',
},
{
command: `coveo org:resources:pull --resourceTypes ${ResourceSnapshotType.queryPipeline} ${ResourceSnapshotType.field},`,
description:
'Pull all query pipelines and fields available in the organization',
},
];
@Trackable()
@Preconditions(
IsAuthenticated(),
IsGitInstalled(),
HasNecessaryCoveoPrivileges(writeSnapshotPrivilege)
)
public async run() {
const targetOrganization = await this.getTargetOrg();
const project = new Project(this.projectPath, targetOrganization);
await this.ensureProjectReset(project);
const snapshot = await this.getSnapshot();
startSpinner('Updating project with Snapshot');
await this.refreshProject(project, snapshot);
project.writeResourcesManifest(targetOrganization);
if (await this.shouldDeleteSnapshot()) {
await snapshot.delete();
}
stopSpinner();
}
private async shouldDeleteSnapshot() {
return !(await this.parse(Pull)).flags.snapshotId;
}
public async catch(err?: Error & {exitCode?: number}) {
cleanupProject(this.projectPath);
await this.supplementErrorMessage(err);
return super.catch(err);
}
private async supplementErrorMessage(err?: Error & {exitCode?: number}) {
if (err instanceof SnapshotOperationTimeoutError) {
const snapshot = err.snapshot;
const target = await this.getTargetOrg();
err.message += PullCommandStrings.howToPullAfterTimeout(
target,
snapshot.id
);
}
}
private async refreshProject(project: Project, snapshot: Snapshot) {
const flags = await this.getFlags();
if (flags.git && !project.contains('.git')) {
await spawnProcess('git', ['init', `${this.projectPath}`], {
stdio: 'ignore',
});
}
const snapshotBlob = await snapshot.download();
await project.refresh(snapshotBlob);
}
private async ensureProjectReset(project: Project) {
if (await this.shouldAbortProjectReset(project)) {
throw new ProcessAbort();
}
project.reset();
}
private async shouldAbortProjectReset(project: Project) {
const flags = await this.getFlags();
return (
!flags.overwrite &&
project.contains(Project.resourceFolderName) &&
!(await this.askUserShouldWeOverwrite())
);
}
private askUserShouldWeOverwrite() {
const question = PullCommandStrings.projectOverwriteQuestion(
Project.resourceFolderName
);
return confirmWithAnalytics(question, 'project overwrite');
}
private async getSnapshot() {
const flags = await this.getFlags();
const target = await this.getTargetOrg();
return flags.snapshotId
? this.getExistingSnapshot(flags.snapshotId, target)
: this.createAndGetNewSnapshot(target);
}
private async createAndGetNewSnapshot(target: string) {
const resourcesToExport = await this.getResourceSnapshotTypesToExport();
startSpinner(`Creating Snapshot from ${formatOrgId(target)}`);
const waitOption = await this.getWaitOption();
return SnapshotFactory.createFromOrg(resourcesToExport, target, waitOption);
}
private async getExistingSnapshot(snapshotId: string, target: string) {
startSpinner('Retrieving Snapshot');
const waitOption = await this.getWaitOption();
return SnapshotFactory.createFromExistingSnapshot(
snapshotId,
target,
waitOption
);
}
private async getWaitOption(): Promise<WaitUntilDoneOptions> {
const flags = await this.getFlags();
return {wait: flags.wait};
}
private get configuration() {
return new Config(this.config.configDir);
}
private async getResourceSnapshotTypesToExport(): Promise<SnapshotPullModelResources> {
const flags = await this.getFlags();
return flags.model
? flags.model.resourcesToExport
: buildResourcesToExport(flags.resourceTypes!);
}
private async getTargetOrg() {
const flags = await this.getFlags();
return getTargetOrg(
this.configuration,
flags.model?.orgId || flags.organization
);
}
public async getFlags() {
const {flags} = await this.parse(Pull);
if (flags.model) {
return {...flags, organization: flags.model.orgId};
}
return flags;
}
private get projectPath() {
return cwd();
}
}