@uipath/solution-tool 1.204.0-preview.255 → 1.205.0-preview.256
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/README.md +3 -3
- package/dist/THIRD-PARTY-NOTICES.md +2 -2
- package/dist/archive-extract-x35t9ktj.js +120 -0
- package/dist/cleanup-command-service-n4e7vxnw.js +149 -0
- package/dist/deploy-delete-service-cag0q8yx.js +25 -0
- package/dist/deploy-run-service-f0h8wb9w.js +25 -0
- package/dist/{deploy-status-service-6svq7c6w.js → deploy-status-service-yh6xn6e0.js} +41 -33
- package/dist/deploy.js +23 -17
- package/dist/{deployment-search-v2qtz23x.js → deployment-search-mnywd9nc.js} +16 -10
- package/dist/{embedded-file-reader-rf2g1kfj.js → embedded-file-reader-z4becc02.js} +2 -2
- package/dist/{feed-resolver-xz3akamf.js → feed-resolver-fve33dxq.js} +10 -9
- package/dist/feed-scope-failure-jmg4t1r1.js +19 -0
- package/dist/{feed-session-sxga31vn.js → feed-session-7xqphdrg.js} +3 -3
- package/dist/governance-options-4vxz7ekn.js +32 -0
- package/dist/index-g8c8nf1c.js +667 -0
- package/dist/index-prm27bvm.js +138 -0
- package/dist/index.js +27 -29
- package/dist/init.js +11 -10
- package/dist/{list-tjetkspc.js → list-50mepbed.js} +10 -8
- package/dist/models/pack-command-types.d.ts +15 -0
- package/dist/node-7jm7ksqa.js +86 -0
- package/dist/pack-command-service-dewf5vsq.js +35 -0
- package/dist/pack-project-xwd1rbaa.js +97 -0
- package/dist/pack.js +24 -14
- package/dist/package-id-1wr3cy9c.js +13 -0
- package/dist/packager-tool-09swr9cp.js +171 -0
- package/dist/{packager-tool-a9jx8gf4.js → packager-tool-2vmsh5pt.js} +196 -1519
- package/dist/{packager-tool-px1j9kre.js → packager-tool-3bwnkjdq.js} +2 -2
- package/dist/{packager-tool-gb35b4qs.js → packager-tool-43ahv5mn.js} +192 -96
- package/dist/{packager-tool-9ha2z8h5.js → packager-tool-47by487p.js} +6 -4
- package/dist/packager-tool-47wzvqw0.js +86 -0
- package/dist/{packager-tool-zns6n8zp.js → packager-tool-5ddtwzva.js} +1 -1
- package/dist/packager-tool-5edvzz80.js +543 -0
- package/dist/{packager-tool-jq9zw82w.js → packager-tool-5n6jsn41.js} +104 -14
- package/dist/packager-tool-60af1hw2.js +26 -0
- package/dist/{packager-tool-f5gq77m5.js → packager-tool-61pwb567.js} +2 -2
- package/dist/{packager-tool-yrz4mwfn.js → packager-tool-6kz1pv9f.js} +1373 -486
- package/dist/packager-tool-6ybywxxn.js +1809 -0
- package/dist/packager-tool-8ptbmmmp.js +2132 -0
- package/dist/{packager-tool-7gr2nphb.js → packager-tool-97rcgwvv.js} +224 -6
- package/dist/{packager-tool-83m6ns0r.js → packager-tool-9t26kde1.js} +3556 -1646
- package/dist/packager-tool-act5r386.js +239 -0
- package/dist/packager-tool-amhyxzz6.js +125 -0
- package/dist/packager-tool-b03rva43.js +188 -0
- package/dist/{packager-tool-e2rtr49j.js → packager-tool-b2nhscfk.js} +15 -31
- package/dist/{packager-tool-tv9c1e80.js → packager-tool-d6rrfd46.js} +149 -53
- package/dist/packager-tool-frr2xmkr.js +445 -0
- package/dist/{packager-tool-v8bkkqdb.js → packager-tool-gmwva4yv.js} +5 -5
- package/dist/packager-tool-gs3f3tf7.js +103 -0
- package/dist/packager-tool-jg692f13.js +517 -0
- package/dist/packager-tool-jmpb786m.js +109 -0
- package/dist/{packager-tool-5sex4a1h.js → packager-tool-kds8ytzw.js} +74 -23
- package/dist/packager-tool-kkfb5kgn.js +182 -0
- package/dist/packager-tool-kvcxg0zv.js +1510 -0
- package/dist/packager-tool-n3jfjn5r.js +42 -0
- package/dist/{packager-tool-fjsn5h8s.js → packager-tool-n5d5dx5j.js} +2196 -3455
- package/dist/{packager-tool-yfpcvdpm.js → packager-tool-nsya65z1.js} +357 -1370
- package/dist/{packager-tool-fv7xyzg2.js → packager-tool-pbmqk39a.js} +126 -128
- package/dist/{packager-tool-bqre28m1.js → packager-tool-qw47eyw7.js} +1 -1
- package/dist/{packager-tool-9qgmwy21.js → packager-tool-rc3hwn4b.js} +25 -4
- package/dist/packager-tool-resolver-994z3jkj.js +23 -0
- package/dist/{packager-tool-tgw5x6vx.js → packager-tool-rvt68c1t.js} +17 -8
- package/dist/{packager-tool-gab9y5ct.js → packager-tool-t8gh825q.js} +37 -13
- package/dist/{packager-tool-kswwd3hp.js → packager-tool-tx834spe.js} +2376 -4077
- package/dist/{packager-tool-zf3929se.js → packager-tool-v1f5e8t9.js} +11 -1
- package/dist/{packager-tool-ssq2kzqm.js → packager-tool-w77d72a7.js} +13 -11
- package/dist/{packager-tool-1rzssy0x.js → packager-tool-wy4fx250.js} +19 -18
- package/dist/packager-tool-wz34ma3s.js +1333 -0
- package/dist/packager-tool-zq16e1xv.js +68 -0
- package/dist/packager-tool.js +2 -2
- package/dist/prepare-solution-resources-m1djfj62.js +29 -0
- package/dist/project-command-utils-t6pmpvq3.js +90 -0
- package/dist/project-contributions-79ndycrx.js +32 -0
- package/dist/publish.js +13 -12
- package/dist/resource-mutations-c3krjqze.js +36 -0
- package/dist/resource-version-lookup-0e3e2j58.js +99 -0
- package/dist/resource.js +10 -9
- package/dist/restore-command-service-5v0b9363.js +155 -0
- package/dist/run-service-tg6b67wp.js +428 -0
- package/dist/services/deploy-activate-service.d.ts +10 -5
- package/dist/services/deploy-delete-service.d.ts +8 -4
- package/dist/services/deploy-run-service.d.ts +34 -1
- package/dist/services/deploy-uninstall-service.d.ts +21 -3
- package/dist/services/deploy-upgrade-service.d.ts +35 -1
- package/dist/services/deployment-feed-locator.d.ts +15 -4
- package/dist/services/deployment-search-client.d.ts +5 -53
- package/dist/services/deployment-search.d.ts +58 -3
- package/dist/services/deployment-state.d.ts +84 -24
- package/dist/services/deployment-validation.d.ts +5 -0
- package/dist/services/emit-runtime-dependencies.d.ts +2 -1
- package/dist/services/feed-resolver.d.ts +6 -4
- package/dist/services/feed-selection.d.ts +47 -0
- package/dist/services/http-status.d.ts +2 -7
- package/dist/services/pack-command-service.d.ts +9 -0
- package/dist/services/pack-project-selection.d.ts +51 -0
- package/dist/services/pack-project.d.ts +26 -0
- package/dist/services/pack-service.d.ts +7 -3
- package/dist/services/package-search-client.d.ts +7 -0
- package/dist/services/packager-tool-resolver.d.ts +7 -1
- package/dist/services/packages-list-service.d.ts +1 -1
- package/dist/services/permanent-deletion-consent.d.ts +110 -0
- package/dist/services/pipeline-deploy-poll.d.ts +7 -0
- package/dist/services/prepare-solution-resources.d.ts +12 -2
- package/dist/services/project-contributions.d.ts +5 -4
- package/dist/services/search-failure.d.ts +9 -13
- package/dist/services/solution-packager-helpers.d.ts +5 -2
- package/dist/services/solution-path.d.ts +1 -1
- package/dist/services/uninstall-consent.d.ts +82 -0
- package/dist/ship-telemetry-myes7ak5.js +21 -0
- package/dist/solution-auth-aead70sw.js +11 -0
- package/dist/templates/AGENTS.md +125 -12
- package/dist/tool.js +27 -29
- package/dist/utils/deployment-errors.d.ts +9 -1
- package/package.json +2 -2
- package/dist/deploy-delete-service-w3e8x1kq.js +0 -22
- package/dist/feed-scope-failure-0j5byccb.js +0 -18
- package/dist/packager-tool-f2e9rzxk.js +0 -104
- package/dist/packager-tool-resolver-6zwhec62.js +0 -20
- package/dist/packager-tool-zdfn32bc.js +0 -4465
- package/dist/prepare-solution-resources-jpb5as2s.js +0 -25
- package/dist/project-contributions-6wpff2hm.js +0 -30
- package/dist/services/legacy-deployment-search-v3.d.ts +0 -23
|
@@ -0,0 +1,1510 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveFeedSelectionAsync
|
|
3
|
+
} from "./packager-tool-zq16e1xv.js";
|
|
4
|
+
import {
|
|
5
|
+
INCOMPLETE_SWEEP_ADVICE,
|
|
6
|
+
findDeploymentAcrossFeeds,
|
|
7
|
+
isNameOrFeedRefusal,
|
|
8
|
+
reportIncompleteSweepAsync,
|
|
9
|
+
retryAdvice
|
|
10
|
+
} from "./packager-tool-rc3hwn4b.js";
|
|
11
|
+
import {
|
|
12
|
+
ACKNOWLEDGE_PERMANENT_DELETION_FLAG,
|
|
13
|
+
NO_SERVER_REASON,
|
|
14
|
+
acknowledgedButNotRunHint,
|
|
15
|
+
activateDeployment,
|
|
16
|
+
describeValidationFailureAsync,
|
|
17
|
+
formatPermanentDeletionResources,
|
|
18
|
+
isPermanentDeletionRefusal,
|
|
19
|
+
listPermanentDeletionResourcesAsync,
|
|
20
|
+
pollPipelineDeploymentAsync,
|
|
21
|
+
remainingTimeoutMs,
|
|
22
|
+
resolveParentFolder,
|
|
23
|
+
settlePermanentDeletionRefusalAsync,
|
|
24
|
+
stillRequiresConfirmation
|
|
25
|
+
} from "./packager-tool-frr2xmkr.js";
|
|
26
|
+
import {
|
|
27
|
+
deploymentNextSteps,
|
|
28
|
+
explainRefusal,
|
|
29
|
+
isCurrentRecordAsync,
|
|
30
|
+
needsPendingResources,
|
|
31
|
+
pendingResourceApis,
|
|
32
|
+
readPendingResourcesAsync,
|
|
33
|
+
reportedActivationStatus
|
|
34
|
+
} from "./packager-tool-5n6jsn41.js";
|
|
35
|
+
import {
|
|
36
|
+
DRAFT_VERSION_MISMATCH_CODE,
|
|
37
|
+
coerceWorkflowErrorMessages,
|
|
38
|
+
explainPipelineStatusAfterInstall,
|
|
39
|
+
formatDeploymentFailureDetails,
|
|
40
|
+
formatServiceErrors,
|
|
41
|
+
hasServerErrorCode,
|
|
42
|
+
installLandedDespiteStatus,
|
|
43
|
+
isBareResponse,
|
|
44
|
+
leavesPendingDraft
|
|
45
|
+
} from "./packager-tool-act5r386.js";
|
|
46
|
+
import {
|
|
47
|
+
DeploymentSearchNeedsUserSessionError,
|
|
48
|
+
findDeploymentByName,
|
|
49
|
+
findDeploymentsByName,
|
|
50
|
+
isApplicationSession,
|
|
51
|
+
isAuthRefusal,
|
|
52
|
+
isCompletedUninstall,
|
|
53
|
+
isMissingPipelinesRoute,
|
|
54
|
+
newestDeploymentRow,
|
|
55
|
+
parseSearchPage,
|
|
56
|
+
readDeploymentByKeyAsync,
|
|
57
|
+
searchDeploymentsPage
|
|
58
|
+
} from "./packager-tool-d6rrfd46.js";
|
|
59
|
+
import {
|
|
60
|
+
describeFeedResolutionFailureAsync,
|
|
61
|
+
feedScopeInitOverride,
|
|
62
|
+
feedSelectorFlag,
|
|
63
|
+
folderKeyInitOverride,
|
|
64
|
+
resolveFeedScope,
|
|
65
|
+
resolvePersonalWorkspace
|
|
66
|
+
} from "./packager-tool-rvt68c1t.js";
|
|
67
|
+
import {
|
|
68
|
+
Configuration,
|
|
69
|
+
DeploymentOperation,
|
|
70
|
+
DeploymentStatus,
|
|
71
|
+
PipelinesApi
|
|
72
|
+
} from "./packager-tool-97rcgwvv.js";
|
|
73
|
+
import {
|
|
74
|
+
Configuration as Configuration2,
|
|
75
|
+
DeploymentsApi,
|
|
76
|
+
PackageInfoFromJSON,
|
|
77
|
+
SearchApi,
|
|
78
|
+
SolutionDeploymentAction,
|
|
79
|
+
WorkflowAction
|
|
80
|
+
} from "./packager-tool-9t26kde1.js";
|
|
81
|
+
import {
|
|
82
|
+
classifyFeedSession,
|
|
83
|
+
feedReadRefusalForApplication,
|
|
84
|
+
feedSessionRefusal
|
|
85
|
+
} from "./packager-tool-qw47eyw7.js";
|
|
86
|
+
import {
|
|
87
|
+
getSolutionAuthContext
|
|
88
|
+
} from "./packager-tool-61pwb567.js";
|
|
89
|
+
import {
|
|
90
|
+
DEFAULT_PAGE_SIZE,
|
|
91
|
+
PollOutcome,
|
|
92
|
+
logger,
|
|
93
|
+
mapPollFailure,
|
|
94
|
+
pollUntil,
|
|
95
|
+
quoteForShell
|
|
96
|
+
} from "./packager-tool-tx834spe.js";
|
|
97
|
+
import {
|
|
98
|
+
catchError,
|
|
99
|
+
extractErrorDetails
|
|
100
|
+
} from "./packager-tool-6ybywxxn.js";
|
|
101
|
+
|
|
102
|
+
// src/services/deploy-activate-service.ts
|
|
103
|
+
var fail = (reason, message, instructions, exitCode = 1) => ({
|
|
104
|
+
ok: false,
|
|
105
|
+
reason,
|
|
106
|
+
message,
|
|
107
|
+
instructions,
|
|
108
|
+
exitCode
|
|
109
|
+
});
|
|
110
|
+
var AUTH_INSTRUCTIONS = "Run 'uip login' to refresh credentials, then retry activation. If the deployment is in a different tenant, switch with 'uip login tenant set <tenant>'.";
|
|
111
|
+
async function incompleteSweepRefusalAsync(deploymentName, skipped, result) {
|
|
112
|
+
const details = `Details: ${result.details}`;
|
|
113
|
+
if (!isNameOrFeedRefusal(result.errorCode, result.httpStatus)) {
|
|
114
|
+
return {
|
|
115
|
+
...fail("request_failed", result.message, `The activation request failed. ${retryAdvice(result.errorCode, result.retry, "Orchestrator")} ${details}`),
|
|
116
|
+
errorCode: result.errorCode,
|
|
117
|
+
retry: result.retry
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const report = await reportIncompleteSweepAsync(deploymentName, skipped);
|
|
121
|
+
return {
|
|
122
|
+
...fail("request_failed", report.message, `${INCOMPLETE_SWEEP_ADVICE} Without its feed the activation went to the tenant feed, which refused it. ${report.nextStep} ${details} (feed search: ${report.details})`),
|
|
123
|
+
errorCode: report.errorCode,
|
|
124
|
+
retry: report.retry
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function activateDeploymentAsync(deploymentName, options = {}) {
|
|
128
|
+
const [authError, auth] = await catchError(getSolutionAuthContext({
|
|
129
|
+
tenant: options.tenant,
|
|
130
|
+
loginValidity: options.loginValidity,
|
|
131
|
+
envFilePath: options.envFilePath
|
|
132
|
+
}));
|
|
133
|
+
if (authError) {
|
|
134
|
+
return fail("auth_failed", authError.message, AUTH_INSTRUCTIONS);
|
|
135
|
+
}
|
|
136
|
+
const selection = await resolveFeedSelectionAsync(auth, options, "Activating a deployment");
|
|
137
|
+
if (!selection.ok) {
|
|
138
|
+
return {
|
|
139
|
+
...fail(selection.reason, selection.message, selection.instructions),
|
|
140
|
+
errorCode: selection.errorCode,
|
|
141
|
+
retry: selection.retry
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const { selectedFeed } = selection;
|
|
145
|
+
const sweep = await findDeploymentAcrossFeeds(auth, deploymentName, {
|
|
146
|
+
tenant: options.tenant,
|
|
147
|
+
loginValidity: options.loginValidity,
|
|
148
|
+
envFilePath: options.envFilePath,
|
|
149
|
+
selectedFeed
|
|
150
|
+
});
|
|
151
|
+
const location = sweep.location;
|
|
152
|
+
const scope = location?.scope ?? (selectedFeed ? feedScopeInitOverride(selectedFeed) : undefined);
|
|
153
|
+
const result = await activateDeployment(auth, deploymentName, {
|
|
154
|
+
timeoutSeconds: options.timeout ?? 360,
|
|
155
|
+
pollIntervalMs: options.pollInterval ?? 5000,
|
|
156
|
+
signal: options.signal,
|
|
157
|
+
scope
|
|
158
|
+
});
|
|
159
|
+
if (result.kind === "RequestFailed") {
|
|
160
|
+
const details = `Details: ${result.details}`;
|
|
161
|
+
const known = result.deployment ?? location?.deployment;
|
|
162
|
+
if (!known && !sweep.complete) {
|
|
163
|
+
return incompleteSweepRefusalAsync(deploymentName, sweep.skipped, result);
|
|
164
|
+
}
|
|
165
|
+
const pendingResources = known && needsPendingResources(known) ? await readPendingResourcesAsync(pendingResourceApis(auth), known, scope) : undefined;
|
|
166
|
+
const feedSelector = feedSelectorFlag(location?.feed ?? selectedFeed);
|
|
167
|
+
const explanation = known ? explainRefusal(known, pendingResources, feedSelector) : undefined;
|
|
168
|
+
const instructions = explanation ? [...explanation, details].join(`
|
|
169
|
+
`) : known ? `Deployment '${known.name}' exists (Operation: ${known.operation}, OperationStatus: ${known.operationStatus}, ActivationStatus: ${known.activationStatus}) but the service refused to activate it. Check it with: uip solution deploy status ${known.key}${feedSelector}. ${details}` : selectedFeed ? `No deployment named '${deploymentName}' was found in the selected feed. Check the name with 'uip solution deploy list${feedSelector}', or drop the feed flag to search every feed you can reach. ${details}` : result.phase === "preActivateSteps" ? `Check deployment name. Deploy first with: uip solution deploy run. ${details}` : `Check deployment name matches an existing deployment. ${details}`;
|
|
170
|
+
return fail("request_failed", result.message, instructions);
|
|
171
|
+
}
|
|
172
|
+
if (result.kind === "NoInstance") {
|
|
173
|
+
return {
|
|
174
|
+
ok: true,
|
|
175
|
+
status: "Activation completed (no instance to poll)",
|
|
176
|
+
deploymentName
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (result.kind === "PollFailed") {
|
|
180
|
+
const { reason, message, exitCode } = mapPollFailure(result.poll, "Activation");
|
|
181
|
+
return fail(reason, message, "Activation may still be in progress.", exitCode);
|
|
182
|
+
}
|
|
183
|
+
if (result.kind === "ActivationFailed") {
|
|
184
|
+
return fail("activation_failed", `Activation failed with status: ${result.status}`, result.errorMessage);
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
ok: true,
|
|
188
|
+
status: result.status,
|
|
189
|
+
deploymentName,
|
|
190
|
+
instanceId: result.instanceId
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/services/search-failure.ts
|
|
195
|
+
async function describeSearchFailureAsync(error, what) {
|
|
196
|
+
const details = await extractErrorDetails(error);
|
|
197
|
+
return {
|
|
198
|
+
message: `Failed to list ${what}: ${details.message}`,
|
|
199
|
+
instructions: searchFailureInstructions(details.errorCode, details.retry),
|
|
200
|
+
errorCode: details.errorCode,
|
|
201
|
+
retry: details.retry,
|
|
202
|
+
context: details.context
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function searchFailureInstructions(errorCode, retry) {
|
|
206
|
+
if (errorCode === "authentication_required") {
|
|
207
|
+
return "Run 'uip login' to refresh the session, then run the command again.";
|
|
208
|
+
}
|
|
209
|
+
if (errorCode === "permission_denied") {
|
|
210
|
+
return "Use an account that can read Automation Solutions in this tenant.";
|
|
211
|
+
}
|
|
212
|
+
if (retry === "RetryLater") {
|
|
213
|
+
return "Wait a moment, then run the command again.";
|
|
214
|
+
}
|
|
215
|
+
return "Check the options and the session ('uip login status --detailed'), then run the command again.";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/services/deploy-list-service.ts
|
|
219
|
+
var fail2 = (reason, message, instructions, extras = {}) => ({
|
|
220
|
+
ok: false,
|
|
221
|
+
reason,
|
|
222
|
+
message,
|
|
223
|
+
instructions,
|
|
224
|
+
exitCode: 1,
|
|
225
|
+
...extras
|
|
226
|
+
});
|
|
227
|
+
var refuseFeedRead = (refusal) => fail2("feed_requires_user_session", refusal.message, refusal.instructions, {
|
|
228
|
+
errorCode: "permission_denied",
|
|
229
|
+
retry: "RetryWillNotFix"
|
|
230
|
+
});
|
|
231
|
+
async function listDeploymentsAsync(options = {}) {
|
|
232
|
+
const limit = options.limit ?? DEFAULT_PAGE_SIZE;
|
|
233
|
+
const offset = options.offset ?? 0;
|
|
234
|
+
const [authError, auth] = await catchError(getSolutionAuthContext({
|
|
235
|
+
tenant: options.tenant,
|
|
236
|
+
loginValidity: options.loginValidity,
|
|
237
|
+
envFilePath: options.envFilePath
|
|
238
|
+
}));
|
|
239
|
+
if (authError) {
|
|
240
|
+
return fail2("auth_failed", authError.message, authError.message);
|
|
241
|
+
}
|
|
242
|
+
if (options.personalWorkspace) {
|
|
243
|
+
const refusal = feedReadRefusalForApplication(auth, "Listing the Personal Workspace's deployments");
|
|
244
|
+
if (refusal) {
|
|
245
|
+
return refuseFeedRead(refusal);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const [folderError, folderPathFilter] = await catchError(resolveParentFolder({
|
|
249
|
+
folderPath: options.folderPath,
|
|
250
|
+
folderKey: options.folderKey,
|
|
251
|
+
tenant: options.tenant,
|
|
252
|
+
loginValidity: options.loginValidity,
|
|
253
|
+
envFilePath: options.envFilePath
|
|
254
|
+
}));
|
|
255
|
+
if (folderError) {
|
|
256
|
+
return fail2("folder_resolution_failed", folderError.message, "Provide a valid --folder-path or --folder-key to filter by folder.");
|
|
257
|
+
}
|
|
258
|
+
const [scopeError, scope] = await catchError(resolveFeedScope({
|
|
259
|
+
personalWorkspace: options.personalWorkspace,
|
|
260
|
+
feed: options.feed,
|
|
261
|
+
tenant: options.tenant,
|
|
262
|
+
loginValidity: options.loginValidity,
|
|
263
|
+
envFilePath: options.envFilePath
|
|
264
|
+
}));
|
|
265
|
+
if (scopeError) {
|
|
266
|
+
const { message, instructions, ...extras } = await describeFeedResolutionFailureAsync(scopeError, options, auth);
|
|
267
|
+
return fail2("feed_resolution_failed", message, instructions, extras);
|
|
268
|
+
}
|
|
269
|
+
if (scope.kind !== "tenant") {
|
|
270
|
+
const refusal = feedReadRefusalForApplication(auth, `Listing the deployments of the feed '${scope.name ?? scope.folderKey}'`);
|
|
271
|
+
if (refusal) {
|
|
272
|
+
return refuseFeedRead(refusal);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const [listError, result] = await catchError(searchDeploymentsPage(auth, {
|
|
276
|
+
take: limit,
|
|
277
|
+
skip: offset,
|
|
278
|
+
orderByColumn: options.sortBy ?? "startTime",
|
|
279
|
+
orderByDirection: options.sortOrder ?? "Descending",
|
|
280
|
+
operationStatuses: options.operationStatuses ?? [],
|
|
281
|
+
activationStatuses: options.activationStatuses ?? [],
|
|
282
|
+
...folderPathFilter ? { targetFolderName: folderPathFilter } : {}
|
|
283
|
+
}, feedScopeInitOverride(scope)));
|
|
284
|
+
if (listError instanceof DeploymentSearchNeedsUserSessionError) {
|
|
285
|
+
return fail2("list_request_failed", `Failed to list deployments: ${listError.message}`, "Run it with a user session ('uip login'). A client-credentials login cannot list deployments on this server.", { errorCode: "permission_denied", retry: "RetryWillNotFix" });
|
|
286
|
+
}
|
|
287
|
+
if (listError) {
|
|
288
|
+
const { message, instructions, ...extras } = await describeSearchFailureAsync(listError, "deployments");
|
|
289
|
+
return fail2("list_request_failed", message, instructions, extras);
|
|
290
|
+
}
|
|
291
|
+
const values = result.values ?? [];
|
|
292
|
+
const deployments = values.map((d) => ({
|
|
293
|
+
key: d.key,
|
|
294
|
+
installDeploymentKey: d.installDeploymentKey,
|
|
295
|
+
name: d.name,
|
|
296
|
+
packageName: d.packageName,
|
|
297
|
+
packageVersion: d.packageVersion,
|
|
298
|
+
currentPackageVersion: d.currentPackageVersion,
|
|
299
|
+
targetPackageVersion: d.targetPackageVersion,
|
|
300
|
+
newPackageVersionAvailable: d.newPackageVersionAvailable,
|
|
301
|
+
operation: d.operation,
|
|
302
|
+
operationStatus: d.operationStatus,
|
|
303
|
+
activationStatus: d.activationStatus,
|
|
304
|
+
folderPath: d.folderPath,
|
|
305
|
+
installedRootFolderKey: d.installedRootFolderKey,
|
|
306
|
+
deploymentCreationTime: d.deploymentCreationTime,
|
|
307
|
+
actions: d.actions ?? []
|
|
308
|
+
}));
|
|
309
|
+
return { ok: true, deployments, limit, offset, total: result.count };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/services/uninstall-consent.ts
|
|
313
|
+
var JOBS_NOT_IN_FINAL_STATE_CODE = "2824";
|
|
314
|
+
var UNINSTALL_CONFIRMATION_REQUIRED_CODE = "4026";
|
|
315
|
+
function isJobsNotInFinalState(row) {
|
|
316
|
+
return row.errorCode === JOBS_NOT_IN_FINAL_STATE_CODE;
|
|
317
|
+
}
|
|
318
|
+
function isRequiresConfirmation(row) {
|
|
319
|
+
return row.errorKind === "RequiresConfirmation";
|
|
320
|
+
}
|
|
321
|
+
function isForceable(row) {
|
|
322
|
+
return isJobsNotInFinalState(row) || isRequiresConfirmation(row);
|
|
323
|
+
}
|
|
324
|
+
function uninstallConsentFacts(rows) {
|
|
325
|
+
return {
|
|
326
|
+
jobsNotInFinalState: rows.some(isJobsNotInFinalState),
|
|
327
|
+
requiresConfirmation: rows.some(isRequiresConfirmation),
|
|
328
|
+
permanentDeletionResources: selectPermanentDeletionResources(rows)
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function selectPermanentDeletionResources(rows) {
|
|
332
|
+
const byKey = new Map;
|
|
333
|
+
for (const row of rows) {
|
|
334
|
+
if (row.errorKind !== "RequiresConfirmation" || row.level !== "Warning") {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const identity = row.key ?? `${row.kind ?? ""}/${row.name ?? ""}`;
|
|
338
|
+
if (byKey.has(identity)) {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
byKey.set(identity, {
|
|
342
|
+
key: row.key ?? undefined,
|
|
343
|
+
kind: row.kind ?? undefined,
|
|
344
|
+
name: row.name ?? undefined
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return [...byKey.values()];
|
|
348
|
+
}
|
|
349
|
+
function isBlocking(row) {
|
|
350
|
+
return !isForceable(row) && row.level !== "Warning" && row.level !== "Info";
|
|
351
|
+
}
|
|
352
|
+
function describeBlockingRows(rows) {
|
|
353
|
+
return rows.filter(isBlocking).map((row) => {
|
|
354
|
+
const subject = row.name ? `${row.kind ? `${row.kind} ` : ""}'${row.name}': ` : "";
|
|
355
|
+
const code = row.errorCode ? `[${row.errorCode}] ` : "";
|
|
356
|
+
const text = row.text || row.errorKind || row.errorType;
|
|
357
|
+
return `${subject}${code}${text}`;
|
|
358
|
+
}).join("; ");
|
|
359
|
+
}
|
|
360
|
+
function classifyUninstallValidation(result) {
|
|
361
|
+
const rows = result.validationErrors ?? [];
|
|
362
|
+
let outcome = "clear";
|
|
363
|
+
if (rows.some(isBlocking)) {
|
|
364
|
+
outcome = "blocked";
|
|
365
|
+
} else if (rows.some(isForceable)) {
|
|
366
|
+
outcome = "force-required";
|
|
367
|
+
}
|
|
368
|
+
return { outcome, ...uninstallConsentFacts(rows) };
|
|
369
|
+
}
|
|
370
|
+
async function readUninstallValidationAsync(api, deploymentName, deploymentKey, scope) {
|
|
371
|
+
if (!deploymentKey) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const [readError, result] = await catchError(() => api.pipelinesGetDeploymentValidationResult({ deploymentName, deploymentAction: "Uninstall" }, scope));
|
|
375
|
+
if (readError || !result || result.deploymentAction !== "Uninstall") {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (!await isCurrentRecordAsync(api, deploymentKey)) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
function servicesReportJobsNotInFinalState(serviceLines) {
|
|
384
|
+
return serviceLines.some((line) => line.includes(`[${JOBS_NOT_IN_FINAL_STATE_CODE}]`));
|
|
385
|
+
}
|
|
386
|
+
function formatPermanentDeletionResource(resource) {
|
|
387
|
+
const label = resource.name ?? resource.key ?? "unnamed resource";
|
|
388
|
+
return resource.kind ? `${resource.kind} '${label}'` : `'${label}'`;
|
|
389
|
+
}
|
|
390
|
+
function describePermanentDeletion(resources) {
|
|
391
|
+
return resources.length > 0 ? `uninstalling it permanently deletes these resources and their data: ${resources.map(formatPermanentDeletionResource).join(", ")}` : "uninstalling it permanently deletes resources and their data";
|
|
392
|
+
}
|
|
393
|
+
function describeForceableCauses(facts) {
|
|
394
|
+
const causes = [];
|
|
395
|
+
if (facts.jobsNotInFinalState) {
|
|
396
|
+
causes.push("this deployment has uncompleted jobs; --force uninstalls anyway");
|
|
397
|
+
}
|
|
398
|
+
if (facts.requiresConfirmation) {
|
|
399
|
+
causes.push(describePermanentDeletion(facts.permanentDeletionResources));
|
|
400
|
+
}
|
|
401
|
+
return causes.join("; and ");
|
|
402
|
+
}
|
|
403
|
+
function consentData(facts, extra) {
|
|
404
|
+
return {
|
|
405
|
+
DeploymentName: extra.deploymentName,
|
|
406
|
+
...extra.instanceId ? { InstanceId: extra.instanceId } : {},
|
|
407
|
+
JobsNotInFinalState: facts.jobsNotInFinalState,
|
|
408
|
+
PermanentDeletionResources: facts.permanentDeletionResources.map((resource) => ({
|
|
409
|
+
Key: resource.key ?? null,
|
|
410
|
+
Kind: resource.kind ?? null,
|
|
411
|
+
Name: resource.name ?? null
|
|
412
|
+
}))
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/services/deploy-uninstall-service.ts
|
|
417
|
+
var TERMINAL_STATUSES = new Set([
|
|
418
|
+
"SuccessfulUninstall",
|
|
419
|
+
"FailedUninstall"
|
|
420
|
+
]);
|
|
421
|
+
var fail3 = (reason, message, instructions, extras = {}) => ({
|
|
422
|
+
ok: false,
|
|
423
|
+
reason,
|
|
424
|
+
message,
|
|
425
|
+
instructions,
|
|
426
|
+
exitCode: extras.exitCode ?? 1,
|
|
427
|
+
errorCode: extras.errorCode,
|
|
428
|
+
retry: extras.retry,
|
|
429
|
+
...extras.data ? { data: extras.data } : {}
|
|
430
|
+
});
|
|
431
|
+
function forceRetryCommand(deploymentName, feedSelector) {
|
|
432
|
+
return `uip solution deploy uninstall ${quoteForShell(deploymentName)}${feedSelector} --force`;
|
|
433
|
+
}
|
|
434
|
+
function consentFailure(message, instructions, facts, ids) {
|
|
435
|
+
return fail3("uninstall_confirmation_required", message, instructions, {
|
|
436
|
+
errorCode: "invalid_argument",
|
|
437
|
+
retry: "RetryWillNotFix",
|
|
438
|
+
data: consentData(facts, ids)
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
function blockedUninstallFailure(deploymentName, feedSelector, rows) {
|
|
442
|
+
return fail3("uninstall_failed", `Deployment '${deploymentName}' was not uninstalled: its Uninstall validation reports errors that --force cannot get past: ${describeBlockingRows(rows)}.`, `Resolve the reason above in Orchestrator, then run the uninstall again: uip solution deploy uninstall ${quoteForShell(deploymentName)}${feedSelector} --yes. It then asks again to confirm the permanent deletion of the resources that were replaced by links.`, { errorCode: "invalid_argument", retry: "RetryLater" });
|
|
443
|
+
}
|
|
444
|
+
async function confirmationRequiredFailureAsync(api, deploymentName, ctx) {
|
|
445
|
+
const { feedSelector, details } = ctx;
|
|
446
|
+
const validation = await readUninstallValidationAsync(api, deploymentName, ctx.deploymentKey, ctx.scope);
|
|
447
|
+
if (validation && classifyUninstallValidation(validation).outcome === "blocked") {
|
|
448
|
+
return blockedUninstallFailure(deploymentName, feedSelector, validation.validationErrors ?? []);
|
|
449
|
+
}
|
|
450
|
+
const facts = {
|
|
451
|
+
...uninstallConsentFacts(validation?.validationErrors ?? []),
|
|
452
|
+
requiresConfirmation: true
|
|
453
|
+
};
|
|
454
|
+
const causes = describeForceableCauses(facts);
|
|
455
|
+
return consentFailure(`Deployment '${deploymentName}' was not uninstalled: ${causes}.`, `Re-run with --force to confirm: ${forceRetryCommand(deploymentName, feedSelector)}. Details: ${details}`, facts, { deploymentName });
|
|
456
|
+
}
|
|
457
|
+
async function forceableUninstallFailureAsync(api, deploymentName, ctx) {
|
|
458
|
+
const validation = await readUninstallValidationAsync(api, deploymentName, ctx.deploymentKey, ctx.scope);
|
|
459
|
+
let facts;
|
|
460
|
+
if (validation) {
|
|
461
|
+
const classification = classifyUninstallValidation(validation);
|
|
462
|
+
if (classification.outcome !== "force-required") {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
facts = classification;
|
|
466
|
+
} else if (servicesReportJobsNotInFinalState(ctx.serviceLines)) {
|
|
467
|
+
facts = {
|
|
468
|
+
jobsNotInFinalState: true,
|
|
469
|
+
requiresConfirmation: false,
|
|
470
|
+
permanentDeletionResources: []
|
|
471
|
+
};
|
|
472
|
+
} else {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const causes = describeForceableCauses(facts);
|
|
476
|
+
const retry = forceRetryCommand(deploymentName, ctx.feedSelector);
|
|
477
|
+
const waitForJobs = facts.jobsNotInFinalState ? " Or let the jobs finish (or stop them) in Orchestrator, then run the uninstall again without --force." : "";
|
|
478
|
+
const reported = ctx.errors ? `${ctx.errors}
|
|
479
|
+
` : "";
|
|
480
|
+
return consentFailure(`Uninstall failed with status: FailedUninstall: ${causes}.`, `${reported}Re-run with --force to uninstall anyway: ${retry}.${waitForJobs}`, facts, { deploymentName, instanceId: ctx.instanceId });
|
|
481
|
+
}
|
|
482
|
+
function requestErrorInstructions(deploymentName, errorCode, details, feedSelector = "") {
|
|
483
|
+
switch (errorCode) {
|
|
484
|
+
case "authentication_required":
|
|
485
|
+
return `Your session may have expired. Run 'uip login' and retry. Details: ${details}`;
|
|
486
|
+
case "permission_denied":
|
|
487
|
+
return `Your account lacks permission to uninstall this deployment. Check your Orchestrator roles, then retry. Details: ${details}`;
|
|
488
|
+
case "rate_limited":
|
|
489
|
+
return `Too many requests. Wait a moment and retry. Details: ${details}`;
|
|
490
|
+
case "network_error":
|
|
491
|
+
return `Could not reach Orchestrator. Check your connection and retry. Details: ${details}`;
|
|
492
|
+
case "server_error":
|
|
493
|
+
return `Orchestrator returned a server error. Wait and retry; if it persists, check Orchestrator status. Details: ${details}`;
|
|
494
|
+
case "not_found":
|
|
495
|
+
return `No deployment named '${deploymentName}' was found. Run 'uip solution deploy list${feedSelector}' to see valid deployment names. Details: ${details}`;
|
|
496
|
+
default:
|
|
497
|
+
return `Uninstall request failed. Run 'uip solution deploy list${feedSelector}' to check the deployment's state, then retry. Details: ${details}`;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function buildDeploymentStateInstructions(deployment, details, feedSelector = "") {
|
|
501
|
+
const availableActions = deployment.actions?.length ? deployment.actions.join(", ") : "none";
|
|
502
|
+
const status = `(ActivationStatus: ${deployment.activationStatus}, OperationStatus: ${deployment.operationStatus})`;
|
|
503
|
+
if (deployment.operationStatus === "InProgress") {
|
|
504
|
+
return `Deployment '${deployment.name}' has an operation in progress ${status}, so it can't be uninstalled yet. Wait for it to finish — run 'uip solution deploy list${feedSelector}' to recheck — then retry. Available actions: ${availableActions}. Details: ${details}`;
|
|
505
|
+
}
|
|
506
|
+
if (deployment.actions?.includes("Uninstall")) {
|
|
507
|
+
return `Deployment '${deployment.name}' lists uninstall as available but Orchestrator rejected the request ${status}. Run 'uip solution deploy list${feedSelector}' to recheck its state, then retry. If it stays stuck, uninstall it from the Orchestrator Solutions UI. Details: ${details}`;
|
|
508
|
+
}
|
|
509
|
+
const removalAdvice = deployment.actions?.includes(WorkflowAction.Delete) ? `Delete it with: uip solution deploy delete ${quoteForShell(deployment.name)}${feedSelector} --yes.` : "Remove it from the Orchestrator Solutions UI.";
|
|
510
|
+
if (deployment.activationStatus === "Inactive") {
|
|
511
|
+
return `Deployment '${deployment.name}' cannot be uninstalled in its current state ${status}. Available actions: ${availableActions}. Superseded or inactive deployments are never uninstallable. ${removalAdvice} Details: ${details}`;
|
|
512
|
+
}
|
|
513
|
+
return `Deployment '${deployment.name}' cannot be uninstalled in its current state ${status}. Available actions: ${availableActions}. Run 'uip solution deploy list${feedSelector}' to recheck its state. ${removalAdvice} Details: ${details}`;
|
|
514
|
+
}
|
|
515
|
+
async function waitForOperationToFinishAsync(auth, deploymentName, opts) {
|
|
516
|
+
let last;
|
|
517
|
+
let byKey = opts.deploymentKey !== undefined;
|
|
518
|
+
let solutionsOnly = false;
|
|
519
|
+
let operationStatus;
|
|
520
|
+
const readState = async () => {
|
|
521
|
+
if (byKey && opts.deploymentKey) {
|
|
522
|
+
const info = await readDeploymentByKeyAsync(auth, opts.deploymentKey, { scope: opts.scope, solutionsOnly });
|
|
523
|
+
if (info) {
|
|
524
|
+
solutionsOnly = info.source === "solutions";
|
|
525
|
+
operationStatus = info.operationStatus;
|
|
526
|
+
return operationStatus;
|
|
527
|
+
}
|
|
528
|
+
byKey = false;
|
|
529
|
+
}
|
|
530
|
+
last = await findDeploymentByName(auth, deploymentName, opts.scope);
|
|
531
|
+
operationStatus = last?.operationStatus;
|
|
532
|
+
return last ? operationStatus : undefined;
|
|
533
|
+
};
|
|
534
|
+
const poll = await pollUntil({
|
|
535
|
+
fn: readState,
|
|
536
|
+
until: (status) => status !== undefined && status !== "InProgress",
|
|
537
|
+
getStatus: (status) => status ?? "Unknown",
|
|
538
|
+
label: `deployment '${deploymentName}' to finish its current operation`,
|
|
539
|
+
logPrefix: "uninstall",
|
|
540
|
+
timeoutMs: opts.remainingMs(),
|
|
541
|
+
intervalMs: opts.intervalMs,
|
|
542
|
+
signal: opts.signal
|
|
543
|
+
});
|
|
544
|
+
if (byKey) {
|
|
545
|
+
const [, row] = await catchError(findDeploymentByName(auth, deploymentName, opts.scope));
|
|
546
|
+
const held = row ?? opts.known;
|
|
547
|
+
last = row ?? (held && operationStatus ? { ...held, operationStatus } : held);
|
|
548
|
+
}
|
|
549
|
+
return { outcome: poll.outcome, state: last, error: poll.error };
|
|
550
|
+
}
|
|
551
|
+
async function requestUninstallAsync(api, auth, deploymentName, ctx) {
|
|
552
|
+
const { options, sweep, scope, intervalMs, remainingMs } = ctx;
|
|
553
|
+
const { location } = sweep;
|
|
554
|
+
const sweepIncomplete = !location && !sweep.complete;
|
|
555
|
+
const feedSelector = feedSelectorFlag(location?.feed ?? ctx.selectedFeed);
|
|
556
|
+
let state = location?.deployment;
|
|
557
|
+
let waited = false;
|
|
558
|
+
let rechecked = false;
|
|
559
|
+
for (;; ) {
|
|
560
|
+
const [uninstallError, result] = await catchError(api.pipelinesUninstall(options.force ? { deploymentName, forceUninstall: true } : { deploymentName }, scope));
|
|
561
|
+
if (!uninstallError) {
|
|
562
|
+
return { ok: true, result };
|
|
563
|
+
}
|
|
564
|
+
const { message, details, errorCode, retry, context } = await extractErrorDetails(uninstallError);
|
|
565
|
+
if (!options.force && hasServerErrorCode(details, UNINSTALL_CONFIRMATION_REQUIRED_CODE)) {
|
|
566
|
+
return confirmationRequiredFailureAsync(api, deploymentName, {
|
|
567
|
+
deploymentKey: state?.key ?? location?.deployment.key,
|
|
568
|
+
scope,
|
|
569
|
+
feedSelector,
|
|
570
|
+
details
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
if (errorCode !== "invalid_argument") {
|
|
574
|
+
if (errorCode === "not_found" && sweepIncomplete) {
|
|
575
|
+
return incompleteSweepFailureAsync(deploymentName, sweep, details);
|
|
576
|
+
}
|
|
577
|
+
return fail3("uninstall_request_failed", message, requestErrorInstructions(deploymentName, errorCode, details, feedSelector), { errorCode, retry });
|
|
578
|
+
}
|
|
579
|
+
if (!rechecked && !waited && state?.operationStatus !== "InProgress") {
|
|
580
|
+
rechecked = true;
|
|
581
|
+
const [readError, fresh] = await catchError(findDeploymentByName(auth, deploymentName, scope));
|
|
582
|
+
if (!readError) {
|
|
583
|
+
state = fresh;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (!waited && state?.operationStatus === "InProgress") {
|
|
587
|
+
waited = true;
|
|
588
|
+
const wait = await waitForOperationToFinishAsync(auth, deploymentName, {
|
|
589
|
+
scope,
|
|
590
|
+
deploymentKey: state?.key ?? location?.deployment.key,
|
|
591
|
+
known: state,
|
|
592
|
+
intervalMs,
|
|
593
|
+
remainingMs,
|
|
594
|
+
signal: options.signal
|
|
595
|
+
});
|
|
596
|
+
if (wait.outcome === PollOutcome.Timeout) {
|
|
597
|
+
return fail3("uninstall_blocked", `Deployment '${deploymentName}' still had an operation running after ${options.timeout ?? 360}s, so it could not be uninstalled.`, buildDeploymentStateInstructions(wait.state ?? state, details, feedSelector), { exitCode: 2, errorCode: "timeout", retry: "RetryLater" });
|
|
598
|
+
}
|
|
599
|
+
if (wait.outcome !== PollOutcome.Completed) {
|
|
600
|
+
const {
|
|
601
|
+
reason,
|
|
602
|
+
message: pollMessage,
|
|
603
|
+
exitCode,
|
|
604
|
+
errorCode: pollErrorCode,
|
|
605
|
+
retry: pollRetry
|
|
606
|
+
} = mapPollFailure(wait, "Deployment");
|
|
607
|
+
return fail3(reason, pollMessage, `The deployment may still be installing. Check it with: uip solution deploy list${feedSelector}, then retry the uninstall.`, { exitCode, errorCode: pollErrorCode, retry: pollRetry });
|
|
608
|
+
}
|
|
609
|
+
state = wait.state;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (sweepIncomplete && (!state || isCompletedUninstall(state))) {
|
|
613
|
+
if (isNameOrFeedRefusal(errorCode, context?.httpStatus)) {
|
|
614
|
+
return incompleteSweepFailureAsync(deploymentName, sweep, details);
|
|
615
|
+
}
|
|
616
|
+
return fail3("uninstall_request_failed", message, requestErrorInstructions(deploymentName, errorCode, details, feedSelector), { errorCode, retry });
|
|
617
|
+
}
|
|
618
|
+
const instructions = state ? buildDeploymentStateInstructions(state, details, feedSelector) : ctx.selectedFeed ? `No deployment named '${deploymentName}' was found in the selected feed. Check the name with 'uip solution deploy list${feedSelector}', or drop the feed flag to search every feed you can reach. Details: ${details}` : `No deployment named '${deploymentName}' was found in any feed you can reach (tenant, Personal Workspace, or a folder feed). Check the name with 'uip solution deploy list'. Details: ${details}`;
|
|
619
|
+
return fail3("uninstall_request_failed", message, instructions, {
|
|
620
|
+
errorCode,
|
|
621
|
+
retry
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
async function incompleteSweepFailureAsync(deploymentName, sweep, refusalDetails) {
|
|
626
|
+
const report = await reportIncompleteSweepAsync(deploymentName, sweep.skipped);
|
|
627
|
+
return fail3("uninstall_request_failed", report.message, `${INCOMPLETE_SWEEP_ADVICE} Without its feed the uninstall request went to the tenant feed, which refused it. ${report.nextStep} Details: ${refusalDetails} (feed search: ${report.details})`, { errorCode: report.errorCode, retry: report.retry });
|
|
628
|
+
}
|
|
629
|
+
async function uninstallDeploymentAsync(deploymentName, options = {}) {
|
|
630
|
+
const [authError, auth] = await catchError(getSolutionAuthContext({
|
|
631
|
+
tenant: options.tenant,
|
|
632
|
+
loginValidity: options.loginValidity,
|
|
633
|
+
envFilePath: options.envFilePath
|
|
634
|
+
}));
|
|
635
|
+
if (authError) {
|
|
636
|
+
return fail3("auth_failed", authError.message, authError.message);
|
|
637
|
+
}
|
|
638
|
+
const api = new PipelinesApi(new Configuration({
|
|
639
|
+
basePath: auth.basePath,
|
|
640
|
+
accessToken: auth.accessToken
|
|
641
|
+
}));
|
|
642
|
+
const deadline = Date.now() + (options.timeout ?? 360) * 1000;
|
|
643
|
+
const intervalMs = options.pollInterval ?? 5000;
|
|
644
|
+
const remainingMs = () => Math.max(deadline - Date.now(), 1);
|
|
645
|
+
const selection = await resolveFeedSelectionAsync(auth, options, "Uninstalling a deployment");
|
|
646
|
+
if (!selection.ok) {
|
|
647
|
+
return fail3(selection.reason, selection.message, selection.instructions, {
|
|
648
|
+
errorCode: selection.errorCode,
|
|
649
|
+
retry: selection.retry
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
const { selectedFeed } = selection;
|
|
653
|
+
const sweep = await findDeploymentAcrossFeeds(auth, deploymentName, {
|
|
654
|
+
...options,
|
|
655
|
+
selectedFeed
|
|
656
|
+
});
|
|
657
|
+
const location = sweep.location;
|
|
658
|
+
const scope = location?.scope ?? (selectedFeed ? feedScopeInitOverride(selectedFeed) : undefined);
|
|
659
|
+
const [requestError, requested] = await catchError(requestUninstallAsync(api, auth, deploymentName, {
|
|
660
|
+
options,
|
|
661
|
+
sweep,
|
|
662
|
+
scope,
|
|
663
|
+
selectedFeed,
|
|
664
|
+
intervalMs,
|
|
665
|
+
remainingMs
|
|
666
|
+
}));
|
|
667
|
+
if (requestError) {
|
|
668
|
+
throw requestError;
|
|
669
|
+
}
|
|
670
|
+
if (!requested.ok) {
|
|
671
|
+
return requested;
|
|
672
|
+
}
|
|
673
|
+
const uninstallResult = requested.result;
|
|
674
|
+
if (uninstallResult.complete) {
|
|
675
|
+
return {
|
|
676
|
+
ok: true,
|
|
677
|
+
status: "Uninstall completed immediately",
|
|
678
|
+
deploymentName
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
const instanceId = uninstallResult.instanceId;
|
|
682
|
+
if (!instanceId) {
|
|
683
|
+
return {
|
|
684
|
+
ok: true,
|
|
685
|
+
status: "Uninstall scheduled",
|
|
686
|
+
deploymentName,
|
|
687
|
+
scheduled: uninstallResult.scheduled
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
const pollResult = await pollUntil({
|
|
691
|
+
fn: () => api.pipelinesGetDeploymentInstanceStatus({ instanceId }, scope),
|
|
692
|
+
until: (result) => TERMINAL_STATUSES.has(result.status),
|
|
693
|
+
getStatus: (result) => result.status,
|
|
694
|
+
label: `uninstall ${deploymentName}`,
|
|
695
|
+
logPrefix: "uninstall",
|
|
696
|
+
timeoutMs: remainingMs(),
|
|
697
|
+
intervalMs,
|
|
698
|
+
signal: options.signal
|
|
699
|
+
});
|
|
700
|
+
if (pollResult.outcome !== PollOutcome.Completed || !pollResult.data) {
|
|
701
|
+
const { reason, message, exitCode, errorCode, retry } = mapPollFailure(pollResult, "Uninstall");
|
|
702
|
+
return fail3(reason, message, "Uninstall may still be in progress.", {
|
|
703
|
+
exitCode,
|
|
704
|
+
errorCode,
|
|
705
|
+
retry
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
const finalStatus = pollResult.data;
|
|
709
|
+
if (finalStatus.status !== "SuccessfulUninstall") {
|
|
710
|
+
const workflowErrors = coerceWorkflowErrorMessages(finalStatus.errorMessage).map((e) => e.errorMessage).join(", ");
|
|
711
|
+
const serviceLines = formatServiceErrors(finalStatus.services);
|
|
712
|
+
const errors = [workflowErrors, ...serviceLines].filter(Boolean).join("; ");
|
|
713
|
+
const feedFlag = feedSelectorFlag(location?.feed ?? selectedFeed);
|
|
714
|
+
if (!options.force) {
|
|
715
|
+
const forceable = await forceableUninstallFailureAsync(api, deploymentName, {
|
|
716
|
+
deploymentKey: finalStatus.deploymentKey,
|
|
717
|
+
scope,
|
|
718
|
+
feedSelector: feedFlag,
|
|
719
|
+
instanceId,
|
|
720
|
+
serviceLines,
|
|
721
|
+
errors
|
|
722
|
+
});
|
|
723
|
+
if (forceable) {
|
|
724
|
+
return forceable;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const consentFlag = options.force ? "--force" : "--yes";
|
|
728
|
+
const classification = {
|
|
729
|
+
errorCode: errors ? "invalid_argument" : "unknown_error",
|
|
730
|
+
retry: "RetryLater"
|
|
731
|
+
};
|
|
732
|
+
return fail3("uninstall_failed", `Uninstall failed with status: ${finalStatus.status}`, errors ? `${errors}
|
|
733
|
+
Resolve the reason above in Orchestrator, then run the uninstall again: uip solution deploy uninstall ${quoteForShell(deploymentName)}${feedFlag} ${consentFlag}` : `Orchestrator ended the uninstall in a FailedUninstall state without an error detail. Run 'uip solution deploy list${feedFlag}' to recheck the deployment's state and retry. If it stays in a failed state and that listing shows Delete among its actions, remove the record with: uip solution deploy delete ${quoteForShell(deploymentName)}${feedFlag} --yes. Remove it from the Orchestrator Solutions UI when it does not.`, classification);
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
ok: true,
|
|
737
|
+
status: finalStatus.status,
|
|
738
|
+
deploymentName,
|
|
739
|
+
instanceId
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/services/deploy-upgrade-service.ts
|
|
744
|
+
var fail4 = (reason, message, instructions, extras = {}) => ({
|
|
745
|
+
ok: false,
|
|
746
|
+
reason,
|
|
747
|
+
message,
|
|
748
|
+
instructions,
|
|
749
|
+
exitCode: extras.exitCode ?? 1,
|
|
750
|
+
errorCode: extras.errorCode,
|
|
751
|
+
retry: extras.retry,
|
|
752
|
+
...extras.pipelineDeploymentId !== undefined && {
|
|
753
|
+
pipelineDeploymentId: extras.pipelineDeploymentId
|
|
754
|
+
},
|
|
755
|
+
...extras.deploymentKey !== undefined && {
|
|
756
|
+
deploymentKey: extras.deploymentKey
|
|
757
|
+
},
|
|
758
|
+
...extras.permanentDeletionResources !== undefined && {
|
|
759
|
+
permanentDeletionResources: extras.permanentDeletionResources
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
var failPermanentDeletion = (refusal, pipelineDeploymentId, deploymentKey) => fail4(refusal.reason, refusal.message, refusal.instructions, {
|
|
763
|
+
...refusal.classification,
|
|
764
|
+
pipelineDeploymentId,
|
|
765
|
+
deploymentKey,
|
|
766
|
+
permanentDeletionResources: refusal.resources
|
|
767
|
+
});
|
|
768
|
+
var DEFAULT_TIMEOUT_SECONDS = 300;
|
|
769
|
+
var DEFAULT_POLL_INTERVAL_MS = 5000;
|
|
770
|
+
var AUTH_INSTRUCTIONS2 = "Run 'uip login' to refresh credentials, then retry. If the deployment is in a different tenant, switch with 'uip login tenant set <tenant>'.";
|
|
771
|
+
var PACKAGE_INSTRUCTIONS = "Confirm the version is published to the tenant (or Personal Workspace) feed: uip solution packages list.";
|
|
772
|
+
var TARGET_INSTRUCTIONS = "Address the deployment either by key — uip solution deploy upgrade <deployment-key> — or by name with its package: uip solution deploy upgrade --name <deployment-name> --package-name <package>. 'uip solution deploy list' reports all three.";
|
|
773
|
+
var DEPLOYMENT_NOT_FOUND_INSTRUCTIONS = "Check the key with 'uip solution deploy list'. A server without the Pipelines deployment lookup answers 404 to every key for an external app; on one of those, address the deployment with --name and --package-name instead.";
|
|
774
|
+
var LOOKUP_FAILED_INSTRUCTIONS = "The deployment lookup failed before any upgrade was requested. Retry, or address the deployment with --name and --package-name.";
|
|
775
|
+
var INVALID_TARGET = {
|
|
776
|
+
errorCode: "invalid_argument",
|
|
777
|
+
retry: "RetryWillNotFix"
|
|
778
|
+
};
|
|
779
|
+
var GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
780
|
+
var UPGRADE_REFUSED_INSTRUCTIONS = "The server refused to start the upgrade. The deployment name may not exist, or may belong to a different package — check both with 'uip solution deploy list'. Another operation may still be running on it (wait, then retry), or it may be in a state the platform does not offer upgrades for.";
|
|
781
|
+
async function upgradeDeploymentAsync(first, second) {
|
|
782
|
+
const options = typeof first === "string" ? { ...second, deploymentKey: first } : first;
|
|
783
|
+
const targetError = validateTarget(options);
|
|
784
|
+
if (targetError) {
|
|
785
|
+
return targetError;
|
|
786
|
+
}
|
|
787
|
+
const [authError, auth] = await catchError(getSolutionAuthContext({
|
|
788
|
+
tenant: options.tenant,
|
|
789
|
+
loginValidity: options.loginValidity,
|
|
790
|
+
envFilePath: options.envFilePath
|
|
791
|
+
}));
|
|
792
|
+
if (authError) {
|
|
793
|
+
return fail4("auth_failed", authError.message, AUTH_INSTRUCTIONS2);
|
|
794
|
+
}
|
|
795
|
+
const pipelinesApi = new PipelinesApi(new Configuration({
|
|
796
|
+
basePath: auth.basePath,
|
|
797
|
+
accessToken: auth.accessToken
|
|
798
|
+
}));
|
|
799
|
+
const target = options.deploymentKey ? await resolveTargetByKeyAsync(pipelinesApi, auth, options, options.deploymentKey) : await resolveTargetByNameAsync(pipelinesApi, auth, options);
|
|
800
|
+
if (!target.ok) {
|
|
801
|
+
return target;
|
|
802
|
+
}
|
|
803
|
+
const { deploymentName, packageName, scope } = target;
|
|
804
|
+
let targetVersion = options.version ?? target.pendingDraftVersion;
|
|
805
|
+
if (targetVersion === undefined) {
|
|
806
|
+
const [versionError, latest] = await catchError(pipelinesApi.pipelinesGetPackageVersion({ packageName, packageVersion: null }, scope));
|
|
807
|
+
if (versionError || !latest) {
|
|
808
|
+
const detail = versionError && await extractErrorDetails(versionError);
|
|
809
|
+
if (!detail || detail.context?.httpStatus === 404) {
|
|
810
|
+
return fail4("package_not_found", detail?.details ? `Package '${packageName}' was not found in the feed: ${detail.details}` : `Package '${packageName}' was not found in the feed.`, PACKAGE_INSTRUCTIONS, { errorCode: "not_found", retry: "RetryWillNotFix" });
|
|
811
|
+
}
|
|
812
|
+
return fail4("lookup_failed", detail.details ? `${detail.message}: ${detail.details}` : detail.message, `The version lookup for '${packageName}' failed before any upgrade was requested. Retry, or pass --version explicitly to skip the lookup.`, { errorCode: detail.errorCode, retry: detail.retry });
|
|
813
|
+
}
|
|
814
|
+
targetVersion = latest.packageVersion;
|
|
815
|
+
}
|
|
816
|
+
const installAsync = async () => {
|
|
817
|
+
const [installError, installResult] = await catchError(pipelinesApi.pipelinesInstall({
|
|
818
|
+
deploymentName,
|
|
819
|
+
packageName,
|
|
820
|
+
packageVersion: targetVersion,
|
|
821
|
+
solutionRootFolderName: deploymentName
|
|
822
|
+
}, scope));
|
|
823
|
+
if (installError || !installResult?.pipelineDeploymentId) {
|
|
824
|
+
return describeInstallRefusalAsync(installError, {
|
|
825
|
+
options,
|
|
826
|
+
target,
|
|
827
|
+
deploymentName,
|
|
828
|
+
packageName
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
return {
|
|
832
|
+
ok: true,
|
|
833
|
+
pipelineDeploymentId: installResult.pipelineDeploymentId
|
|
834
|
+
};
|
|
835
|
+
};
|
|
836
|
+
const installed = await installAsync();
|
|
837
|
+
if (!installed.ok) {
|
|
838
|
+
return installed;
|
|
839
|
+
}
|
|
840
|
+
const targetFields = {
|
|
841
|
+
...target.fromVersion !== undefined && {
|
|
842
|
+
fromVersion: target.fromVersion
|
|
843
|
+
},
|
|
844
|
+
...target.installDeploymentKey !== undefined && {
|
|
845
|
+
installDeploymentKey: target.installDeploymentKey
|
|
846
|
+
},
|
|
847
|
+
...target.discardedDraftKey !== undefined && {
|
|
848
|
+
discardedDraftKey: target.discardedDraftKey
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
if (options.noWait) {
|
|
852
|
+
return {
|
|
853
|
+
ok: true,
|
|
854
|
+
deploymentName,
|
|
855
|
+
toVersion: targetVersion,
|
|
856
|
+
pipelineDeploymentId: installed.pipelineDeploymentId,
|
|
857
|
+
...targetFields,
|
|
858
|
+
status: "Accepted"
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
const timeoutSeconds = Number.isFinite(options.timeout) ? options.timeout : DEFAULT_TIMEOUT_SECONDS;
|
|
862
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
863
|
+
const deadline = Date.now() + timeoutMs;
|
|
864
|
+
const pollAsync = (pipelineDeploymentId2) => pollPipelineDeploymentAsync(pipelinesApi, auth, {
|
|
865
|
+
pipelineDeploymentId: pipelineDeploymentId2,
|
|
866
|
+
deploymentName,
|
|
867
|
+
scope,
|
|
868
|
+
timeoutMs: remainingTimeoutMs(timeoutMs, deadline),
|
|
869
|
+
intervalMs: Number.isFinite(options.pollInterval) ? options.pollInterval : DEFAULT_POLL_INTERVAL_MS,
|
|
870
|
+
signal: options.signal,
|
|
871
|
+
logPrefix: "deploy upgrade"
|
|
872
|
+
});
|
|
873
|
+
let pipelineDeploymentId = installed.pipelineDeploymentId;
|
|
874
|
+
let poll = await pollAsync(pipelineDeploymentId);
|
|
875
|
+
let permanentlyDeletedResources;
|
|
876
|
+
if (poll.outcome === PollOutcome.Completed && isPermanentDeletionRefusal(poll.data)) {
|
|
877
|
+
const refused = poll.data;
|
|
878
|
+
const refusedKey = refused.deploymentKey || undefined;
|
|
879
|
+
const keepCopiesHint = `discard the draft and upgrade without those links: ${discardRetryCommand(options, target, refusedKey)}, or remove the links from the deployment in Orchestrator first`;
|
|
880
|
+
const consent = await settlePermanentDeletionRefusalAsync({
|
|
881
|
+
api: pipelinesApi,
|
|
882
|
+
deploymentName,
|
|
883
|
+
scope,
|
|
884
|
+
signal: options.signal,
|
|
885
|
+
acknowledge: options.acknowledgePermanentDeletion === true,
|
|
886
|
+
status: refused,
|
|
887
|
+
verb: "Upgrading",
|
|
888
|
+
confirmHint: `re-run the same command with ${ACKNOWLEDGE_PERMANENT_DELETION_FLAG}: ${acknowledgeRetryCommand(options, target)}`,
|
|
889
|
+
keepCopiesHint
|
|
890
|
+
});
|
|
891
|
+
if (!consent.ok) {
|
|
892
|
+
return failPermanentDeletion(consent, pipelineDeploymentId, refusedKey);
|
|
893
|
+
}
|
|
894
|
+
const reinstalled = await installAsync();
|
|
895
|
+
if (!reinstalled.ok) {
|
|
896
|
+
return {
|
|
897
|
+
...reinstalled,
|
|
898
|
+
instructions: `${reinstalled.instructions} ${acknowledgedButNotRunHint(consent.resources, keepCopiesHint)}`
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
pipelineDeploymentId = reinstalled.pipelineDeploymentId;
|
|
902
|
+
poll = await pollAsync(pipelineDeploymentId);
|
|
903
|
+
if (poll.outcome === PollOutcome.Completed && isPermanentDeletionRefusal(poll.data)) {
|
|
904
|
+
const deploymentKey = poll.data?.deploymentKey || undefined;
|
|
905
|
+
return failPermanentDeletion(stillRequiresConfirmation(deploymentName, consent.resources, deploymentKey), pipelineDeploymentId, deploymentKey);
|
|
906
|
+
}
|
|
907
|
+
permanentlyDeletedResources = consent.resources.map((resource) => formatPermanentDeletionResources([resource]));
|
|
908
|
+
}
|
|
909
|
+
const base = {
|
|
910
|
+
deploymentName,
|
|
911
|
+
toVersion: targetVersion,
|
|
912
|
+
pipelineDeploymentId,
|
|
913
|
+
...targetFields,
|
|
914
|
+
...permanentlyDeletedResources && { permanentlyDeletedResources }
|
|
915
|
+
};
|
|
916
|
+
if (poll.outcome !== PollOutcome.Completed) {
|
|
917
|
+
const failure = mapPollFailure(poll, `Upgrade ${pipelineDeploymentId}`);
|
|
918
|
+
const isTimeout = poll.outcome === PollOutcome.Timeout;
|
|
919
|
+
const lastStatus = poll.data?.status ?? "unknown";
|
|
920
|
+
return fail4(isTimeout ? "install_timeout" : failure.reason, isTimeout ? `The upgrade of '${deploymentName}' to ${targetVersion} did not finish within ${timeoutSeconds}s (last status: ${lastStatus}).` : `${failure.message} while upgrading '${deploymentName}' to ${targetVersion} (last status: ${lastStatus}).`, isTimeout ? `It may still be running. ${trackingHint(pipelineDeploymentId, target.listFlag)} Or pass a longer --timeout.` : `The upgrade may still be running. ${trackingHint(pipelineDeploymentId, target.listFlag)}`, {
|
|
921
|
+
exitCode: failure.exitCode,
|
|
922
|
+
errorCode: failure.errorCode,
|
|
923
|
+
retry: failure.retry
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
const finalStatus = poll.data;
|
|
927
|
+
if (!finalStatus) {
|
|
928
|
+
return fail4("deploy_failed", `The upgrade of '${deploymentName}' returned no final status.`, trackingHint(pipelineDeploymentId, target.listFlag));
|
|
929
|
+
}
|
|
930
|
+
const landedDespiteStatus = installLandedDespiteStatus(finalStatus);
|
|
931
|
+
if (finalStatus.status !== "DeploymentSucceeded" && !landedDespiteStatus) {
|
|
932
|
+
return fail4("deploy_failed", `The upgrade of '${deploymentName}' to ${targetVersion} ended in ${finalStatus.status}.`, `${await describeUpgradeFailureAsync(auth.basePath, auth.accessToken, pipelineDeploymentId, finalStatus, scope, options.signal)}
|
|
933
|
+
${leavesPendingDraft(finalStatus.status) ? `Nothing was installed; the deployment stays on the version it was already running. The rejected attempt is kept as a pending draft${finalStatus.deploymentKey ? ` (${finalStatus.deploymentKey})` : ""}, which a plain retry would run into, so once the cause is fixed discard it and retry: ${discardRetryCommand(options, target, finalStatus.deploymentKey || undefined)}` : "Fix the reported problem, then retry the upgrade. The deployment stays on the version it was already running."}`, classifyTerminalFailure(finalStatus.status));
|
|
934
|
+
}
|
|
935
|
+
const described = await describeUpgradedDeploymentAsync(auth, deploymentName, finalStatus, scope, target.listFlag);
|
|
936
|
+
return {
|
|
937
|
+
ok: true,
|
|
938
|
+
...base,
|
|
939
|
+
deploymentKey: finalStatus.deploymentKey,
|
|
940
|
+
operation: finalStatus.deploymentResult?.operation ?? finalStatus.synthesizedOperation,
|
|
941
|
+
status: finalStatus.status,
|
|
942
|
+
...finalStatus.deploymentResult?.status && {
|
|
943
|
+
deploymentStatus: finalStatus.deploymentResult.status,
|
|
944
|
+
actions: finalStatus.deploymentResult.actions ?? []
|
|
945
|
+
},
|
|
946
|
+
...described,
|
|
947
|
+
...landedDespiteStatus && {
|
|
948
|
+
nextSteps: [
|
|
949
|
+
explainPipelineStatusAfterInstall(finalStatus),
|
|
950
|
+
...described.nextSteps ?? []
|
|
951
|
+
]
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
async function describeUpgradedDeploymentAsync(auth, deploymentName, finalStatus, scope, listFlag) {
|
|
956
|
+
const record = finalStatus.deploymentResult;
|
|
957
|
+
const actions = record?.actions ?? [];
|
|
958
|
+
if (record?.status === "SuccessfulActivate") {
|
|
959
|
+
return { activationStatus: "Active" };
|
|
960
|
+
}
|
|
961
|
+
const reported = reportedActivationStatus(record);
|
|
962
|
+
const [, rows] = reported ? [undefined, undefined] : await catchError(findDeploymentsByName(auth, deploymentName, scope));
|
|
963
|
+
const row = rows?.find((candidate) => candidate.key === finalStatus.deploymentKey);
|
|
964
|
+
const activationStatus = reported ?? row?.activationStatus ?? (actions.includes("SetupActivation") ? "NeedsSetupToActivate" : actions.includes("Activate") ? "ReadyToActivate" : undefined);
|
|
965
|
+
if (!activationStatus) {
|
|
966
|
+
return {};
|
|
967
|
+
}
|
|
968
|
+
const state = {
|
|
969
|
+
name: deploymentName,
|
|
970
|
+
operation: row?.operation ?? record?.operation ?? finalStatus.synthesizedOperation,
|
|
971
|
+
operationStatus: row?.operationStatus ?? record?.operationStatus ?? "Successful",
|
|
972
|
+
activationStatus,
|
|
973
|
+
packageVersion: row?.packageVersion ?? record?.packageVersion,
|
|
974
|
+
currentPackageVersion: row?.currentPackageVersion ?? record?.packageVersion
|
|
975
|
+
};
|
|
976
|
+
const pendingResources = needsPendingResources(state) && finalStatus.deploymentKey ? await readPendingResourcesAsync(pendingResourceApis(auth), { name: deploymentName, key: finalStatus.deploymentKey }, scope) : undefined;
|
|
977
|
+
const nextSteps = deploymentNextSteps(state, pendingResources, listFlag ? ` ${listFlag}` : "");
|
|
978
|
+
return {
|
|
979
|
+
activationStatus,
|
|
980
|
+
...pendingResources ? { pendingResources } : {},
|
|
981
|
+
...nextSteps && nextSteps.length > 0 ? { nextSteps } : {}
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
async function describeInstallRefusalAsync(installError, context) {
|
|
985
|
+
const { options, target, deploymentName, packageName } = context;
|
|
986
|
+
let message = "The upgrade request returned no pipeline deployment.";
|
|
987
|
+
let classification = {};
|
|
988
|
+
let instructions = UPGRADE_REFUSED_INSTRUCTIONS;
|
|
989
|
+
if (installError) {
|
|
990
|
+
const detail = await extractErrorDetails(installError);
|
|
991
|
+
message = detail.details ? `${detail.message}: ${detail.details}` : detail.message;
|
|
992
|
+
classification = {
|
|
993
|
+
errorCode: detail.errorCode,
|
|
994
|
+
retry: detail.retry
|
|
995
|
+
};
|
|
996
|
+
if (detail.context?.httpStatus === 404) {
|
|
997
|
+
instructions = `The server knows no such version of '${packageName}' in this feed. ${PACKAGE_INSTRUCTIONS} 'uip solution deploy list' shows the deployment's package and current version.`;
|
|
998
|
+
}
|
|
999
|
+
if (hasServerErrorCode(detail.details, DRAFT_VERSION_MISMATCH_CODE)) {
|
|
1000
|
+
return fail4("pending_draft", message, `'${deploymentName}' has a pending draft for another version: an earlier attempt that never completed, usually one rejected by validation, and the server will not upgrade past it. Discard it and upgrade the live version: ${discardRetryCommand(options, target)}${options.deploymentKey ? "" : `
|
|
1001
|
+
Under an external app the name form cannot find the draft; pass the deployment's key instead (InstallDeploymentKey from the original deploy, or Key from 'uip solution deploy list').`}`, INVALID_TARGET);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return fail4("request_failed", message, instructions, classification);
|
|
1005
|
+
}
|
|
1006
|
+
function trackingHint(pipelineDeploymentId, listFlag) {
|
|
1007
|
+
return listFlag ? `Track it with: uip solution deploy list ${listFlag} (deploy status reads the tenant feed and cannot see this run).` : `Track it with: uip solution deploy status ${pipelineDeploymentId}, or uip solution deploy list.`;
|
|
1008
|
+
}
|
|
1009
|
+
function acknowledgeRetryCommand(options, target) {
|
|
1010
|
+
const version = versionFlag(options);
|
|
1011
|
+
let address = options.deploymentKey ? quoteForShell(options.deploymentKey) : undefined;
|
|
1012
|
+
if (!address) {
|
|
1013
|
+
const workspace = options.personalWorkspace ? " --personal-workspace" : "";
|
|
1014
|
+
address = `--name ${quoteForShell(target.deploymentName)} --package-name ${quoteForShell(target.packageName)}${workspace}`;
|
|
1015
|
+
}
|
|
1016
|
+
return `uip solution deploy upgrade ${address}${version}${tenantFlag(options)} ${ACKNOWLEDGE_PERMANENT_DELETION_FLAG}`;
|
|
1017
|
+
}
|
|
1018
|
+
async function consentDraftDecisionAsync(pipelinesApi, options, target, draft, scope) {
|
|
1019
|
+
if (options.noWait && !options.discardDraft) {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
if (!await draftAwaitsConsentAsync(pipelinesApi, options, target.deploymentName, draft, scope)) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (options.discardDraft) {
|
|
1026
|
+
return fail4("pending_draft", `Deployment '${target.deploymentName}' has a pending draft (${draft.key}) that awaits the permanent-deletion confirmation, and --discard-draft would drop the links being confirmed.`, `Nothing was changed. To confirm the deletion and upgrade with those links, re-run without --discard-draft: ${acknowledgeRetryCommand(options, target)}. To keep the installed copies instead, re-run without ${ACKNOWLEDGE_PERMANENT_DELETION_FLAG}, which discards the draft: ${discardRetryCommand(options, target, draft.key)}`, INVALID_TARGET);
|
|
1027
|
+
}
|
|
1028
|
+
return "rerun";
|
|
1029
|
+
}
|
|
1030
|
+
async function draftAwaitsConsentAsync(pipelinesApi, options, deploymentName, draft, scope) {
|
|
1031
|
+
if (!options.acknowledgePermanentDeletion || draft.firstInstall || draft.activationSetup || !draft.targetVersion) {
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
if (options.version !== undefined && options.version.toLowerCase() !== draft.targetVersion.toLowerCase()) {
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
const resources = await listPermanentDeletionResourcesAsync(pipelinesApi, deploymentName, draft.key, scope, options.signal);
|
|
1038
|
+
return (resources?.length ?? 0) > 0;
|
|
1039
|
+
}
|
|
1040
|
+
function discardRetryCommand(options, target, draftKey) {
|
|
1041
|
+
const version = `${versionFlag(options)}${tenantFlag(options)}`;
|
|
1042
|
+
const key = draftKey ?? options.deploymentKey;
|
|
1043
|
+
if (key) {
|
|
1044
|
+
return `uip solution deploy upgrade ${quoteForShell(key)} --discard-draft${version}`;
|
|
1045
|
+
}
|
|
1046
|
+
const workspace = options.personalWorkspace ? " --personal-workspace" : "";
|
|
1047
|
+
return `uip solution deploy upgrade --name ${quoteForShell(target.deploymentName)} --package-name ${quoteForShell(target.packageName)}${workspace} --discard-draft${version}`;
|
|
1048
|
+
}
|
|
1049
|
+
function tenantFlag(options) {
|
|
1050
|
+
return options.tenant ? ` --tenant ${quoteForShell(options.tenant)}` : "";
|
|
1051
|
+
}
|
|
1052
|
+
function versionFlag(options) {
|
|
1053
|
+
return options.version ? ` --version ${quoteForShell(options.version)}` : "";
|
|
1054
|
+
}
|
|
1055
|
+
function validateTarget(options) {
|
|
1056
|
+
const byKey = options.deploymentKey !== undefined;
|
|
1057
|
+
const byName = options.deploymentName !== undefined || options.packageName !== undefined;
|
|
1058
|
+
if (options.version !== undefined && options.version.trim() === "") {
|
|
1059
|
+
return fail4("invalid_target", "--version was given but is empty.", "Pass the version to upgrade to (uip solution packages list shows the published ones), or omit --version to upgrade to the newest.", INVALID_TARGET);
|
|
1060
|
+
}
|
|
1061
|
+
if (byKey && byName) {
|
|
1062
|
+
return fail4("invalid_target", "Pass either a deployment key or --name with --package-name, not both.", TARGET_INSTRUCTIONS, INVALID_TARGET);
|
|
1063
|
+
}
|
|
1064
|
+
if (byKey) {
|
|
1065
|
+
return GUID_PATTERN.test(options.deploymentKey) ? undefined : fail4("invalid_target", `'${options.deploymentKey}' is not a deployment key.`, `A deployment key is a GUID, reported as Key by 'uip solution deploy list'. To address the deployment by name instead: ${TARGET_INSTRUCTIONS}`, INVALID_TARGET);
|
|
1066
|
+
}
|
|
1067
|
+
if (!options.deploymentName || !options.packageName) {
|
|
1068
|
+
return fail4("invalid_target", "--name and --package-name go together: the Pipelines install route needs both.", TARGET_INSTRUCTIONS, INVALID_TARGET);
|
|
1069
|
+
}
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
async function resolveTargetByNameAsync(pipelinesApi, auth, options) {
|
|
1073
|
+
const deploymentName = options.deploymentName;
|
|
1074
|
+
let scope;
|
|
1075
|
+
if (options.personalWorkspace) {
|
|
1076
|
+
const verdict = classifyFeedSession(auth);
|
|
1077
|
+
if (verdict !== "user") {
|
|
1078
|
+
const { message, instructions } = feedSessionRefusal(verdict, `Upgrading the Personal Workspace deployment '${deploymentName}'`, "address the deployment by key");
|
|
1079
|
+
return fail4("feed_requires_user_session", message, instructions, {
|
|
1080
|
+
errorCode: "permission_denied",
|
|
1081
|
+
retry: "RetryWillNotFix"
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
const [pwError, pw] = await catchError(resolvePersonalWorkspace({
|
|
1085
|
+
tenant: options.tenant,
|
|
1086
|
+
loginValidity: options.loginValidity,
|
|
1087
|
+
envFilePath: options.envFilePath
|
|
1088
|
+
}));
|
|
1089
|
+
if (pwError || !pw) {
|
|
1090
|
+
return fail4("personal_workspace_failed", pwError?.message ?? "Personal Workspace could not be resolved.", "Ensure your user has a Personal Workspace, then retry.");
|
|
1091
|
+
}
|
|
1092
|
+
scope = folderKeyInitOverride(pw.key);
|
|
1093
|
+
}
|
|
1094
|
+
const [existsError] = await catchError(pipelinesApi.pipelinesGetPreActivateSteps({ deploymentName }, scope));
|
|
1095
|
+
if (existsError) {
|
|
1096
|
+
const detail = await extractErrorDetails(existsError);
|
|
1097
|
+
const where = scope ? "your Personal Workspace feed" : `the tenant feed of '${auth.tenantName}'`;
|
|
1098
|
+
if (isDeploymentNotFound(detail)) {
|
|
1099
|
+
return fail4("deployment_not_found", `Deployment '${deploymentName}' was not found in ${where}. 'deploy upgrade' never creates a deployment.`, `Check the name with 'uip solution deploy list'${scope ? "" : " (add --personal-workspace for a Personal Workspace deployment)"}. For a first deployment use 'uip solution deploy run'.`, { errorCode: "not_found", retry: "RetryWillNotFix" });
|
|
1100
|
+
}
|
|
1101
|
+
return fail4("lookup_failed", detail.details ? `${detail.message}: ${detail.details}` : detail.message, `The existence check for '${deploymentName}' failed before any upgrade was requested. Retry, or address the deployment by key: uip solution deploy upgrade <deployment-key>.`, { errorCode: detail.errorCode, retry: detail.retry });
|
|
1102
|
+
}
|
|
1103
|
+
const [rowsError, rows] = await catchError(findDeploymentsByName(auth, deploymentName, scope));
|
|
1104
|
+
if (rowsError) {
|
|
1105
|
+
const unreadable = rowsError instanceof DeploymentSearchNeedsUserSessionError || classifyFeedSession(auth) === "application" && isAuthRefusal(rowsError);
|
|
1106
|
+
if (!unreadable) {
|
|
1107
|
+
const detail = await extractErrorDetails(rowsError);
|
|
1108
|
+
return fail4("lookup_failed", `The status check for '${deploymentName}' failed: ${detail.message}`, "The deployment exists, but whether it is still installed could not be confirmed, and upgrading an uninstalled name would deploy the package afresh under it. Retry, or address the deployment by key: uip solution deploy upgrade <deployment-key>.", { errorCode: detail.errorCode, retry: detail.retry });
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
if (isCompletedUninstall(rows ? newestDeploymentRow(rows) : undefined)) {
|
|
1112
|
+
return fail4("deployment_uninstalled", `Deployment '${deploymentName}' has been uninstalled, so there is nothing to upgrade.`, "The install route would create a new deployment under that name rather than upgrade this one. Deploy the package afresh with 'uip solution deploy run', or pick a live deployment from 'uip solution deploy list'.", INVALID_TARGET);
|
|
1113
|
+
}
|
|
1114
|
+
const resolved = {
|
|
1115
|
+
deploymentName,
|
|
1116
|
+
packageName: options.packageName,
|
|
1117
|
+
listFlag: scope ? "--personal-workspace" : undefined
|
|
1118
|
+
};
|
|
1119
|
+
let discardedDraftKey;
|
|
1120
|
+
const newest = rows ? newestDeploymentRow(rows) : undefined;
|
|
1121
|
+
if (newest?.operationStatus === "Draft") {
|
|
1122
|
+
const draft = {
|
|
1123
|
+
key: newest.key,
|
|
1124
|
+
firstInstall: newest.operation === "Install",
|
|
1125
|
+
activationSetup: newest.operation === "ActivationSetup",
|
|
1126
|
+
targetVersion: newest.targetPackageVersion ?? newest.packageVersion,
|
|
1127
|
+
liveVersion: newest.currentPackageVersion
|
|
1128
|
+
};
|
|
1129
|
+
const consentDraft = await consentDraftDecisionAsync(pipelinesApi, options, resolved, draft, scope);
|
|
1130
|
+
if (consentDraft && consentDraft !== "rerun") {
|
|
1131
|
+
return consentDraft;
|
|
1132
|
+
}
|
|
1133
|
+
if (consentDraft === "rerun") {
|
|
1134
|
+
return {
|
|
1135
|
+
ok: true,
|
|
1136
|
+
...resolved,
|
|
1137
|
+
scope,
|
|
1138
|
+
pendingDraftVersion: draft.targetVersion ?? undefined
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
const refusal = refusePendingDraft(options, resolved, draft);
|
|
1142
|
+
if (refusal) {
|
|
1143
|
+
return refusal;
|
|
1144
|
+
}
|
|
1145
|
+
const discarded = await discardDraftAsync(pipelinesApi, deploymentName, draft.key, scope);
|
|
1146
|
+
if (discarded) {
|
|
1147
|
+
return discarded;
|
|
1148
|
+
}
|
|
1149
|
+
discardedDraftKey = draft.key;
|
|
1150
|
+
}
|
|
1151
|
+
return {
|
|
1152
|
+
ok: true,
|
|
1153
|
+
...resolved,
|
|
1154
|
+
scope,
|
|
1155
|
+
discardedDraftKey
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
function isDeploymentNotFound(detail) {
|
|
1159
|
+
const status = detail.context?.httpStatus;
|
|
1160
|
+
if (status === 404) {
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
return status === 400 && (/"errorCode"\s*:\s*"4004"/.test(detail.details) || (detail.parsedErrors?.deploymentName ?? []).some((message) => /not found/i.test(message)));
|
|
1164
|
+
}
|
|
1165
|
+
async function resolveTargetByKeyAsync(pipelinesApi, auth, options, deploymentKey) {
|
|
1166
|
+
const [lookupError, info] = await catchError(pipelinesApi.pipelinesGetDeploymentByKey({ key: deploymentKey }));
|
|
1167
|
+
if (lookupError || !info) {
|
|
1168
|
+
const detail = lookupError ? await extractErrorDetails(lookupError) : undefined;
|
|
1169
|
+
if (detail?.context?.httpStatus === 404 && classifyFeedSession(auth) === "user") {
|
|
1170
|
+
return resolveTargetByKeyViaSolutionsAsync(pipelinesApi, auth, options, deploymentKey);
|
|
1171
|
+
}
|
|
1172
|
+
if (!detail || detail.context?.httpStatus === 404) {
|
|
1173
|
+
return fail4("deployment_not_found", `Deployment '${deploymentKey}' was not found in tenant '${auth.tenantName}'.`, DEPLOYMENT_NOT_FOUND_INSTRUCTIONS, { errorCode: "not_found", retry: "RetryWillNotFix" });
|
|
1174
|
+
}
|
|
1175
|
+
return fail4("lookup_failed", detail.details ? `${detail.message}: ${detail.details}` : detail.message, LOOKUP_FAILED_INSTRUCTIONS, { errorCode: detail.errorCode, retry: detail.retry });
|
|
1176
|
+
}
|
|
1177
|
+
if (info.status === DeploymentStatus.SuccessfulUninstall) {
|
|
1178
|
+
return fail4("deployment_uninstalled", `Deployment '${info.deploymentName}' (${deploymentKey}) has been uninstalled, so there is nothing to upgrade.`, "The install route would create a new deployment under that name rather than upgrade this one. Deploy the package afresh with 'uip solution deploy run', or pick a live deployment from 'uip solution deploy list'.", INVALID_TARGET);
|
|
1179
|
+
}
|
|
1180
|
+
let scope;
|
|
1181
|
+
if (info.locationKey) {
|
|
1182
|
+
const verdict = classifyFeedSession(auth);
|
|
1183
|
+
if (verdict !== "user") {
|
|
1184
|
+
const { message, instructions } = feedSessionRefusal(verdict, `Upgrading deployment '${info.deploymentName}', which is in a Personal Workspace or folder feed (folder key ${info.locationKey}),`, "run the upgrade from the Orchestrator UI");
|
|
1185
|
+
return fail4("feed_requires_user_session", message, instructions, {
|
|
1186
|
+
errorCode: "permission_denied",
|
|
1187
|
+
retry: "RetryWillNotFix"
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
scope = folderKeyInitOverride(info.locationKey);
|
|
1191
|
+
} else if (options.personalWorkspace) {
|
|
1192
|
+
return fail4("invalid_target", `Deployment '${info.deploymentName}' (${deploymentKey}) is on the tenant feed, not in a Personal Workspace.`, "Drop --personal-workspace: with a deployment key the feed is read from the deployment itself.", INVALID_TARGET);
|
|
1193
|
+
}
|
|
1194
|
+
const listFlag = info.locationKey ? `--feed ${info.locationKey}` : undefined;
|
|
1195
|
+
let live = info;
|
|
1196
|
+
let discardedDraftKey;
|
|
1197
|
+
if (info.status === DeploymentStatus.Draft) {
|
|
1198
|
+
const draft = {
|
|
1199
|
+
key: info.deploymentKey,
|
|
1200
|
+
firstInstall: info.operation === DeploymentOperation.Install,
|
|
1201
|
+
activationSetup: info.operation === DeploymentOperation.ActivationSetup,
|
|
1202
|
+
targetVersion: info.targetPackageVersion ?? info.packageVersion,
|
|
1203
|
+
liveVersion: info.packageVersion
|
|
1204
|
+
};
|
|
1205
|
+
const consentDraft = await consentDraftDecisionAsync(pipelinesApi, options, {
|
|
1206
|
+
deploymentName: info.deploymentName,
|
|
1207
|
+
packageName: info.packageName,
|
|
1208
|
+
listFlag
|
|
1209
|
+
}, draft, scope);
|
|
1210
|
+
if (consentDraft && consentDraft !== "rerun") {
|
|
1211
|
+
return consentDraft;
|
|
1212
|
+
}
|
|
1213
|
+
if (consentDraft === "rerun") {
|
|
1214
|
+
return {
|
|
1215
|
+
ok: true,
|
|
1216
|
+
deploymentName: info.deploymentName,
|
|
1217
|
+
packageName: info.packageName,
|
|
1218
|
+
scope,
|
|
1219
|
+
listFlag,
|
|
1220
|
+
fromVersion: info.packageVersion ?? undefined,
|
|
1221
|
+
installDeploymentKey: info.installDeploymentKey,
|
|
1222
|
+
pendingDraftVersion: draft.targetVersion ?? undefined
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
const refusal = refusePendingDraft(options, {
|
|
1226
|
+
deploymentName: info.deploymentName,
|
|
1227
|
+
packageName: info.packageName,
|
|
1228
|
+
listFlag
|
|
1229
|
+
}, draft);
|
|
1230
|
+
if (refusal) {
|
|
1231
|
+
return refusal;
|
|
1232
|
+
}
|
|
1233
|
+
const discarded = await discardDraftAsync(pipelinesApi, info.deploymentName, draft.key, scope);
|
|
1234
|
+
if (discarded) {
|
|
1235
|
+
return discarded;
|
|
1236
|
+
}
|
|
1237
|
+
const [relookupError, current] = await catchError(pipelinesApi.pipelinesGetDeploymentByKey({
|
|
1238
|
+
key: info.installDeploymentKey
|
|
1239
|
+
}));
|
|
1240
|
+
if (relookupError || !current) {
|
|
1241
|
+
const detail = relookupError ? await extractErrorDetails(relookupError) : undefined;
|
|
1242
|
+
return fail4("lookup_failed", `The pending draft ${draft.key} of '${info.deploymentName}' was discarded, but the live deployment could not be looked up again${detail ? `: ${detail.message}` : "."}`, `Nothing was upgraded. Retry the upgrade: uip solution deploy upgrade ${quoteForShell(info.installDeploymentKey)}${versionFlag(options)}${tenantFlag(options)}`, detail ? { errorCode: detail.errorCode, retry: detail.retry } : {});
|
|
1243
|
+
}
|
|
1244
|
+
if (current.status === DeploymentStatus.Draft || current.status === DeploymentStatus.SuccessfulUninstall) {
|
|
1245
|
+
return fail4("pending_draft", `The pending draft ${draft.key} of '${info.deploymentName}' was discarded, but the deployment's newest generation is now ${current.status === DeploymentStatus.Draft ? `another draft (${current.deploymentKey})` : "an uninstall"}.`, "Nothing was upgraded. Another operation on this deployment ran in between; check it with 'uip solution deploy list', then retry.", INVALID_TARGET);
|
|
1246
|
+
}
|
|
1247
|
+
live = current;
|
|
1248
|
+
discardedDraftKey = draft.key;
|
|
1249
|
+
}
|
|
1250
|
+
return {
|
|
1251
|
+
ok: true,
|
|
1252
|
+
deploymentName: live.deploymentName,
|
|
1253
|
+
packageName: live.packageName,
|
|
1254
|
+
scope,
|
|
1255
|
+
listFlag,
|
|
1256
|
+
fromVersion: live.packageVersion ?? undefined,
|
|
1257
|
+
installDeploymentKey: live.installDeploymentKey,
|
|
1258
|
+
discardedDraftKey
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
async function resolveTargetByKeyViaSolutionsAsync(pipelinesApi, auth, options, deploymentKey) {
|
|
1262
|
+
let scope;
|
|
1263
|
+
if (options.personalWorkspace) {
|
|
1264
|
+
const [pwError, pw] = await catchError(resolvePersonalWorkspace({
|
|
1265
|
+
tenant: options.tenant,
|
|
1266
|
+
loginValidity: options.loginValidity,
|
|
1267
|
+
envFilePath: options.envFilePath
|
|
1268
|
+
}));
|
|
1269
|
+
if (pwError || !pw) {
|
|
1270
|
+
return fail4("personal_workspace_failed", pwError?.message ?? "Personal Workspace could not be resolved.", "Ensure your user has a Personal Workspace, then retry.");
|
|
1271
|
+
}
|
|
1272
|
+
scope = folderKeyInitOverride(pw.key);
|
|
1273
|
+
}
|
|
1274
|
+
const deploymentsApi = new DeploymentsApi(new Configuration2({
|
|
1275
|
+
basePath: auth.basePath,
|
|
1276
|
+
accessToken: auth.accessToken
|
|
1277
|
+
}));
|
|
1278
|
+
const [readError, row] = await catchError(deploymentsApi.deploymentsGetByKey({ deploymentKey }, scope));
|
|
1279
|
+
if (readError || !row) {
|
|
1280
|
+
const detail = readError ? await extractErrorDetails(readError) : undefined;
|
|
1281
|
+
if (!detail || detail.context?.httpStatus === 404) {
|
|
1282
|
+
return fail4("deployment_not_found", `Deployment '${deploymentKey}' was not found in ${scope ? "your Personal Workspace feed" : `the tenant feed of '${auth.tenantName}'`}.`, `Check the key with 'uip solution deploy list'. Neither the Pipelines deployment lookup nor the Solutions read found it; the latter reads a key on the tenant feed, or with --personal-workspace in your workspace, so address a folder-feed deployment with --name and --package-name instead.`, { errorCode: "not_found", retry: "RetryWillNotFix" });
|
|
1283
|
+
}
|
|
1284
|
+
return fail4("lookup_failed", detail.details ? `${detail.message}: ${detail.details}` : detail.message, LOOKUP_FAILED_INSTRUCTIONS, { errorCode: detail.errorCode, retry: detail.retry });
|
|
1285
|
+
}
|
|
1286
|
+
if (row.status === "SuccessfulUninstall") {
|
|
1287
|
+
return fail4("deployment_uninstalled", `Deployment '${row.name}' (${deploymentKey}) has been uninstalled, so there is nothing to upgrade.`, "The install route would create a new deployment under that name rather than upgrade this one. Deploy the package afresh with 'uip solution deploy run', or pick a live deployment from 'uip solution deploy list'.", INVALID_TARGET);
|
|
1288
|
+
}
|
|
1289
|
+
const target = await resolveTargetByNameAsync(pipelinesApi, auth, {
|
|
1290
|
+
...options,
|
|
1291
|
+
deploymentName: row.name,
|
|
1292
|
+
packageName: row.packageName
|
|
1293
|
+
});
|
|
1294
|
+
if (!target.ok) {
|
|
1295
|
+
return target;
|
|
1296
|
+
}
|
|
1297
|
+
return { ...target, installDeploymentKey: row.installDeploymentKey };
|
|
1298
|
+
}
|
|
1299
|
+
function refusePendingDraft(options, target, draft) {
|
|
1300
|
+
const targetVersion = draft.targetVersion ?? "<version>";
|
|
1301
|
+
const feed = target.listFlag ? ` ${target.listFlag}` : "";
|
|
1302
|
+
const packageVersion = draft.targetVersion ? quoteForShell(draft.targetVersion) : "<version>";
|
|
1303
|
+
const retryDraft = `uip solution deploy run --name ${quoteForShell(target.deploymentName)} --package-name ${quoteForShell(target.packageName)} --package-version ${packageVersion} --config-file <fixed-config>${feed}${tenantFlag(options)} --yes`;
|
|
1304
|
+
if (draft.firstInstall) {
|
|
1305
|
+
return fail4("pending_draft", `Deployment '${target.deploymentName}' was never installed: its first install (${draft.key}) is still a pending draft, so there is nothing to upgrade.`, `The draft is an attempt that never completed, usually one rejected by validation, or one still running. To retry it with a corrected configuration: ${retryDraft} (the server re-validates the draft with the new file). To abandon it, delete it in Orchestrator.`, INVALID_TARGET);
|
|
1306
|
+
}
|
|
1307
|
+
if (options.discardDraft) {
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
if (draft.activationSetup) {
|
|
1311
|
+
return fail4("pending_draft", `Deployment '${target.deploymentName}' has an unfinished Setup activation (${draft.key}): the Orchestrator "Setup activation" wizard was started on version ${targetVersion} and not completed.`, `While that draft exists the server would run it in place of this upgrade, or refuse a different version. Finish the setup in Orchestrator (Solutions, the deployment, Setup activation), then retry; or discard it and upgrade the live version: ${discardRetryCommand(options, target, draft.key)}`, INVALID_TARGET);
|
|
1312
|
+
}
|
|
1313
|
+
const live = draft.liveVersion ? ` ${draft.liveVersion}` : "";
|
|
1314
|
+
return fail4("pending_draft", `Deployment '${target.deploymentName}' has a pending draft (${draft.key}) for version ${targetVersion}: an earlier attempt that never completed, usually one rejected by validation, or one still running.`, `The server would run that draft, with the configuration it was built with, in place of this upgrade, or refuse a different version. To discard it and upgrade the live version${live}: ${discardRetryCommand(options, target, draft.key)}
|
|
1315
|
+
To retry the draft itself with a corrected configuration instead: ${retryDraft}`, INVALID_TARGET);
|
|
1316
|
+
}
|
|
1317
|
+
async function discardDraftAsync(pipelinesApi, deploymentName, draftKey, scope) {
|
|
1318
|
+
const [error] = await catchError(pipelinesApi.pipelinesDeleteDeploymentByKey({ deploymentKey: draftKey }, scope));
|
|
1319
|
+
if (!error) {
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
const detail = await extractErrorDetails(error);
|
|
1323
|
+
const status = detail.context?.httpStatus;
|
|
1324
|
+
const hasBody = !isBareResponse(detail.details);
|
|
1325
|
+
if (hasServerErrorCode(detail.details, "DeploymentNotFound") || status === 404 && hasBody) {
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
const message = `The pending draft ${draftKey} of '${deploymentName}' could not be discarded: ${hasBody ? `${detail.message}: ${detail.details}` : detail.message}`;
|
|
1329
|
+
if (status === 405 || status === 404 && !hasBody) {
|
|
1330
|
+
return fail4("discard_draft_failed", message, "This server cannot discard drafts through the Pipelines API yet. Nothing was upgraded. Delete the draft in Orchestrator, then retry the upgrade.", {
|
|
1331
|
+
errorCode: status === 405 ? "method_not_allowed" : "not_found",
|
|
1332
|
+
retry: "RetryWillNotFix"
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
if (hasServerErrorCode(detail.details, "DeploymentAlreadyInProgress")) {
|
|
1336
|
+
return fail4("discard_draft_failed", message, "The draft is still being validated. Nothing was upgraded. Wait for its run to finish (uip solution deploy list shows it), then retry.", { errorCode: "invalid_argument", retry: "RetryLater" });
|
|
1337
|
+
}
|
|
1338
|
+
if (hasServerErrorCode(detail.details, "DeploymentInvalidState")) {
|
|
1339
|
+
return fail4("discard_draft_failed", message, "The draft is no longer one that can be discarded: it moved on (it was promoted, or it is a rollback). Nothing was upgraded. Check the deployment with 'uip solution deploy list', then retry.", INVALID_TARGET);
|
|
1340
|
+
}
|
|
1341
|
+
return fail4("discard_draft_failed", message, "Nothing was upgraded. Retry, or delete the draft in Orchestrator and then retry the upgrade.", { errorCode: detail.errorCode, retry: detail.retry });
|
|
1342
|
+
}
|
|
1343
|
+
function classifyTerminalFailure(status) {
|
|
1344
|
+
return status === "ValidationFailed" || status === "ConflictFixingError" ? { errorCode: "invalid_argument", retry: "RetryWillNotFix" } : {};
|
|
1345
|
+
}
|
|
1346
|
+
async function describeUpgradeFailureAsync(basePath, accessToken, pipelineDeploymentId, finalStatus, scope, signal) {
|
|
1347
|
+
const fromPipeline = formatDeploymentFailureDetails(pipelineDeploymentId, finalStatus);
|
|
1348
|
+
const explained = finalStatus.validationResult?.validationErrors?.length || finalStatus.validationResult?.conflictErrors?.length || finalStatus.conflictFixingErrors?.length || finalStatus.deploymentScheduleErrors?.length || finalStatus.deploymentResult?.errorMessage;
|
|
1349
|
+
if (explained || !finalStatus.deploymentKey) {
|
|
1350
|
+
return fromPipeline;
|
|
1351
|
+
}
|
|
1352
|
+
const deploymentsApi = new DeploymentsApi(new Configuration2({ basePath, accessToken }));
|
|
1353
|
+
const reason = await describeValidationFailureAsync(deploymentsApi, finalStatus.deploymentKey, SolutionDeploymentAction.Upgrade, scope, signal);
|
|
1354
|
+
return reason === NO_SERVER_REASON ? fromPipeline : `Validation: ${reason}
|
|
1355
|
+
${fromPipeline}`;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// ../solution-sdk/src/legacy-package-search.ts
|
|
1359
|
+
var LEGACY_PACKAGE_SEARCH_ROUTE = "POST /api/search/packages";
|
|
1360
|
+
async function searchPackagesLegacy(auth, query, scope) {
|
|
1361
|
+
const api = new SearchApi(new Configuration2({
|
|
1362
|
+
basePath: auth.basePath,
|
|
1363
|
+
accessToken: auth.accessToken
|
|
1364
|
+
}));
|
|
1365
|
+
const response = await api.searchSearchPackagesRaw({
|
|
1366
|
+
searchFilter: {
|
|
1367
|
+
take: query.take,
|
|
1368
|
+
skip: query.skip,
|
|
1369
|
+
searchTerm: query.searchTerm,
|
|
1370
|
+
orderByColumn: query.orderByColumn,
|
|
1371
|
+
orderByDirection: query.orderByDirection
|
|
1372
|
+
}
|
|
1373
|
+
}, scope);
|
|
1374
|
+
return response.raw.json();
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// ../solution-sdk/src/package-search.ts
|
|
1378
|
+
var PIPELINES_SEARCH_ROUTE = "POST /v1/pipelines/packages/search";
|
|
1379
|
+
|
|
1380
|
+
class PackageSearchNeedsUserSessionError extends Error {
|
|
1381
|
+
constructor() {
|
|
1382
|
+
super("This server's package search reads user sessions only; an application (client-credentials) login cannot read it. Sign in with 'uip login' to use it.");
|
|
1383
|
+
this.name = "PackageSearchNeedsUserSessionError";
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
var PACKAGE_ROWS = {
|
|
1387
|
+
singular: "package",
|
|
1388
|
+
plural: "packages",
|
|
1389
|
+
fromJSON: PackageInfoFromJSON,
|
|
1390
|
+
isRow: (row) => isNonEmptyString(row, "key") && isNonEmptyString(row, "name")
|
|
1391
|
+
};
|
|
1392
|
+
function isNonEmptyString(row, field) {
|
|
1393
|
+
const value = field in row ? row[field] : undefined;
|
|
1394
|
+
return typeof value === "string" && value !== "";
|
|
1395
|
+
}
|
|
1396
|
+
var serversWithoutPipelinesSearch = new Set;
|
|
1397
|
+
async function searchPackagesPage(auth, query, scope) {
|
|
1398
|
+
if (!serversWithoutPipelinesSearch.has(auth.basePath)) {
|
|
1399
|
+
const [error, body2] = await catchError(searchViaPipelines(auth, query, scope));
|
|
1400
|
+
if (!error) {
|
|
1401
|
+
return {
|
|
1402
|
+
...parseSearchPage(body2, PIPELINES_SEARCH_ROUTE, PACKAGE_ROWS),
|
|
1403
|
+
source: "pipelines"
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
if (!await isMissingPipelinesRoute(error)) {
|
|
1407
|
+
throw error;
|
|
1408
|
+
}
|
|
1409
|
+
serversWithoutPipelinesSearch.add(auth.basePath);
|
|
1410
|
+
logger.debug(`${PIPELINES_SEARCH_ROUTE} is not served at ${auth.basePath}; using the deprecated ${LEGACY_PACKAGE_SEARCH_ROUTE}.`);
|
|
1411
|
+
}
|
|
1412
|
+
if (isApplicationSession(auth)) {
|
|
1413
|
+
throw new PackageSearchNeedsUserSessionError;
|
|
1414
|
+
}
|
|
1415
|
+
const body = await searchPackagesLegacy(auth, query, scope);
|
|
1416
|
+
return {
|
|
1417
|
+
...parseSearchPage(body, LEGACY_PACKAGE_SEARCH_ROUTE, PACKAGE_ROWS),
|
|
1418
|
+
source: "legacy"
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
async function searchViaPipelines(auth, query, scope) {
|
|
1422
|
+
const api = new PipelinesApi(new Configuration({
|
|
1423
|
+
basePath: auth.basePath,
|
|
1424
|
+
accessToken: auth.accessToken
|
|
1425
|
+
}));
|
|
1426
|
+
const response = await api.pipelinesSearchPackagesRaw({
|
|
1427
|
+
pipelinePackagesSearchRequest: {
|
|
1428
|
+
take: query.take,
|
|
1429
|
+
skip: query.skip,
|
|
1430
|
+
searchTerm: query.searchTerm,
|
|
1431
|
+
orderByColumn: query.orderByColumn,
|
|
1432
|
+
orderByDirection: query.orderByDirection
|
|
1433
|
+
}
|
|
1434
|
+
}, scope);
|
|
1435
|
+
return response.raw.json();
|
|
1436
|
+
}
|
|
1437
|
+
// src/services/packages-list-service.ts
|
|
1438
|
+
var fail5 = (reason, message, instructions, extras = {}) => ({
|
|
1439
|
+
ok: false,
|
|
1440
|
+
reason,
|
|
1441
|
+
message,
|
|
1442
|
+
instructions,
|
|
1443
|
+
exitCode: 1,
|
|
1444
|
+
...extras
|
|
1445
|
+
});
|
|
1446
|
+
var refuseFeedRead2 = (refusal) => fail5("feed_requires_user_session", refusal.message, refusal.instructions, {
|
|
1447
|
+
errorCode: "permission_denied",
|
|
1448
|
+
retry: "RetryWillNotFix"
|
|
1449
|
+
});
|
|
1450
|
+
async function listPackagesAsync(options = {}) {
|
|
1451
|
+
const limit = options.limit ?? DEFAULT_PAGE_SIZE;
|
|
1452
|
+
const offset = options.offset ?? 0;
|
|
1453
|
+
const [authError, auth] = await catchError(getSolutionAuthContext({
|
|
1454
|
+
tenant: options.tenant,
|
|
1455
|
+
loginValidity: options.loginValidity,
|
|
1456
|
+
envFilePath: options.envFilePath
|
|
1457
|
+
}));
|
|
1458
|
+
if (authError) {
|
|
1459
|
+
return fail5("auth_failed", authError.message, authError.message);
|
|
1460
|
+
}
|
|
1461
|
+
if (options.personalWorkspace) {
|
|
1462
|
+
const refusal = feedReadRefusalForApplication(auth, "Listing the Personal Workspace's packages");
|
|
1463
|
+
if (refusal) {
|
|
1464
|
+
return refuseFeedRead2(refusal);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
const [scopeError, scope] = await catchError(resolveFeedScope({
|
|
1468
|
+
personalWorkspace: options.personalWorkspace,
|
|
1469
|
+
feed: options.feed,
|
|
1470
|
+
tenant: options.tenant,
|
|
1471
|
+
loginValidity: options.loginValidity,
|
|
1472
|
+
envFilePath: options.envFilePath
|
|
1473
|
+
}));
|
|
1474
|
+
if (scopeError) {
|
|
1475
|
+
const { message, instructions, ...extras } = await describeFeedResolutionFailureAsync(scopeError, options, auth);
|
|
1476
|
+
return fail5("feed_resolution_failed", message, instructions, extras);
|
|
1477
|
+
}
|
|
1478
|
+
if (scope.kind !== "tenant") {
|
|
1479
|
+
const refusal = feedReadRefusalForApplication(auth, `Listing the packages of the feed '${scope.name ?? scope.folderKey}'`);
|
|
1480
|
+
if (refusal) {
|
|
1481
|
+
return refuseFeedRead2(refusal);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
const [listError, result] = await catchError(searchPackagesPage(auth, {
|
|
1485
|
+
take: limit,
|
|
1486
|
+
skip: offset,
|
|
1487
|
+
orderByColumn: options.sortBy ?? "publishDate",
|
|
1488
|
+
orderByDirection: options.sortOrder ?? "Descending",
|
|
1489
|
+
searchTerm: options.name
|
|
1490
|
+
}, feedScopeInitOverride(scope)));
|
|
1491
|
+
if (listError instanceof PackageSearchNeedsUserSessionError) {
|
|
1492
|
+
return fail5("list_request_failed", `Failed to list packages: ${listError.message}`, "Run it with a user session ('uip login'). A client-credentials login cannot list packages on this server.", { errorCode: "permission_denied", retry: "RetryWillNotFix" });
|
|
1493
|
+
}
|
|
1494
|
+
if (listError) {
|
|
1495
|
+
const { message, instructions, ...extras } = await describeSearchFailureAsync(listError, "packages");
|
|
1496
|
+
return fail5("list_request_failed", message, instructions, extras);
|
|
1497
|
+
}
|
|
1498
|
+
const packages = result.values;
|
|
1499
|
+
return {
|
|
1500
|
+
ok: true,
|
|
1501
|
+
packages,
|
|
1502
|
+
limit,
|
|
1503
|
+
offset,
|
|
1504
|
+
total: result.count
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
export { activateDeploymentAsync, listDeploymentsAsync, uninstallDeploymentAsync, upgradeDeploymentAsync, listPackagesAsync };
|
|
1509
|
+
|
|
1510
|
+
//# debugId=AC06BACB6D23A44764756E2164756E21
|