@postman-cse/onboarding-insights 2.1.1 → 2.1.2
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 +7 -5
- package/action.yml +11 -3
- package/dist/action.cjs +508 -170
- package/dist/cli.cjs +678 -346
- package/dist/index.cjs +512 -153
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -18687,7 +18687,9 @@ __export(index_exports, {
|
|
|
18687
18687
|
createPlannedOutputs: () => createPlannedOutputs,
|
|
18688
18688
|
getInput: () => getInput2,
|
|
18689
18689
|
getTeams: () => getTeams,
|
|
18690
|
+
parseCreateApiKey: () => parseCreateApiKey,
|
|
18690
18691
|
parsePreflightMode: () => parsePreflightMode,
|
|
18692
|
+
parseServiceNotFoundPolicy: () => parseServiceNotFoundPolicy,
|
|
18691
18693
|
resolveApiKeyAndTeamId: () => resolveApiKeyAndTeamId,
|
|
18692
18694
|
resolveInputs: () => resolveInputs,
|
|
18693
18695
|
runAction: () => runAction,
|
|
@@ -21563,6 +21565,42 @@ async function retry(operation, options = {}) {
|
|
|
21563
21565
|
}
|
|
21564
21566
|
throw new Error("Retry exhausted without returning or throwing");
|
|
21565
21567
|
}
|
|
21568
|
+
function isTransientHttpStatus(status) {
|
|
21569
|
+
return status === 408 || status === 429 || status >= 500;
|
|
21570
|
+
}
|
|
21571
|
+
function extractStatus(error2) {
|
|
21572
|
+
if (error2 instanceof HttpError) {
|
|
21573
|
+
return error2.status;
|
|
21574
|
+
}
|
|
21575
|
+
if (error2 && typeof error2 === "object" && "status" in error2) {
|
|
21576
|
+
const status = error2.status;
|
|
21577
|
+
return typeof status === "number" ? status : void 0;
|
|
21578
|
+
}
|
|
21579
|
+
if (error2 && typeof error2 === "object" && "cause" in error2) {
|
|
21580
|
+
return extractStatus(error2.cause);
|
|
21581
|
+
}
|
|
21582
|
+
return void 0;
|
|
21583
|
+
}
|
|
21584
|
+
function shouldRetryReadError(error2) {
|
|
21585
|
+
const status = extractStatus(error2);
|
|
21586
|
+
if (status === void 0) {
|
|
21587
|
+
return true;
|
|
21588
|
+
}
|
|
21589
|
+
return isTransientHttpStatus(status);
|
|
21590
|
+
}
|
|
21591
|
+
function isAmbiguousMutationFailure(error2) {
|
|
21592
|
+
const status = extractStatus(error2);
|
|
21593
|
+
if (status === void 0) {
|
|
21594
|
+
return true;
|
|
21595
|
+
}
|
|
21596
|
+
return isTransientHttpStatus(status);
|
|
21597
|
+
}
|
|
21598
|
+
var SAFE_READ_RETRY = {
|
|
21599
|
+
maxAttempts: 3,
|
|
21600
|
+
delayMs: 2e3,
|
|
21601
|
+
backoffMultiplier: 2,
|
|
21602
|
+
shouldRetry: (error2) => shouldRetryReadError(error2)
|
|
21603
|
+
};
|
|
21566
21604
|
|
|
21567
21605
|
// src/lib/postman/base-urls.ts
|
|
21568
21606
|
var POSTMAN_ENDPOINT_PROFILES = {
|
|
@@ -21777,6 +21815,27 @@ var MAX_PROVIDER_SERVICE_PAGES = 100;
|
|
|
21777
21815
|
function isExpiredAuthError(status, body) {
|
|
21778
21816
|
return status === 401 || body.includes("UNAUTHENTICATED") || body.includes("authenticationError");
|
|
21779
21817
|
}
|
|
21818
|
+
function normalizeRepoUrl(url) {
|
|
21819
|
+
return url.trim().replace(/\/+$/, "").toLowerCase();
|
|
21820
|
+
}
|
|
21821
|
+
async function mutateOnceThenReconcile(options) {
|
|
21822
|
+
const existing = await options.findExisting();
|
|
21823
|
+
if (existing !== null) {
|
|
21824
|
+
return existing;
|
|
21825
|
+
}
|
|
21826
|
+
try {
|
|
21827
|
+
return await options.mutate();
|
|
21828
|
+
} catch (error2) {
|
|
21829
|
+
if (!isAmbiguousMutationFailure(error2)) {
|
|
21830
|
+
throw error2;
|
|
21831
|
+
}
|
|
21832
|
+
const adopted = await options.findExisting();
|
|
21833
|
+
if (adopted !== null) {
|
|
21834
|
+
return adopted;
|
|
21835
|
+
}
|
|
21836
|
+
throw error2;
|
|
21837
|
+
}
|
|
21838
|
+
}
|
|
21780
21839
|
var BifrostCatalogClient = class {
|
|
21781
21840
|
tokenProvider;
|
|
21782
21841
|
teamId;
|
|
@@ -21813,8 +21872,9 @@ var BifrostCatalogClient = class {
|
|
|
21813
21872
|
}
|
|
21814
21873
|
/**
|
|
21815
21874
|
* Build Bifrost proxy headers.
|
|
21816
|
-
* x-entity-team-id is ONLY included when teamId is present (
|
|
21817
|
-
* Non-org-mode tokens must OMIT it so Bifrost resolves team
|
|
21875
|
+
* x-entity-team-id is ONLY included when teamId is present (explicit input /
|
|
21876
|
+
* POSTMAN_TEAM_ID). Non-org-mode tokens must OMIT it so Bifrost resolves team
|
|
21877
|
+
* from the access token. Team is never inferred from PMAK.
|
|
21818
21878
|
*/
|
|
21819
21879
|
headers() {
|
|
21820
21880
|
const h = {
|
|
@@ -21842,7 +21902,7 @@ var BifrostCatalogClient = class {
|
|
|
21842
21902
|
mask: createSecretMasker(this.secretValues)
|
|
21843
21903
|
};
|
|
21844
21904
|
}
|
|
21845
|
-
async proxyRequest(method, path6, body = {}, operation = "api-catalog request") {
|
|
21905
|
+
async proxyRequest(method, path6, body = {}, operation = "api-catalog request", allowAuthReplay = false) {
|
|
21846
21906
|
const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
|
|
21847
21907
|
method: "POST",
|
|
21848
21908
|
headers: this.headers(),
|
|
@@ -21856,7 +21916,7 @@ var BifrostCatalogClient = class {
|
|
|
21856
21916
|
let response = await send2();
|
|
21857
21917
|
if (!response.ok) {
|
|
21858
21918
|
const bodyText = await response.text().catch(() => "");
|
|
21859
|
-
if (isExpiredAuthError(response.status, bodyText) && this.tokenProvider.canRefresh()) {
|
|
21919
|
+
if (allowAuthReplay && isExpiredAuthError(response.status, bodyText) && this.tokenProvider.canRefresh()) {
|
|
21860
21920
|
try {
|
|
21861
21921
|
const refreshed = await this.tokenProvider.refresh();
|
|
21862
21922
|
this.registerAccessToken(refreshed);
|
|
@@ -21907,7 +21967,7 @@ var BifrostCatalogClient = class {
|
|
|
21907
21967
|
}
|
|
21908
21968
|
return data;
|
|
21909
21969
|
}
|
|
21910
|
-
async akitaProxyRequest(method, path6, body = {}, operation = "Insights request") {
|
|
21970
|
+
async akitaProxyRequest(method, path6, body = {}, operation = "Insights request", allowAuthReplay = false) {
|
|
21911
21971
|
const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
|
|
21912
21972
|
method: "POST",
|
|
21913
21973
|
headers: this.headers(),
|
|
@@ -21921,7 +21981,7 @@ var BifrostCatalogClient = class {
|
|
|
21921
21981
|
let response = await send2();
|
|
21922
21982
|
if (!response.ok) {
|
|
21923
21983
|
const text = await response.text().catch(() => "");
|
|
21924
|
-
if (isExpiredAuthError(response.status, text) && this.tokenProvider.canRefresh()) {
|
|
21984
|
+
if (allowAuthReplay && isExpiredAuthError(response.status, text) && this.tokenProvider.canRefresh()) {
|
|
21925
21985
|
try {
|
|
21926
21986
|
const refreshed = await this.tokenProvider.refresh();
|
|
21927
21987
|
this.registerAccessToken(refreshed);
|
|
@@ -21950,6 +22010,17 @@ ${advised.message}` : advised.message : text;
|
|
|
21950
22010
|
const data = await response.json();
|
|
21951
22011
|
return { ok: true, status: response.status, data, errorText: "" };
|
|
21952
22012
|
}
|
|
22013
|
+
throwAkitaFailure(status, errorText, operation, path6) {
|
|
22014
|
+
const httpErr = new HttpError({
|
|
22015
|
+
method: "POST",
|
|
22016
|
+
url: `bifrost:akita:${path6}`,
|
|
22017
|
+
status,
|
|
22018
|
+
statusText: status >= 500 ? "Error" : "Client Error",
|
|
22019
|
+
responseBody: errorText
|
|
22020
|
+
});
|
|
22021
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
|
|
22022
|
+
throw advised ?? httpErr;
|
|
22023
|
+
}
|
|
21953
22024
|
async listDiscoveredServices() {
|
|
21954
22025
|
return retry(
|
|
21955
22026
|
async () => {
|
|
@@ -21977,26 +22048,80 @@ ${advised.message}` : advised.message : text;
|
|
|
21977
22048
|
}
|
|
21978
22049
|
return allItems;
|
|
21979
22050
|
},
|
|
21980
|
-
|
|
22051
|
+
SAFE_READ_RETRY
|
|
21981
22052
|
);
|
|
21982
22053
|
}
|
|
21983
|
-
|
|
22054
|
+
/** List discovered + integrated services for exact link reconciliation. */
|
|
22055
|
+
async listServicesForReconcile() {
|
|
21984
22056
|
return retry(
|
|
21985
22057
|
async () => {
|
|
22058
|
+
const allItems = [];
|
|
22059
|
+
let cursor = null;
|
|
22060
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
22061
|
+
for (let page = 0; page < MAX_DISCOVERED_SERVICE_PAGES; page += 1) {
|
|
22062
|
+
const query = cursor ? `?cursor=${encodeURIComponent(cursor)}` : "";
|
|
22063
|
+
const data = await this.proxyRequest(
|
|
22064
|
+
"GET",
|
|
22065
|
+
`/api/v1/onboarding/discovered-services${query}`,
|
|
22066
|
+
{},
|
|
22067
|
+
"service link reconciliation"
|
|
22068
|
+
);
|
|
22069
|
+
allItems.push(...data.items || []);
|
|
22070
|
+
if (data.total && allItems.length >= data.total) {
|
|
22071
|
+
break;
|
|
22072
|
+
}
|
|
22073
|
+
const nextCursor = data.nextCursor || null;
|
|
22074
|
+
if (!nextCursor || seenCursors.has(nextCursor)) {
|
|
22075
|
+
break;
|
|
22076
|
+
}
|
|
22077
|
+
seenCursors.add(nextCursor);
|
|
22078
|
+
cursor = nextCursor;
|
|
22079
|
+
}
|
|
22080
|
+
return allItems;
|
|
22081
|
+
},
|
|
22082
|
+
SAFE_READ_RETRY
|
|
22083
|
+
);
|
|
22084
|
+
}
|
|
22085
|
+
async findPreparedCollection(serviceId, workspaceId) {
|
|
22086
|
+
const services = await this.listServicesForReconcile();
|
|
22087
|
+
const match = services.find(
|
|
22088
|
+
(service) => service.id === serviceId && Boolean(service.collectionId) && service.workspaceId === workspaceId
|
|
22089
|
+
);
|
|
22090
|
+
return match?.collectionId ? String(match.collectionId) : null;
|
|
22091
|
+
}
|
|
22092
|
+
async findGitLink(params) {
|
|
22093
|
+
const services = await this.listServicesForReconcile();
|
|
22094
|
+
const match = services.find((service) => {
|
|
22095
|
+
if (service.id !== params.serviceId) return false;
|
|
22096
|
+
if (service.workspaceId !== params.workspaceId) return false;
|
|
22097
|
+
if (service.environmentId !== params.environmentId) return false;
|
|
22098
|
+
if (!service.gitRepositoryUrl) return false;
|
|
22099
|
+
return normalizeRepoUrl(service.gitRepositoryUrl) === normalizeRepoUrl(params.gitRepositoryUrl);
|
|
22100
|
+
});
|
|
22101
|
+
return match ? true : null;
|
|
22102
|
+
}
|
|
22103
|
+
async prepareCollection(serviceId, workspaceId) {
|
|
22104
|
+
return mutateOnceThenReconcile({
|
|
22105
|
+
findExisting: () => this.findPreparedCollection(serviceId, workspaceId),
|
|
22106
|
+
mutate: async () => {
|
|
21986
22107
|
const data = await this.proxyRequest(
|
|
21987
22108
|
"POST",
|
|
21988
22109
|
"/api/v1/onboarding/prepare-collection",
|
|
21989
22110
|
{ service_id: String(serviceId), workspace_id: workspaceId },
|
|
21990
|
-
"collection preparation"
|
|
22111
|
+
"collection preparation",
|
|
22112
|
+
false
|
|
21991
22113
|
);
|
|
22114
|
+
if (!data?.id) {
|
|
22115
|
+
throw new Error("prepare-collection succeeded without a collection id");
|
|
22116
|
+
}
|
|
21992
22117
|
return data.id;
|
|
21993
|
-
}
|
|
21994
|
-
|
|
21995
|
-
);
|
|
22118
|
+
}
|
|
22119
|
+
});
|
|
21996
22120
|
}
|
|
21997
22121
|
async onboardGit(params) {
|
|
21998
|
-
await
|
|
21999
|
-
|
|
22122
|
+
await mutateOnceThenReconcile({
|
|
22123
|
+
findExisting: () => this.findGitLink(params),
|
|
22124
|
+
mutate: async () => {
|
|
22000
22125
|
const body = {
|
|
22001
22126
|
via_integrations: false,
|
|
22002
22127
|
git_service_name: "github",
|
|
@@ -22012,24 +22137,32 @@ ${advised.message}` : advised.message : text;
|
|
|
22012
22137
|
"POST",
|
|
22013
22138
|
"/api/v1/onboarding/git",
|
|
22014
22139
|
body,
|
|
22015
|
-
"git onboarding"
|
|
22140
|
+
"git onboarding",
|
|
22141
|
+
false
|
|
22016
22142
|
);
|
|
22017
|
-
|
|
22018
|
-
|
|
22019
|
-
);
|
|
22143
|
+
return true;
|
|
22144
|
+
}
|
|
22145
|
+
});
|
|
22020
22146
|
}
|
|
22021
|
-
async
|
|
22147
|
+
async listProviderServices() {
|
|
22022
22148
|
const allServices = [];
|
|
22023
22149
|
let page = 1;
|
|
22024
22150
|
const pageSize = 100;
|
|
22025
22151
|
for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
|
|
22026
22152
|
const result = await this.akitaProxyRequest(
|
|
22027
22153
|
"GET",
|
|
22028
|
-
`/v2/api-catalog/services?
|
|
22154
|
+
`/v2/api-catalog/services?populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
|
|
22029
22155
|
{},
|
|
22030
22156
|
"provider service resolution"
|
|
22031
22157
|
);
|
|
22032
|
-
if (!result.ok || !result.data)
|
|
22158
|
+
if (!result.ok || !result.data) {
|
|
22159
|
+
this.throwAkitaFailure(
|
|
22160
|
+
result.status,
|
|
22161
|
+
result.errorText,
|
|
22162
|
+
"provider service resolution",
|
|
22163
|
+
"GET /v2/api-catalog/services"
|
|
22164
|
+
);
|
|
22165
|
+
}
|
|
22033
22166
|
const services = result.data.services || [];
|
|
22034
22167
|
allServices.push(...services);
|
|
22035
22168
|
if (result.data.total && allServices.length >= result.data.total) {
|
|
@@ -22040,86 +22173,225 @@ ${advised.message}` : advised.message : text;
|
|
|
22040
22173
|
}
|
|
22041
22174
|
page++;
|
|
22042
22175
|
}
|
|
22176
|
+
return allServices;
|
|
22177
|
+
}
|
|
22178
|
+
async resolveProviderServiceId(projectName, clusterName) {
|
|
22179
|
+
const allServices = await retry(
|
|
22180
|
+
async () => this.listProviderServices(),
|
|
22181
|
+
SAFE_READ_RETRY
|
|
22182
|
+
);
|
|
22043
22183
|
if (clusterName) {
|
|
22044
22184
|
const fullName = `${clusterName}/${projectName}`;
|
|
22045
22185
|
const exactMatch = allServices.find((s) => s.name === fullName);
|
|
22046
22186
|
return exactMatch?.id || null;
|
|
22047
22187
|
}
|
|
22048
|
-
const
|
|
22188
|
+
const finalSegmentMatches = allServices.filter(
|
|
22049
22189
|
(s) => getFinalServiceSegment(s.name) === projectName
|
|
22050
22190
|
);
|
|
22051
|
-
if (
|
|
22052
|
-
|
|
22191
|
+
if (finalSegmentMatches.length > 1) {
|
|
22192
|
+
throw new Error(
|
|
22193
|
+
`Ambiguous Insights provider service "${projectName}": multiple final-segment matches (${finalSegmentMatches.map((s) => s.name).join(", ")}). Provide cluster-name to select the canonical service identity.`
|
|
22194
|
+
);
|
|
22195
|
+
}
|
|
22196
|
+
if (finalSegmentMatches.length === 1) return finalSegmentMatches[0].id;
|
|
22197
|
+
const bracketedMatches = allServices.filter(
|
|
22053
22198
|
(s) => getFinalServiceSegment(s.name).includes(`[${projectName}]`)
|
|
22054
22199
|
);
|
|
22055
|
-
|
|
22200
|
+
if (bracketedMatches.length > 1) {
|
|
22201
|
+
throw new Error(
|
|
22202
|
+
`Ambiguous Insights provider service "${projectName}": multiple bracketed matches. Provide cluster-name to select the canonical service identity.`
|
|
22203
|
+
);
|
|
22204
|
+
}
|
|
22205
|
+
return bracketedMatches[0]?.id || null;
|
|
22206
|
+
}
|
|
22207
|
+
async findAcknowledgedOnboarding(providerServiceId, workspaceId, systemEnvironmentId) {
|
|
22208
|
+
const services = await this.listProviderServices();
|
|
22209
|
+
const match = services.find(
|
|
22210
|
+
(service) => service.id === providerServiceId && service.workspace_id === workspaceId && service.system_env === systemEnvironmentId && (service.status === "onboarded" || service.status === "integrated" || Boolean(service.workspace_id))
|
|
22211
|
+
);
|
|
22212
|
+
return match ? true : null;
|
|
22056
22213
|
}
|
|
22057
22214
|
async acknowledgeOnboarding(providerServiceId, workspaceId, systemEnvironmentId) {
|
|
22058
|
-
|
|
22059
|
-
|
|
22060
|
-
|
|
22061
|
-
|
|
22062
|
-
|
|
22063
|
-
|
|
22064
|
-
|
|
22065
|
-
|
|
22066
|
-
|
|
22215
|
+
await mutateOnceThenReconcile({
|
|
22216
|
+
findExisting: () => this.findAcknowledgedOnboarding(providerServiceId, workspaceId, systemEnvironmentId),
|
|
22217
|
+
mutate: async () => {
|
|
22218
|
+
const result = await this.akitaProxyRequest(
|
|
22219
|
+
"POST",
|
|
22220
|
+
"/v2/api-catalog/services/onboard",
|
|
22221
|
+
{
|
|
22222
|
+
services: [{
|
|
22223
|
+
service_id: providerServiceId,
|
|
22224
|
+
workspace_id: workspaceId,
|
|
22225
|
+
system_env: systemEnvironmentId
|
|
22226
|
+
}]
|
|
22227
|
+
},
|
|
22228
|
+
"Insights onboarding acknowledgment",
|
|
22229
|
+
false
|
|
22230
|
+
);
|
|
22231
|
+
if (!result.ok) {
|
|
22232
|
+
this.throwAkitaFailure(
|
|
22233
|
+
result.status,
|
|
22234
|
+
result.errorText,
|
|
22235
|
+
"Insights onboarding acknowledgment",
|
|
22236
|
+
"POST /v2/api-catalog/services/onboard"
|
|
22237
|
+
);
|
|
22238
|
+
}
|
|
22239
|
+
return true;
|
|
22240
|
+
}
|
|
22241
|
+
});
|
|
22242
|
+
}
|
|
22243
|
+
async findWorkspaceAcknowledged(workspaceId) {
|
|
22244
|
+
const result = await retry(
|
|
22245
|
+
async () => {
|
|
22246
|
+
const response = await this.akitaProxyRequest(
|
|
22247
|
+
"GET",
|
|
22248
|
+
`/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
|
|
22249
|
+
{},
|
|
22250
|
+
"workspace onboarding acknowledgment status"
|
|
22251
|
+
);
|
|
22252
|
+
if (!response.ok) {
|
|
22253
|
+
this.throwAkitaFailure(
|
|
22254
|
+
response.status,
|
|
22255
|
+
response.errorText,
|
|
22256
|
+
"workspace onboarding acknowledgment status",
|
|
22257
|
+
`GET /v2/workspaces/${workspaceId}/onboarding/acknowledge`
|
|
22258
|
+
);
|
|
22259
|
+
}
|
|
22260
|
+
return response;
|
|
22067
22261
|
},
|
|
22068
|
-
|
|
22262
|
+
SAFE_READ_RETRY
|
|
22069
22263
|
);
|
|
22070
|
-
|
|
22071
|
-
throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
|
|
22072
|
-
}
|
|
22264
|
+
return result.data?.onboarding_acknowledged ? true : null;
|
|
22073
22265
|
}
|
|
22074
22266
|
async acknowledgeWorkspace(workspaceId) {
|
|
22075
|
-
|
|
22076
|
-
|
|
22077
|
-
|
|
22078
|
-
|
|
22079
|
-
|
|
22080
|
-
|
|
22081
|
-
|
|
22082
|
-
|
|
22083
|
-
|
|
22267
|
+
await mutateOnceThenReconcile({
|
|
22268
|
+
findExisting: () => this.findWorkspaceAcknowledged(workspaceId),
|
|
22269
|
+
mutate: async () => {
|
|
22270
|
+
const result = await this.akitaProxyRequest(
|
|
22271
|
+
"POST",
|
|
22272
|
+
`/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
|
|
22273
|
+
{},
|
|
22274
|
+
"workspace onboarding acknowledgment",
|
|
22275
|
+
false
|
|
22276
|
+
);
|
|
22277
|
+
if (!result.ok) {
|
|
22278
|
+
this.throwAkitaFailure(
|
|
22279
|
+
result.status,
|
|
22280
|
+
result.errorText,
|
|
22281
|
+
"workspace onboarding acknowledgment",
|
|
22282
|
+
`POST /v2/workspaces/${workspaceId}/onboarding/acknowledge`
|
|
22283
|
+
);
|
|
22284
|
+
}
|
|
22285
|
+
return true;
|
|
22286
|
+
}
|
|
22287
|
+
});
|
|
22084
22288
|
}
|
|
22085
|
-
|
|
22086
|
-
// application-binding endpoint (POST /v2/agent/api-catalog/workspaces/:id/
|
|
22087
|
-
// applications) showed x-access-token is rejected identically to the x-api-key
|
|
22088
|
-
// control: both return 401 {"message":"Postman User not found"} for a
|
|
22089
|
-
// service-account credential, because the observability service has no
|
|
22090
|
-
// "Postman User" for a service account. The access token offers no improvement
|
|
22091
|
-
// over the API key here, so this route is not migrated to access-token-primary
|
|
22092
|
-
// (the suite-wide migration explicitly leaves probe-failed routes on PMAK).
|
|
22093
|
-
async createApplication(workspaceId, systemEnv) {
|
|
22289
|
+
async findApplication(workspaceId, systemEnv, expectedServiceId) {
|
|
22094
22290
|
const response = await this.fetchFn(
|
|
22095
22291
|
`${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
|
|
22096
22292
|
{
|
|
22097
|
-
method: "
|
|
22293
|
+
method: "GET",
|
|
22098
22294
|
headers: {
|
|
22099
22295
|
"x-api-key": this.apiKey,
|
|
22100
22296
|
"x-postman-env": this.observabilityEnv,
|
|
22101
22297
|
"Content-Type": "application/json"
|
|
22102
|
-
}
|
|
22103
|
-
body: JSON.stringify({ system_env: systemEnv })
|
|
22298
|
+
}
|
|
22104
22299
|
}
|
|
22105
22300
|
);
|
|
22106
22301
|
if (!response.ok) {
|
|
22107
22302
|
const httpErr = await HttpError.fromResponse(response, {
|
|
22108
|
-
method: "
|
|
22109
|
-
url: `observability:
|
|
22303
|
+
method: "GET",
|
|
22304
|
+
url: `observability:listApplications(${workspaceId})`,
|
|
22110
22305
|
secretValues: this.secretValues
|
|
22111
22306
|
});
|
|
22112
|
-
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
|
|
22307
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding lookup"));
|
|
22113
22308
|
throw advised ?? httpErr;
|
|
22114
22309
|
}
|
|
22115
|
-
|
|
22310
|
+
const data = await response.json();
|
|
22311
|
+
const matches = (data.applications || []).filter(
|
|
22312
|
+
(app) => app.application_id && app.service_id && app.system_env === systemEnv && (!expectedServiceId || app.service_id === expectedServiceId)
|
|
22313
|
+
);
|
|
22314
|
+
if (matches.length > 1) {
|
|
22315
|
+
throw new Error(
|
|
22316
|
+
`Multiple application bindings match workspace ${workspaceId}, system environment ${systemEnv}, and service ${expectedServiceId || "(unspecified)"}`
|
|
22317
|
+
);
|
|
22318
|
+
}
|
|
22319
|
+
const match = matches[0];
|
|
22320
|
+
if (!match?.application_id || !match.service_id) {
|
|
22321
|
+
return null;
|
|
22322
|
+
}
|
|
22323
|
+
return {
|
|
22324
|
+
application_id: String(match.application_id),
|
|
22325
|
+
service_id: String(match.service_id)
|
|
22326
|
+
};
|
|
22327
|
+
}
|
|
22328
|
+
// PMAK-only by proven exception. A live probe against the observability
|
|
22329
|
+
// application-binding endpoint (POST /v2/agent/api-catalog/workspaces/:id/
|
|
22330
|
+
// applications) showed x-access-token is rejected identically to the x-api-key
|
|
22331
|
+
// control: both return 401 {"message":"Postman User not found"} for a
|
|
22332
|
+
// service-account credential, because the observability service has no
|
|
22333
|
+
// "Postman User" for a service account. The access token offers no improvement
|
|
22334
|
+
// over the API key here, so this route is not migrated to access-token-primary
|
|
22335
|
+
// (the suite-wide migration explicitly leaves probe-failed routes on PMAK).
|
|
22336
|
+
async createApplication(workspaceId, systemEnv, expectedServiceId) {
|
|
22337
|
+
return mutateOnceThenReconcile({
|
|
22338
|
+
findExisting: () => retry(
|
|
22339
|
+
() => this.findApplication(workspaceId, systemEnv, expectedServiceId),
|
|
22340
|
+
SAFE_READ_RETRY
|
|
22341
|
+
),
|
|
22342
|
+
mutate: async () => {
|
|
22343
|
+
const response = await this.fetchFn(
|
|
22344
|
+
`${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
|
|
22345
|
+
{
|
|
22346
|
+
method: "POST",
|
|
22347
|
+
headers: {
|
|
22348
|
+
"x-api-key": this.apiKey,
|
|
22349
|
+
"x-postman-env": this.observabilityEnv,
|
|
22350
|
+
"Content-Type": "application/json"
|
|
22351
|
+
},
|
|
22352
|
+
body: JSON.stringify({ system_env: systemEnv })
|
|
22353
|
+
}
|
|
22354
|
+
);
|
|
22355
|
+
if (!response.ok) {
|
|
22356
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
22357
|
+
method: "POST",
|
|
22358
|
+
url: `observability:createApplication(${workspaceId})`,
|
|
22359
|
+
secretValues: this.secretValues
|
|
22360
|
+
});
|
|
22361
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
|
|
22362
|
+
throw advised ?? httpErr;
|
|
22363
|
+
}
|
|
22364
|
+
const created = await response.json();
|
|
22365
|
+
if (expectedServiceId && created.service_id !== expectedServiceId) {
|
|
22366
|
+
throw new Error(
|
|
22367
|
+
`Application binding credential/scope mismatch: expected service ${expectedServiceId}, received ${created.service_id}`
|
|
22368
|
+
);
|
|
22369
|
+
}
|
|
22370
|
+
return created;
|
|
22371
|
+
}
|
|
22372
|
+
});
|
|
22116
22373
|
}
|
|
22117
22374
|
async getTeamVerificationToken(workspaceId) {
|
|
22118
|
-
const result = await
|
|
22119
|
-
|
|
22120
|
-
|
|
22121
|
-
|
|
22122
|
-
|
|
22375
|
+
const result = await retry(
|
|
22376
|
+
async () => {
|
|
22377
|
+
const page = await this.akitaProxyRequest(
|
|
22378
|
+
"GET",
|
|
22379
|
+
`/v2/workspaces/${workspaceId}/team-verification-token`,
|
|
22380
|
+
{},
|
|
22381
|
+
"team verification token retrieval"
|
|
22382
|
+
);
|
|
22383
|
+
if (!page.ok && (page.status === 408 || page.status === 429 || page.status >= 500)) {
|
|
22384
|
+
throw new HttpError({
|
|
22385
|
+
method: "GET",
|
|
22386
|
+
url: `bifrost:akita:GET /v2/workspaces/${workspaceId}/team-verification-token`,
|
|
22387
|
+
status: page.status,
|
|
22388
|
+
statusText: "Error",
|
|
22389
|
+
responseBody: page.errorText
|
|
22390
|
+
});
|
|
22391
|
+
}
|
|
22392
|
+
return page;
|
|
22393
|
+
},
|
|
22394
|
+
SAFE_READ_RETRY
|
|
22123
22395
|
);
|
|
22124
22396
|
if (!result.ok || !result.data) return null;
|
|
22125
22397
|
return result.data.team_verification_token || null;
|
|
@@ -22152,18 +22424,52 @@ ${advised.message}` : advised.message : text;
|
|
|
22152
22424
|
return String(apikey.key);
|
|
22153
22425
|
}
|
|
22154
22426
|
};
|
|
22427
|
+
function buildCanonicalServiceIdentity(service, projectName, clusterName, providerServiceId) {
|
|
22428
|
+
const derivedCluster = clusterName || (service.name.includes("/") ? service.name.slice(0, service.name.lastIndexOf("/")) : null);
|
|
22429
|
+
return {
|
|
22430
|
+
serviceId: service.id,
|
|
22431
|
+
serviceName: service.name,
|
|
22432
|
+
clusterName: derivedCluster,
|
|
22433
|
+
projectName,
|
|
22434
|
+
...providerServiceId ? { providerServiceId } : {}
|
|
22435
|
+
};
|
|
22436
|
+
}
|
|
22155
22437
|
function findDiscoveredService(services, projectName, clusterName) {
|
|
22156
22438
|
if (clusterName) {
|
|
22157
22439
|
const fullName = `${clusterName}/${projectName}`;
|
|
22158
|
-
|
|
22440
|
+
const match = services.find((s) => s.name === fullName);
|
|
22441
|
+
if (match) {
|
|
22442
|
+
match.canonicalIdentity = buildCanonicalServiceIdentity(match, projectName, clusterName);
|
|
22443
|
+
}
|
|
22444
|
+
return match;
|
|
22159
22445
|
}
|
|
22160
|
-
const
|
|
22446
|
+
const finalSegmentMatches = services.filter(
|
|
22161
22447
|
(service) => getFinalServiceSegment(service.name) === projectName
|
|
22162
22448
|
);
|
|
22163
|
-
if (
|
|
22164
|
-
|
|
22449
|
+
if (finalSegmentMatches.length > 1) {
|
|
22450
|
+
throw new Error(
|
|
22451
|
+
`Ambiguous discovered service "${projectName}": multiple final-segment matches (${finalSegmentMatches.map((s) => s.name).join(", ")}). cluster-name is required to select the canonical service identity.`
|
|
22452
|
+
);
|
|
22453
|
+
}
|
|
22454
|
+
if (finalSegmentMatches.length === 1) {
|
|
22455
|
+
const match = finalSegmentMatches[0];
|
|
22456
|
+
match.canonicalIdentity = buildCanonicalServiceIdentity(match, projectName);
|
|
22457
|
+
return match;
|
|
22458
|
+
}
|
|
22459
|
+
const bracketedMatches = services.filter(
|
|
22165
22460
|
(service) => getFinalServiceSegment(service.name).includes(`[${projectName}]`)
|
|
22166
22461
|
);
|
|
22462
|
+
if (bracketedMatches.length > 1) {
|
|
22463
|
+
throw new Error(
|
|
22464
|
+
`Ambiguous discovered service "${projectName}": multiple bracketed matches. cluster-name is required to select the canonical service identity.`
|
|
22465
|
+
);
|
|
22466
|
+
}
|
|
22467
|
+
if (bracketedMatches.length === 1) {
|
|
22468
|
+
const match = bracketedMatches[0];
|
|
22469
|
+
match.canonicalIdentity = buildCanonicalServiceIdentity(match, projectName);
|
|
22470
|
+
return match;
|
|
22471
|
+
}
|
|
22472
|
+
return void 0;
|
|
22167
22473
|
}
|
|
22168
22474
|
function getFinalServiceSegment(serviceName) {
|
|
22169
22475
|
const lastSlash = serviceName.lastIndexOf("/");
|
|
@@ -22355,7 +22661,7 @@ function normalize(value) {
|
|
|
22355
22661
|
const trimmed = (value ?? "").trim();
|
|
22356
22662
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
22357
22663
|
}
|
|
22358
|
-
function
|
|
22664
|
+
function normalizeRepoUrl2(url) {
|
|
22359
22665
|
const raw = normalize(url);
|
|
22360
22666
|
if (!raw) {
|
|
22361
22667
|
return void 0;
|
|
@@ -22423,7 +22729,7 @@ function classifyRefKind(env = process.env) {
|
|
|
22423
22729
|
return "unknown";
|
|
22424
22730
|
}
|
|
22425
22731
|
function detectRepoContext(input, env = process.env) {
|
|
22426
|
-
const repoUrl =
|
|
22732
|
+
const repoUrl = normalizeRepoUrl2(input.repoUrl) ?? normalizeRepoUrl2(env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}` : void 0) ?? normalizeRepoUrl2(env.CI_PROJECT_URL) ?? normalizeRepoUrl2(env.BITBUCKET_GIT_HTTP_ORIGIN) ?? normalizeRepoUrl2(env.BUILD_REPOSITORY_URI);
|
|
22427
22733
|
const repoSlug = normalize(input.repoSlug) ?? normalize(env.GITHUB_REPOSITORY) ?? normalize(env.CI_PROJECT_PATH) ?? (env.BITBUCKET_WORKSPACE && env.BITBUCKET_REPO_SLUG ? normalize(`${env.BITBUCKET_WORKSPACE}/${env.BITBUCKET_REPO_SLUG}`) : void 0) ?? normalize(env.BUILD_REPOSITORY_NAME);
|
|
22428
22734
|
const ref = normalize(input.ref) ?? normalize(env.GITHUB_REF_NAME) ?? normalize(env.CI_COMMIT_REF_NAME) ?? normalize(env.BITBUCKET_BRANCH) ?? normalize(env.BUILD_SOURCEBRANCHNAME);
|
|
22429
22735
|
const sha = normalize(input.sha) ?? normalize(env.GITHUB_SHA) ?? normalize(env.CI_COMMIT_SHA) ?? normalize(env.BITBUCKET_COMMIT) ?? normalize(env.BUILD_SOURCEVERSION);
|
|
@@ -22600,7 +22906,7 @@ var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
|
|
|
22600
22906
|
var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
|
|
22601
22907
|
var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
|
|
22602
22908
|
function parsePreflightMode(value) {
|
|
22603
|
-
const normalized = String(value || "
|
|
22909
|
+
const normalized = String(value || "enforce").trim().toLowerCase();
|
|
22604
22910
|
if (normalized === "enforce" || normalized === "warn") {
|
|
22605
22911
|
return normalized;
|
|
22606
22912
|
}
|
|
@@ -22608,6 +22914,27 @@ function parsePreflightMode(value) {
|
|
|
22608
22914
|
`Unsupported credential-preflight "${value}". Supported values: enforce, warn`
|
|
22609
22915
|
);
|
|
22610
22916
|
}
|
|
22917
|
+
function parseServiceNotFoundPolicy(value) {
|
|
22918
|
+
const normalized = String(value || "fail").trim().toLowerCase();
|
|
22919
|
+
if (normalized === "fail" || normalized === "warn") {
|
|
22920
|
+
return normalized;
|
|
22921
|
+
}
|
|
22922
|
+
throw new Error(
|
|
22923
|
+
`Unsupported service-not-found-policy "${value}". Supported values: fail, warn`
|
|
22924
|
+
);
|
|
22925
|
+
}
|
|
22926
|
+
function parseCreateApiKey(value) {
|
|
22927
|
+
const normalized = String(value || "false").trim().toLowerCase();
|
|
22928
|
+
if (normalized === "true") {
|
|
22929
|
+
return true;
|
|
22930
|
+
}
|
|
22931
|
+
if (normalized === "false" || normalized === "") {
|
|
22932
|
+
return false;
|
|
22933
|
+
}
|
|
22934
|
+
throw new Error(
|
|
22935
|
+
`Unsupported create-api-key "${value}". Supported values: true, false`
|
|
22936
|
+
);
|
|
22937
|
+
}
|
|
22611
22938
|
function trimTrailingSlash(value) {
|
|
22612
22939
|
return value.replace(/\/+$/, "");
|
|
22613
22940
|
}
|
|
@@ -22689,7 +23016,9 @@ function resolveInputs(env = process.env) {
|
|
|
22689
23016
|
postmanApiKey,
|
|
22690
23017
|
postmanTeamId,
|
|
22691
23018
|
githubToken: get("github-token", env.GITHUB_TOKEN || ""),
|
|
22692
|
-
credentialPreflight: parsePreflightMode(get("credential-preflight", "
|
|
23019
|
+
credentialPreflight: parsePreflightMode(get("credential-preflight", "enforce")),
|
|
23020
|
+
createApiKey: parseCreateApiKey(get("create-api-key", "false")),
|
|
23021
|
+
serviceNotFoundPolicy: parseServiceNotFoundPolicy(get("service-not-found-policy", "fail")),
|
|
22693
23022
|
pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
|
|
22694
23023
|
pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
|
|
22695
23024
|
postmanRegion,
|
|
@@ -22715,6 +23044,7 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22715
23044
|
const timeoutMs = inputs.pollTimeoutSeconds * 1e3;
|
|
22716
23045
|
const intervalMs = inputs.pollIntervalSeconds * 1e3;
|
|
22717
23046
|
const startTime = Date.now();
|
|
23047
|
+
const policy = inputs.serviceNotFoundPolicy ?? "fail";
|
|
22718
23048
|
reporter.info(`Looking for discovered service matching "${inputs.clusterName ? `${inputs.clusterName}/` : ""}${inputs.projectName}"...`);
|
|
22719
23049
|
let match = void 0;
|
|
22720
23050
|
while (Date.now() - startTime < timeoutMs) {
|
|
@@ -22729,15 +23059,35 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22729
23059
|
await sleepFn(intervalMs);
|
|
22730
23060
|
}
|
|
22731
23061
|
if (!match) {
|
|
22732
|
-
|
|
22733
|
-
|
|
22734
|
-
|
|
22735
|
-
|
|
22736
|
-
|
|
22737
|
-
|
|
22738
|
-
|
|
22739
|
-
|
|
22740
|
-
|
|
23062
|
+
const message = `Service "${inputs.projectName}" not found in discovered services after ${inputs.pollTimeoutSeconds}s`;
|
|
23063
|
+
if (policy === "warn") {
|
|
23064
|
+
reporter.warning(message);
|
|
23065
|
+
return {
|
|
23066
|
+
discoveredServiceId: 0,
|
|
23067
|
+
discoveredServiceName: "",
|
|
23068
|
+
collectionId: "",
|
|
23069
|
+
applicationId: "",
|
|
23070
|
+
verificationToken: null,
|
|
23071
|
+
status: "not-found"
|
|
23072
|
+
};
|
|
23073
|
+
}
|
|
23074
|
+
throw new Error(`${message}. Full linking requires a discovered service (service-not-found-policy=fail).`);
|
|
23075
|
+
}
|
|
23076
|
+
const canonicalBase = match.canonicalIdentity ?? buildCanonicalServiceIdentity(match, inputs.projectName, inputs.clusterName || void 0);
|
|
23077
|
+
const providerServiceId = await client.resolveProviderServiceId(
|
|
23078
|
+
inputs.projectName,
|
|
23079
|
+
inputs.clusterName || void 0
|
|
23080
|
+
);
|
|
23081
|
+
if (!providerServiceId) {
|
|
23082
|
+
throw new Error(
|
|
23083
|
+
`Insights provider service "${canonicalBase.serviceName}" was not found. Full linking requires one exact canonical service identity.`
|
|
23084
|
+
);
|
|
23085
|
+
}
|
|
23086
|
+
const sysEnvId = inputs.systemEnvironmentId || match.systemEnvironmentId || "";
|
|
23087
|
+
if (!sysEnvId) {
|
|
23088
|
+
throw new Error(
|
|
23089
|
+
`No system environment id is available for "${canonicalBase.serviceName}"; refusing partial linking writes.`
|
|
23090
|
+
);
|
|
22741
23091
|
}
|
|
22742
23092
|
reporter.info(`Preparing collection for service ${match.id} in workspace ${inputs.workspaceId}...`);
|
|
22743
23093
|
const collectionId = await client.prepareCollection(match.id, inputs.workspaceId);
|
|
@@ -22757,27 +23107,12 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22757
23107
|
} else {
|
|
22758
23108
|
reporter.info(`Skipping git onboarding for non-GitHub repo: ${repoUrl}`);
|
|
22759
23109
|
}
|
|
22760
|
-
|
|
22761
|
-
|
|
22762
|
-
|
|
22763
|
-
);
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
const sysEnvId = inputs.systemEnvironmentId || match.systemEnvironmentId || "";
|
|
22767
|
-
if (sysEnvId) {
|
|
22768
|
-
reporter.info(`Acknowledging Insights onboarding for ${providerServiceId}...`);
|
|
22769
|
-
await client.acknowledgeOnboarding(providerServiceId, inputs.workspaceId, sysEnvId);
|
|
22770
|
-
reporter.info(`Insights acknowledged: ${providerServiceId}`);
|
|
22771
|
-
reporter.info(`Creating application binding for workspace ${inputs.workspaceId} with system_env ${sysEnvId}...`);
|
|
22772
|
-
const appResult = await client.createApplication(inputs.workspaceId, sysEnvId);
|
|
22773
|
-
applicationId = appResult.application_id;
|
|
22774
|
-
reporter.info(`Application binding created: ${appResult.application_id} for service ${appResult.service_id}`);
|
|
22775
|
-
} else {
|
|
22776
|
-
reporter.warning("No systemEnvironmentId available; skipping Insights acknowledgment and application binding");
|
|
22777
|
-
}
|
|
22778
|
-
} else {
|
|
22779
|
-
reporter.warning("Could not resolve Akita provider service ID; skipping acknowledgment and application binding");
|
|
22780
|
-
}
|
|
23110
|
+
reporter.info(`Acknowledging Insights onboarding for ${providerServiceId}...`);
|
|
23111
|
+
await client.acknowledgeOnboarding(providerServiceId, inputs.workspaceId, sysEnvId);
|
|
23112
|
+
reporter.info(`Insights acknowledged: ${providerServiceId}`);
|
|
23113
|
+
reporter.info(`Creating application binding for workspace ${inputs.workspaceId} with system_env ${sysEnvId}...`);
|
|
23114
|
+
const appResult = await client.createApplication(inputs.workspaceId, sysEnvId, providerServiceId);
|
|
23115
|
+
reporter.info(`Application binding created: ${appResult.application_id} for service ${appResult.service_id}`);
|
|
22781
23116
|
reporter.info(`Acknowledging workspace onboarding for ${inputs.workspaceId}...`);
|
|
22782
23117
|
await client.acknowledgeWorkspace(inputs.workspaceId);
|
|
22783
23118
|
reporter.info("Workspace onboarding acknowledged");
|
|
@@ -22793,9 +23128,13 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22793
23128
|
discoveredServiceId: match.id,
|
|
22794
23129
|
discoveredServiceName: match.name,
|
|
22795
23130
|
collectionId,
|
|
22796
|
-
applicationId,
|
|
23131
|
+
applicationId: appResult.application_id,
|
|
22797
23132
|
verificationToken,
|
|
22798
|
-
status: "success"
|
|
23133
|
+
status: "success",
|
|
23134
|
+
canonicalIdentity: {
|
|
23135
|
+
...canonicalBase,
|
|
23136
|
+
...providerServiceId ? { providerServiceId } : {}
|
|
23137
|
+
}
|
|
22799
23138
|
};
|
|
22800
23139
|
}
|
|
22801
23140
|
async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
@@ -22804,6 +23143,7 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
22804
23143
|
let keyValid = false;
|
|
22805
23144
|
let pmakIdentity;
|
|
22806
23145
|
const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
|
|
23146
|
+
const createApiKey = inputs.createApiKey === true;
|
|
22807
23147
|
if (apiKey) {
|
|
22808
23148
|
const result = await validateApiKey(apiKey, apiBase);
|
|
22809
23149
|
keyValid = result.valid;
|
|
@@ -22814,49 +23154,36 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
22814
23154
|
}
|
|
22815
23155
|
}
|
|
22816
23156
|
if (!keyValid) {
|
|
22817
|
-
|
|
22818
|
-
|
|
23157
|
+
if (!createApiKey) {
|
|
23158
|
+
if (apiKey) {
|
|
23159
|
+
throw new Error(
|
|
23160
|
+
"postman-api-key is invalid or expired. Provide a valid key, or set create-api-key=true to opt in to durable Bifrost API-key creation."
|
|
23161
|
+
);
|
|
23162
|
+
}
|
|
23163
|
+
throw new Error(
|
|
23164
|
+
"postman-api-key is required for application binding. Provide a valid key, or set create-api-key=true to opt in to durable Bifrost API-key creation."
|
|
23165
|
+
);
|
|
23166
|
+
}
|
|
23167
|
+
reporter.info("create-api-key=true: generating a durable Postman API key via Bifrost identity service...");
|
|
23168
|
+
const keyName = `insights-onboarding-${inputs.projectName}`;
|
|
22819
23169
|
apiKey = await client.createApiKey(keyName);
|
|
22820
23170
|
reporter.setSecret(apiKey);
|
|
22821
23171
|
client.setApiKey(apiKey);
|
|
22822
|
-
|
|
22823
|
-
|
|
22824
|
-
|
|
22825
|
-
if (!resolvedTeamId && apiKey) {
|
|
22826
|
-
try {
|
|
22827
|
-
const teams = await getTeams(apiKey, apiBase);
|
|
22828
|
-
if (teams.length > 1 && teams.every((t) => t.organizationId == null)) {
|
|
22829
|
-
reporter.warning(
|
|
22830
|
-
"GET /teams returned multiple teams but none include organizationId. Org-mode auto-detection may be degraded due to an upstream API change. Set postman-team-id explicitly if Bifrost calls fail."
|
|
22831
|
-
);
|
|
22832
|
-
}
|
|
22833
|
-
const isOrgMode = teams.some((t) => t.organizationId != null);
|
|
22834
|
-
if (isOrgMode) {
|
|
22835
|
-
if (teams.length === 1) {
|
|
22836
|
-
resolvedTeamId = String(teams[0].id);
|
|
22837
|
-
reporter.info(
|
|
22838
|
-
`Org-mode account detected. Using sub-team ${teams[0].id} (${teams[0].name ?? "unknown"}) for Bifrost calls.`
|
|
22839
|
-
);
|
|
22840
|
-
} else {
|
|
22841
|
-
const meResult = await validateApiKey(apiKey, apiBase);
|
|
22842
|
-
const meTeamId = meResult.teamId ? parseInt(meResult.teamId, 10) : NaN;
|
|
22843
|
-
if (!Number.isNaN(meTeamId) && teams.some((t) => t.id === meTeamId)) {
|
|
22844
|
-
resolvedTeamId = String(meTeamId);
|
|
22845
|
-
reporter.info(
|
|
22846
|
-
`Org-mode account detected. Using sub-team ${meTeamId} (from /me) for Bifrost calls.`
|
|
22847
|
-
);
|
|
22848
|
-
}
|
|
22849
|
-
}
|
|
22850
|
-
}
|
|
22851
|
-
} catch {
|
|
23172
|
+
const createdIdentity = await validateApiKey(apiKey, apiBase);
|
|
23173
|
+
if (!createdIdentity.valid) {
|
|
23174
|
+
throw new Error("The explicitly created postman-api-key could not be validated; refusing linking writes.");
|
|
22852
23175
|
}
|
|
23176
|
+
pmakIdentity = { source: "pmak/me", teamId: createdIdentity.teamId };
|
|
23177
|
+
reporter.info(`New API key created successfully (${keyName}).`);
|
|
22853
23178
|
}
|
|
22854
|
-
if (
|
|
22855
|
-
reporter.info(`Using postman-team-id for Bifrost headers: ${
|
|
23179
|
+
if (teamId) {
|
|
23180
|
+
reporter.info(`Using explicit postman-team-id for Bifrost headers: ${teamId}`);
|
|
22856
23181
|
} else {
|
|
22857
|
-
reporter.info(
|
|
23182
|
+
reporter.info(
|
|
23183
|
+
"No postman-team-id / POSTMAN_TEAM_ID provided; omitting x-entity-team-id so Bifrost resolves team from the access token."
|
|
23184
|
+
);
|
|
22858
23185
|
}
|
|
22859
|
-
return { apiKey, teamId
|
|
23186
|
+
return { apiKey, teamId, pmakIdentity };
|
|
22860
23187
|
}
|
|
22861
23188
|
async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl, liveAccessToken) {
|
|
22862
23189
|
const accessToken = liveAccessToken ?? inputs.postmanAccessToken;
|
|
@@ -22914,24 +23241,54 @@ async function runAction() {
|
|
|
22914
23241
|
inputs.postmanTeamId,
|
|
22915
23242
|
inputs.postmanApiKey
|
|
22916
23243
|
);
|
|
22917
|
-
const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
|
|
22918
|
-
const activeTokenProvider = apiKey !== inputs.postmanApiKey ? new AccessTokenProvider({
|
|
22919
|
-
accessToken: tokenProvider.current(),
|
|
22920
|
-
apiKey,
|
|
22921
|
-
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
22922
|
-
onToken: (token) => setSecret(token)
|
|
22923
|
-
}) : tokenProvider;
|
|
22924
23244
|
const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", actionVersion: resolveActionVersion2(), logger: core_exports });
|
|
22925
|
-
telemetry.setTeamId(inputs.postmanTeamId
|
|
23245
|
+
telemetry.setTeamId(inputs.postmanTeamId);
|
|
22926
23246
|
let result;
|
|
22927
23247
|
try {
|
|
23248
|
+
let apiKey = inputs.postmanApiKey;
|
|
23249
|
+
let pmakIdentity;
|
|
23250
|
+
const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
|
|
23251
|
+
if (apiKey) {
|
|
23252
|
+
const validated = await validateApiKey(apiKey, apiBase);
|
|
23253
|
+
if (validated.valid) {
|
|
23254
|
+
pmakIdentity = { source: "pmak/me", teamId: validated.teamId };
|
|
23255
|
+
} else if (!inputs.createApiKey) {
|
|
23256
|
+
throw new Error(
|
|
23257
|
+
"postman-api-key is invalid or expired. Provide a valid key, or set create-api-key=true to opt in to durable Bifrost API-key creation."
|
|
23258
|
+
);
|
|
23259
|
+
} else {
|
|
23260
|
+
warning("Provided postman-api-key is invalid or expired.");
|
|
23261
|
+
}
|
|
23262
|
+
} else if (!inputs.createApiKey) {
|
|
23263
|
+
throw new Error(
|
|
23264
|
+
"postman-api-key is required for application binding. Provide a valid key, or set create-api-key=true to opt in to durable Bifrost API-key creation."
|
|
23265
|
+
);
|
|
23266
|
+
}
|
|
22928
23267
|
await runCredentialPreflightForInputs(
|
|
22929
23268
|
inputs,
|
|
22930
23269
|
pmakIdentity,
|
|
22931
23270
|
core_exports,
|
|
22932
23271
|
void 0,
|
|
22933
|
-
|
|
23272
|
+
tokenProvider.current()
|
|
22934
23273
|
);
|
|
23274
|
+
const resolved = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
|
|
23275
|
+
apiKey = resolved.apiKey;
|
|
23276
|
+
const teamId = resolved.teamId;
|
|
23277
|
+
if (resolved.pmakIdentity?.teamId !== pmakIdentity?.teamId) {
|
|
23278
|
+
await runCredentialPreflightForInputs(
|
|
23279
|
+
inputs,
|
|
23280
|
+
resolved.pmakIdentity,
|
|
23281
|
+
core_exports,
|
|
23282
|
+
void 0,
|
|
23283
|
+
tokenProvider.current()
|
|
23284
|
+
);
|
|
23285
|
+
}
|
|
23286
|
+
const activeTokenProvider = apiKey !== inputs.postmanApiKey ? new AccessTokenProvider({
|
|
23287
|
+
accessToken: tokenProvider.current(),
|
|
23288
|
+
apiKey,
|
|
23289
|
+
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
23290
|
+
onToken: (token) => setSecret(token)
|
|
23291
|
+
}) : tokenProvider;
|
|
22935
23292
|
const client = createInsightsBifrostClient(inputs, activeTokenProvider, teamId, apiKey);
|
|
22936
23293
|
result = await runOnboarding(inputs, client, sleep, core_exports);
|
|
22937
23294
|
} catch (error2) {
|
|
@@ -22968,7 +23325,9 @@ async function runAction() {
|
|
|
22968
23325
|
createPlannedOutputs,
|
|
22969
23326
|
getInput,
|
|
22970
23327
|
getTeams,
|
|
23328
|
+
parseCreateApiKey,
|
|
22971
23329
|
parsePreflightMode,
|
|
23330
|
+
parseServiceNotFoundPolicy,
|
|
22972
23331
|
resolveApiKeyAndTeamId,
|
|
22973
23332
|
resolveInputs,
|
|
22974
23333
|
runAction,
|