@postman-cse/onboarding-insights 2.1.0 → 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 +538 -171
- package/dist/cli.cjs +879 -391
- package/dist/index.cjs +544 -154
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -18685,8 +18685,11 @@ __export(index_exports, {
|
|
|
18685
18685
|
createInsightsBifrostClient: () => createInsightsBifrostClient,
|
|
18686
18686
|
createInsightsTokenProvider: () => createInsightsTokenProvider,
|
|
18687
18687
|
createPlannedOutputs: () => createPlannedOutputs,
|
|
18688
|
+
getInput: () => getInput2,
|
|
18688
18689
|
getTeams: () => getTeams,
|
|
18690
|
+
parseCreateApiKey: () => parseCreateApiKey,
|
|
18689
18691
|
parsePreflightMode: () => parsePreflightMode,
|
|
18692
|
+
parseServiceNotFoundPolicy: () => parseServiceNotFoundPolicy,
|
|
18690
18693
|
resolveApiKeyAndTeamId: () => resolveApiKeyAndTeamId,
|
|
18691
18694
|
resolveInputs: () => resolveInputs,
|
|
18692
18695
|
runAction: () => runAction,
|
|
@@ -21562,6 +21565,42 @@ async function retry(operation, options = {}) {
|
|
|
21562
21565
|
}
|
|
21563
21566
|
throw new Error("Retry exhausted without returning or throwing");
|
|
21564
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
|
+
};
|
|
21565
21604
|
|
|
21566
21605
|
// src/lib/postman/base-urls.ts
|
|
21567
21606
|
var POSTMAN_ENDPOINT_PROFILES = {
|
|
@@ -21776,6 +21815,27 @@ var MAX_PROVIDER_SERVICE_PAGES = 100;
|
|
|
21776
21815
|
function isExpiredAuthError(status, body) {
|
|
21777
21816
|
return status === 401 || body.includes("UNAUTHENTICATED") || body.includes("authenticationError");
|
|
21778
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
|
+
}
|
|
21779
21839
|
var BifrostCatalogClient = class {
|
|
21780
21840
|
tokenProvider;
|
|
21781
21841
|
teamId;
|
|
@@ -21812,8 +21872,9 @@ var BifrostCatalogClient = class {
|
|
|
21812
21872
|
}
|
|
21813
21873
|
/**
|
|
21814
21874
|
* Build Bifrost proxy headers.
|
|
21815
|
-
* x-entity-team-id is ONLY included when teamId is present (
|
|
21816
|
-
* 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.
|
|
21817
21878
|
*/
|
|
21818
21879
|
headers() {
|
|
21819
21880
|
const h = {
|
|
@@ -21841,7 +21902,7 @@ var BifrostCatalogClient = class {
|
|
|
21841
21902
|
mask: createSecretMasker(this.secretValues)
|
|
21842
21903
|
};
|
|
21843
21904
|
}
|
|
21844
|
-
async proxyRequest(method, path6, body = {}, operation = "api-catalog request") {
|
|
21905
|
+
async proxyRequest(method, path6, body = {}, operation = "api-catalog request", allowAuthReplay = false) {
|
|
21845
21906
|
const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
|
|
21846
21907
|
method: "POST",
|
|
21847
21908
|
headers: this.headers(),
|
|
@@ -21855,7 +21916,7 @@ var BifrostCatalogClient = class {
|
|
|
21855
21916
|
let response = await send2();
|
|
21856
21917
|
if (!response.ok) {
|
|
21857
21918
|
const bodyText = await response.text().catch(() => "");
|
|
21858
|
-
if (isExpiredAuthError(response.status, bodyText) && this.tokenProvider.canRefresh()) {
|
|
21919
|
+
if (allowAuthReplay && isExpiredAuthError(response.status, bodyText) && this.tokenProvider.canRefresh()) {
|
|
21859
21920
|
try {
|
|
21860
21921
|
const refreshed = await this.tokenProvider.refresh();
|
|
21861
21922
|
this.registerAccessToken(refreshed);
|
|
@@ -21906,7 +21967,7 @@ var BifrostCatalogClient = class {
|
|
|
21906
21967
|
}
|
|
21907
21968
|
return data;
|
|
21908
21969
|
}
|
|
21909
|
-
async akitaProxyRequest(method, path6, body = {}, operation = "Insights request") {
|
|
21970
|
+
async akitaProxyRequest(method, path6, body = {}, operation = "Insights request", allowAuthReplay = false) {
|
|
21910
21971
|
const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
|
|
21911
21972
|
method: "POST",
|
|
21912
21973
|
headers: this.headers(),
|
|
@@ -21920,7 +21981,7 @@ var BifrostCatalogClient = class {
|
|
|
21920
21981
|
let response = await send2();
|
|
21921
21982
|
if (!response.ok) {
|
|
21922
21983
|
const text = await response.text().catch(() => "");
|
|
21923
|
-
if (isExpiredAuthError(response.status, text) && this.tokenProvider.canRefresh()) {
|
|
21984
|
+
if (allowAuthReplay && isExpiredAuthError(response.status, text) && this.tokenProvider.canRefresh()) {
|
|
21924
21985
|
try {
|
|
21925
21986
|
const refreshed = await this.tokenProvider.refresh();
|
|
21926
21987
|
this.registerAccessToken(refreshed);
|
|
@@ -21949,6 +22010,17 @@ ${advised.message}` : advised.message : text;
|
|
|
21949
22010
|
const data = await response.json();
|
|
21950
22011
|
return { ok: true, status: response.status, data, errorText: "" };
|
|
21951
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
|
+
}
|
|
21952
22024
|
async listDiscoveredServices() {
|
|
21953
22025
|
return retry(
|
|
21954
22026
|
async () => {
|
|
@@ -21976,26 +22048,80 @@ ${advised.message}` : advised.message : text;
|
|
|
21976
22048
|
}
|
|
21977
22049
|
return allItems;
|
|
21978
22050
|
},
|
|
21979
|
-
|
|
22051
|
+
SAFE_READ_RETRY
|
|
21980
22052
|
);
|
|
21981
22053
|
}
|
|
21982
|
-
|
|
22054
|
+
/** List discovered + integrated services for exact link reconciliation. */
|
|
22055
|
+
async listServicesForReconcile() {
|
|
21983
22056
|
return retry(
|
|
21984
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 () => {
|
|
21985
22107
|
const data = await this.proxyRequest(
|
|
21986
22108
|
"POST",
|
|
21987
22109
|
"/api/v1/onboarding/prepare-collection",
|
|
21988
22110
|
{ service_id: String(serviceId), workspace_id: workspaceId },
|
|
21989
|
-
"collection preparation"
|
|
22111
|
+
"collection preparation",
|
|
22112
|
+
false
|
|
21990
22113
|
);
|
|
22114
|
+
if (!data?.id) {
|
|
22115
|
+
throw new Error("prepare-collection succeeded without a collection id");
|
|
22116
|
+
}
|
|
21991
22117
|
return data.id;
|
|
21992
|
-
}
|
|
21993
|
-
|
|
21994
|
-
);
|
|
22118
|
+
}
|
|
22119
|
+
});
|
|
21995
22120
|
}
|
|
21996
22121
|
async onboardGit(params) {
|
|
21997
|
-
await
|
|
21998
|
-
|
|
22122
|
+
await mutateOnceThenReconcile({
|
|
22123
|
+
findExisting: () => this.findGitLink(params),
|
|
22124
|
+
mutate: async () => {
|
|
21999
22125
|
const body = {
|
|
22000
22126
|
via_integrations: false,
|
|
22001
22127
|
git_service_name: "github",
|
|
@@ -22011,24 +22137,32 @@ ${advised.message}` : advised.message : text;
|
|
|
22011
22137
|
"POST",
|
|
22012
22138
|
"/api/v1/onboarding/git",
|
|
22013
22139
|
body,
|
|
22014
|
-
"git onboarding"
|
|
22140
|
+
"git onboarding",
|
|
22141
|
+
false
|
|
22015
22142
|
);
|
|
22016
|
-
|
|
22017
|
-
|
|
22018
|
-
);
|
|
22143
|
+
return true;
|
|
22144
|
+
}
|
|
22145
|
+
});
|
|
22019
22146
|
}
|
|
22020
|
-
async
|
|
22147
|
+
async listProviderServices() {
|
|
22021
22148
|
const allServices = [];
|
|
22022
22149
|
let page = 1;
|
|
22023
22150
|
const pageSize = 100;
|
|
22024
22151
|
for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
|
|
22025
22152
|
const result = await this.akitaProxyRequest(
|
|
22026
22153
|
"GET",
|
|
22027
|
-
`/v2/api-catalog/services?
|
|
22154
|
+
`/v2/api-catalog/services?populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
|
|
22028
22155
|
{},
|
|
22029
22156
|
"provider service resolution"
|
|
22030
22157
|
);
|
|
22031
|
-
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
|
+
}
|
|
22032
22166
|
const services = result.data.services || [];
|
|
22033
22167
|
allServices.push(...services);
|
|
22034
22168
|
if (result.data.total && allServices.length >= result.data.total) {
|
|
@@ -22039,86 +22173,225 @@ ${advised.message}` : advised.message : text;
|
|
|
22039
22173
|
}
|
|
22040
22174
|
page++;
|
|
22041
22175
|
}
|
|
22176
|
+
return allServices;
|
|
22177
|
+
}
|
|
22178
|
+
async resolveProviderServiceId(projectName, clusterName) {
|
|
22179
|
+
const allServices = await retry(
|
|
22180
|
+
async () => this.listProviderServices(),
|
|
22181
|
+
SAFE_READ_RETRY
|
|
22182
|
+
);
|
|
22042
22183
|
if (clusterName) {
|
|
22043
22184
|
const fullName = `${clusterName}/${projectName}`;
|
|
22044
22185
|
const exactMatch = allServices.find((s) => s.name === fullName);
|
|
22045
22186
|
return exactMatch?.id || null;
|
|
22046
22187
|
}
|
|
22047
|
-
const
|
|
22188
|
+
const finalSegmentMatches = allServices.filter(
|
|
22048
22189
|
(s) => getFinalServiceSegment(s.name) === projectName
|
|
22049
22190
|
);
|
|
22050
|
-
if (
|
|
22051
|
-
|
|
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(
|
|
22052
22198
|
(s) => getFinalServiceSegment(s.name).includes(`[${projectName}]`)
|
|
22053
22199
|
);
|
|
22054
|
-
|
|
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;
|
|
22055
22213
|
}
|
|
22056
22214
|
async acknowledgeOnboarding(providerServiceId, workspaceId, systemEnvironmentId) {
|
|
22057
|
-
|
|
22058
|
-
|
|
22059
|
-
|
|
22060
|
-
|
|
22061
|
-
|
|
22062
|
-
|
|
22063
|
-
|
|
22064
|
-
|
|
22065
|
-
|
|
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;
|
|
22066
22261
|
},
|
|
22067
|
-
|
|
22262
|
+
SAFE_READ_RETRY
|
|
22068
22263
|
);
|
|
22069
|
-
|
|
22070
|
-
throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
|
|
22071
|
-
}
|
|
22264
|
+
return result.data?.onboarding_acknowledged ? true : null;
|
|
22072
22265
|
}
|
|
22073
22266
|
async acknowledgeWorkspace(workspaceId) {
|
|
22074
|
-
|
|
22075
|
-
|
|
22076
|
-
|
|
22077
|
-
|
|
22078
|
-
|
|
22079
|
-
|
|
22080
|
-
|
|
22081
|
-
|
|
22082
|
-
|
|
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
|
+
});
|
|
22083
22288
|
}
|
|
22084
|
-
|
|
22085
|
-
// application-binding endpoint (POST /v2/agent/api-catalog/workspaces/:id/
|
|
22086
|
-
// applications) showed x-access-token is rejected identically to the x-api-key
|
|
22087
|
-
// control: both return 401 {"message":"Postman User not found"} for a
|
|
22088
|
-
// service-account credential, because the observability service has no
|
|
22089
|
-
// "Postman User" for a service account. The access token offers no improvement
|
|
22090
|
-
// over the API key here, so this route is not migrated to access-token-primary
|
|
22091
|
-
// (the suite-wide migration explicitly leaves probe-failed routes on PMAK).
|
|
22092
|
-
async createApplication(workspaceId, systemEnv) {
|
|
22289
|
+
async findApplication(workspaceId, systemEnv, expectedServiceId) {
|
|
22093
22290
|
const response = await this.fetchFn(
|
|
22094
22291
|
`${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
|
|
22095
22292
|
{
|
|
22096
|
-
method: "
|
|
22293
|
+
method: "GET",
|
|
22097
22294
|
headers: {
|
|
22098
22295
|
"x-api-key": this.apiKey,
|
|
22099
22296
|
"x-postman-env": this.observabilityEnv,
|
|
22100
22297
|
"Content-Type": "application/json"
|
|
22101
|
-
}
|
|
22102
|
-
body: JSON.stringify({ system_env: systemEnv })
|
|
22298
|
+
}
|
|
22103
22299
|
}
|
|
22104
22300
|
);
|
|
22105
22301
|
if (!response.ok) {
|
|
22106
22302
|
const httpErr = await HttpError.fromResponse(response, {
|
|
22107
|
-
method: "
|
|
22108
|
-
url: `observability:
|
|
22303
|
+
method: "GET",
|
|
22304
|
+
url: `observability:listApplications(${workspaceId})`,
|
|
22109
22305
|
secretValues: this.secretValues
|
|
22110
22306
|
});
|
|
22111
|
-
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
|
|
22307
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding lookup"));
|
|
22112
22308
|
throw advised ?? httpErr;
|
|
22113
22309
|
}
|
|
22114
|
-
|
|
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
|
+
});
|
|
22115
22373
|
}
|
|
22116
22374
|
async getTeamVerificationToken(workspaceId) {
|
|
22117
|
-
const result = await
|
|
22118
|
-
|
|
22119
|
-
|
|
22120
|
-
|
|
22121
|
-
|
|
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
|
|
22122
22395
|
);
|
|
22123
22396
|
if (!result.ok || !result.data) return null;
|
|
22124
22397
|
return result.data.team_verification_token || null;
|
|
@@ -22151,24 +22424,87 @@ ${advised.message}` : advised.message : text;
|
|
|
22151
22424
|
return String(apikey.key);
|
|
22152
22425
|
}
|
|
22153
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
|
+
}
|
|
22154
22437
|
function findDiscoveredService(services, projectName, clusterName) {
|
|
22155
22438
|
if (clusterName) {
|
|
22156
22439
|
const fullName = `${clusterName}/${projectName}`;
|
|
22157
|
-
|
|
22440
|
+
const match = services.find((s) => s.name === fullName);
|
|
22441
|
+
if (match) {
|
|
22442
|
+
match.canonicalIdentity = buildCanonicalServiceIdentity(match, projectName, clusterName);
|
|
22443
|
+
}
|
|
22444
|
+
return match;
|
|
22158
22445
|
}
|
|
22159
|
-
const
|
|
22446
|
+
const finalSegmentMatches = services.filter(
|
|
22160
22447
|
(service) => getFinalServiceSegment(service.name) === projectName
|
|
22161
22448
|
);
|
|
22162
|
-
if (
|
|
22163
|
-
|
|
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(
|
|
22164
22460
|
(service) => getFinalServiceSegment(service.name).includes(`[${projectName}]`)
|
|
22165
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;
|
|
22166
22473
|
}
|
|
22167
22474
|
function getFinalServiceSegment(serviceName) {
|
|
22168
22475
|
const lastSlash = serviceName.lastIndexOf("/");
|
|
22169
22476
|
return lastSlash === -1 ? serviceName : serviceName.slice(lastSlash + 1);
|
|
22170
22477
|
}
|
|
22171
22478
|
|
|
22479
|
+
// src/lib/input.ts
|
|
22480
|
+
function normalizeInputValue(value) {
|
|
22481
|
+
return String(value ?? "").trim();
|
|
22482
|
+
}
|
|
22483
|
+
function runnerInputEnvName(name) {
|
|
22484
|
+
return `INPUT_${name.replace(/ /g, "_").toUpperCase()}`;
|
|
22485
|
+
}
|
|
22486
|
+
function normalizedInputEnvName(name) {
|
|
22487
|
+
return `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
|
|
22488
|
+
}
|
|
22489
|
+
function getInput2(name, env = process.env) {
|
|
22490
|
+
const normalizedName = normalizedInputEnvName(name);
|
|
22491
|
+
const runnerName = runnerInputEnvName(name);
|
|
22492
|
+
const normalizedRaw = env[normalizedName];
|
|
22493
|
+
const runnerRaw = runnerName === normalizedName ? void 0 : env[runnerName];
|
|
22494
|
+
const hasNormalized = normalizedRaw !== void 0;
|
|
22495
|
+
const hasRunner = runnerRaw !== void 0;
|
|
22496
|
+
if (hasNormalized && hasRunner) {
|
|
22497
|
+
const normalizedValue = normalizeInputValue(normalizedRaw);
|
|
22498
|
+
const runnerValue = normalizeInputValue(runnerRaw);
|
|
22499
|
+
if (normalizedValue !== runnerValue) {
|
|
22500
|
+
throw new Error(
|
|
22501
|
+
`Conflicting values for ${name}: ${normalizedName}=${JSON.stringify(normalizedValue)} vs ${runnerName}=${JSON.stringify(runnerValue)}`
|
|
22502
|
+
);
|
|
22503
|
+
}
|
|
22504
|
+
}
|
|
22505
|
+
return normalizeInputValue(hasNormalized ? normalizedRaw : runnerRaw);
|
|
22506
|
+
}
|
|
22507
|
+
|
|
22172
22508
|
// node_modules/@postman-cse/automation-telemetry-core/dist/ci-context.js
|
|
22173
22509
|
function norm(value) {
|
|
22174
22510
|
const trimmed = (value ?? "").trim();
|
|
@@ -22325,7 +22661,7 @@ function normalize(value) {
|
|
|
22325
22661
|
const trimmed = (value ?? "").trim();
|
|
22326
22662
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
22327
22663
|
}
|
|
22328
|
-
function
|
|
22664
|
+
function normalizeRepoUrl2(url) {
|
|
22329
22665
|
const raw = normalize(url);
|
|
22330
22666
|
if (!raw) {
|
|
22331
22667
|
return void 0;
|
|
@@ -22393,7 +22729,7 @@ function classifyRefKind(env = process.env) {
|
|
|
22393
22729
|
return "unknown";
|
|
22394
22730
|
}
|
|
22395
22731
|
function detectRepoContext(input, env = process.env) {
|
|
22396
|
-
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);
|
|
22397
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);
|
|
22398
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);
|
|
22399
22735
|
const sha = normalize(input.sha) ?? normalize(env.GITHUB_SHA) ?? normalize(env.CI_COMMIT_SHA) ?? normalize(env.BITBUCKET_COMMIT) ?? normalize(env.BUILD_SOURCEVERSION);
|
|
@@ -22570,7 +22906,7 @@ var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
|
|
|
22570
22906
|
var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
|
|
22571
22907
|
var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
|
|
22572
22908
|
function parsePreflightMode(value) {
|
|
22573
|
-
const normalized = String(value || "
|
|
22909
|
+
const normalized = String(value || "enforce").trim().toLowerCase();
|
|
22574
22910
|
if (normalized === "enforce" || normalized === "warn") {
|
|
22575
22911
|
return normalized;
|
|
22576
22912
|
}
|
|
@@ -22578,6 +22914,27 @@ function parsePreflightMode(value) {
|
|
|
22578
22914
|
`Unsupported credential-preflight "${value}". Supported values: enforce, warn`
|
|
22579
22915
|
);
|
|
22580
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
|
+
}
|
|
22581
22938
|
function trimTrailingSlash(value) {
|
|
22582
22939
|
return value.replace(/\/+$/, "");
|
|
22583
22940
|
}
|
|
@@ -22618,7 +22975,7 @@ function clamp(value, min, max, fallback) {
|
|
|
22618
22975
|
return Math.min(max, Math.max(min, parsed));
|
|
22619
22976
|
}
|
|
22620
22977
|
function resolveInputs(env = process.env) {
|
|
22621
|
-
const get = (name, fallback = "") =>
|
|
22978
|
+
const get = (name, fallback = "") => getInput2(name, env) || fallback;
|
|
22622
22979
|
const projectName = get("project-name");
|
|
22623
22980
|
if (!projectName) throw new Error("project-name is required");
|
|
22624
22981
|
const postmanAccessToken = get("postman-access-token");
|
|
@@ -22659,7 +23016,9 @@ function resolveInputs(env = process.env) {
|
|
|
22659
23016
|
postmanApiKey,
|
|
22660
23017
|
postmanTeamId,
|
|
22661
23018
|
githubToken: get("github-token", env.GITHUB_TOKEN || ""),
|
|
22662
|
-
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")),
|
|
22663
23022
|
pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
|
|
22664
23023
|
pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
|
|
22665
23024
|
postmanRegion,
|
|
@@ -22685,6 +23044,7 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22685
23044
|
const timeoutMs = inputs.pollTimeoutSeconds * 1e3;
|
|
22686
23045
|
const intervalMs = inputs.pollIntervalSeconds * 1e3;
|
|
22687
23046
|
const startTime = Date.now();
|
|
23047
|
+
const policy = inputs.serviceNotFoundPolicy ?? "fail";
|
|
22688
23048
|
reporter.info(`Looking for discovered service matching "${inputs.clusterName ? `${inputs.clusterName}/` : ""}${inputs.projectName}"...`);
|
|
22689
23049
|
let match = void 0;
|
|
22690
23050
|
while (Date.now() - startTime < timeoutMs) {
|
|
@@ -22699,15 +23059,35 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22699
23059
|
await sleepFn(intervalMs);
|
|
22700
23060
|
}
|
|
22701
23061
|
if (!match) {
|
|
22702
|
-
|
|
22703
|
-
|
|
22704
|
-
|
|
22705
|
-
|
|
22706
|
-
|
|
22707
|
-
|
|
22708
|
-
|
|
22709
|
-
|
|
22710
|
-
|
|
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
|
+
);
|
|
22711
23091
|
}
|
|
22712
23092
|
reporter.info(`Preparing collection for service ${match.id} in workspace ${inputs.workspaceId}...`);
|
|
22713
23093
|
const collectionId = await client.prepareCollection(match.id, inputs.workspaceId);
|
|
@@ -22727,27 +23107,12 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22727
23107
|
} else {
|
|
22728
23108
|
reporter.info(`Skipping git onboarding for non-GitHub repo: ${repoUrl}`);
|
|
22729
23109
|
}
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
);
|
|
22734
|
-
|
|
22735
|
-
|
|
22736
|
-
const sysEnvId = inputs.systemEnvironmentId || match.systemEnvironmentId || "";
|
|
22737
|
-
if (sysEnvId) {
|
|
22738
|
-
reporter.info(`Acknowledging Insights onboarding for ${providerServiceId}...`);
|
|
22739
|
-
await client.acknowledgeOnboarding(providerServiceId, inputs.workspaceId, sysEnvId);
|
|
22740
|
-
reporter.info(`Insights acknowledged: ${providerServiceId}`);
|
|
22741
|
-
reporter.info(`Creating application binding for workspace ${inputs.workspaceId} with system_env ${sysEnvId}...`);
|
|
22742
|
-
const appResult = await client.createApplication(inputs.workspaceId, sysEnvId);
|
|
22743
|
-
applicationId = appResult.application_id;
|
|
22744
|
-
reporter.info(`Application binding created: ${appResult.application_id} for service ${appResult.service_id}`);
|
|
22745
|
-
} else {
|
|
22746
|
-
reporter.warning("No systemEnvironmentId available; skipping Insights acknowledgment and application binding");
|
|
22747
|
-
}
|
|
22748
|
-
} else {
|
|
22749
|
-
reporter.warning("Could not resolve Akita provider service ID; skipping acknowledgment and application binding");
|
|
22750
|
-
}
|
|
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}`);
|
|
22751
23116
|
reporter.info(`Acknowledging workspace onboarding for ${inputs.workspaceId}...`);
|
|
22752
23117
|
await client.acknowledgeWorkspace(inputs.workspaceId);
|
|
22753
23118
|
reporter.info("Workspace onboarding acknowledged");
|
|
@@ -22763,9 +23128,13 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22763
23128
|
discoveredServiceId: match.id,
|
|
22764
23129
|
discoveredServiceName: match.name,
|
|
22765
23130
|
collectionId,
|
|
22766
|
-
applicationId,
|
|
23131
|
+
applicationId: appResult.application_id,
|
|
22767
23132
|
verificationToken,
|
|
22768
|
-
status: "success"
|
|
23133
|
+
status: "success",
|
|
23134
|
+
canonicalIdentity: {
|
|
23135
|
+
...canonicalBase,
|
|
23136
|
+
...providerServiceId ? { providerServiceId } : {}
|
|
23137
|
+
}
|
|
22769
23138
|
};
|
|
22770
23139
|
}
|
|
22771
23140
|
async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
@@ -22774,6 +23143,7 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
22774
23143
|
let keyValid = false;
|
|
22775
23144
|
let pmakIdentity;
|
|
22776
23145
|
const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
|
|
23146
|
+
const createApiKey = inputs.createApiKey === true;
|
|
22777
23147
|
if (apiKey) {
|
|
22778
23148
|
const result = await validateApiKey(apiKey, apiBase);
|
|
22779
23149
|
keyValid = result.valid;
|
|
@@ -22784,49 +23154,36 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
22784
23154
|
}
|
|
22785
23155
|
}
|
|
22786
23156
|
if (!keyValid) {
|
|
22787
|
-
|
|
22788
|
-
|
|
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}`;
|
|
22789
23169
|
apiKey = await client.createApiKey(keyName);
|
|
22790
23170
|
reporter.setSecret(apiKey);
|
|
22791
23171
|
client.setApiKey(apiKey);
|
|
22792
|
-
|
|
22793
|
-
|
|
22794
|
-
|
|
22795
|
-
if (!resolvedTeamId && apiKey) {
|
|
22796
|
-
try {
|
|
22797
|
-
const teams = await getTeams(apiKey, apiBase);
|
|
22798
|
-
if (teams.length > 1 && teams.every((t) => t.organizationId == null)) {
|
|
22799
|
-
reporter.warning(
|
|
22800
|
-
"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."
|
|
22801
|
-
);
|
|
22802
|
-
}
|
|
22803
|
-
const isOrgMode = teams.some((t) => t.organizationId != null);
|
|
22804
|
-
if (isOrgMode) {
|
|
22805
|
-
if (teams.length === 1) {
|
|
22806
|
-
resolvedTeamId = String(teams[0].id);
|
|
22807
|
-
reporter.info(
|
|
22808
|
-
`Org-mode account detected. Using sub-team ${teams[0].id} (${teams[0].name ?? "unknown"}) for Bifrost calls.`
|
|
22809
|
-
);
|
|
22810
|
-
} else {
|
|
22811
|
-
const meResult = await validateApiKey(apiKey, apiBase);
|
|
22812
|
-
const meTeamId = meResult.teamId ? parseInt(meResult.teamId, 10) : NaN;
|
|
22813
|
-
if (!Number.isNaN(meTeamId) && teams.some((t) => t.id === meTeamId)) {
|
|
22814
|
-
resolvedTeamId = String(meTeamId);
|
|
22815
|
-
reporter.info(
|
|
22816
|
-
`Org-mode account detected. Using sub-team ${meTeamId} (from /me) for Bifrost calls.`
|
|
22817
|
-
);
|
|
22818
|
-
}
|
|
22819
|
-
}
|
|
22820
|
-
}
|
|
22821
|
-
} 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.");
|
|
22822
23175
|
}
|
|
23176
|
+
pmakIdentity = { source: "pmak/me", teamId: createdIdentity.teamId };
|
|
23177
|
+
reporter.info(`New API key created successfully (${keyName}).`);
|
|
22823
23178
|
}
|
|
22824
|
-
if (
|
|
22825
|
-
reporter.info(`Using postman-team-id for Bifrost headers: ${
|
|
23179
|
+
if (teamId) {
|
|
23180
|
+
reporter.info(`Using explicit postman-team-id for Bifrost headers: ${teamId}`);
|
|
22826
23181
|
} else {
|
|
22827
|
-
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
|
+
);
|
|
22828
23185
|
}
|
|
22829
|
-
return { apiKey, teamId
|
|
23186
|
+
return { apiKey, teamId, pmakIdentity };
|
|
22830
23187
|
}
|
|
22831
23188
|
async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl, liveAccessToken) {
|
|
22832
23189
|
const accessToken = liveAccessToken ?? inputs.postmanAccessToken;
|
|
@@ -22884,24 +23241,54 @@ async function runAction() {
|
|
|
22884
23241
|
inputs.postmanTeamId,
|
|
22885
23242
|
inputs.postmanApiKey
|
|
22886
23243
|
);
|
|
22887
|
-
const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
|
|
22888
|
-
const activeTokenProvider = apiKey !== inputs.postmanApiKey ? new AccessTokenProvider({
|
|
22889
|
-
accessToken: tokenProvider.current(),
|
|
22890
|
-
apiKey,
|
|
22891
|
-
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
22892
|
-
onToken: (token) => setSecret(token)
|
|
22893
|
-
}) : tokenProvider;
|
|
22894
23244
|
const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", actionVersion: resolveActionVersion2(), logger: core_exports });
|
|
22895
|
-
telemetry.setTeamId(inputs.postmanTeamId
|
|
23245
|
+
telemetry.setTeamId(inputs.postmanTeamId);
|
|
22896
23246
|
let result;
|
|
22897
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
|
+
}
|
|
22898
23267
|
await runCredentialPreflightForInputs(
|
|
22899
23268
|
inputs,
|
|
22900
23269
|
pmakIdentity,
|
|
22901
23270
|
core_exports,
|
|
22902
23271
|
void 0,
|
|
22903
|
-
|
|
23272
|
+
tokenProvider.current()
|
|
22904
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;
|
|
22905
23292
|
const client = createInsightsBifrostClient(inputs, activeTokenProvider, teamId, apiKey);
|
|
22906
23293
|
result = await runOnboarding(inputs, client, sleep, core_exports);
|
|
22907
23294
|
} catch (error2) {
|
|
@@ -22936,8 +23323,11 @@ async function runAction() {
|
|
|
22936
23323
|
createInsightsBifrostClient,
|
|
22937
23324
|
createInsightsTokenProvider,
|
|
22938
23325
|
createPlannedOutputs,
|
|
23326
|
+
getInput,
|
|
22939
23327
|
getTeams,
|
|
23328
|
+
parseCreateApiKey,
|
|
22940
23329
|
parsePreflightMode,
|
|
23330
|
+
parseServiceNotFoundPolicy,
|
|
22941
23331
|
resolveApiKeyAndTeamId,
|
|
22942
23332
|
resolveInputs,
|
|
22943
23333
|
runAction,
|