@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/action.cjs
CHANGED
|
@@ -21540,6 +21540,42 @@ async function retry(operation, options = {}) {
|
|
|
21540
21540
|
}
|
|
21541
21541
|
throw new Error("Retry exhausted without returning or throwing");
|
|
21542
21542
|
}
|
|
21543
|
+
function isTransientHttpStatus(status) {
|
|
21544
|
+
return status === 408 || status === 429 || status >= 500;
|
|
21545
|
+
}
|
|
21546
|
+
function extractStatus(error2) {
|
|
21547
|
+
if (error2 instanceof HttpError) {
|
|
21548
|
+
return error2.status;
|
|
21549
|
+
}
|
|
21550
|
+
if (error2 && typeof error2 === "object" && "status" in error2) {
|
|
21551
|
+
const status = error2.status;
|
|
21552
|
+
return typeof status === "number" ? status : void 0;
|
|
21553
|
+
}
|
|
21554
|
+
if (error2 && typeof error2 === "object" && "cause" in error2) {
|
|
21555
|
+
return extractStatus(error2.cause);
|
|
21556
|
+
}
|
|
21557
|
+
return void 0;
|
|
21558
|
+
}
|
|
21559
|
+
function shouldRetryReadError(error2) {
|
|
21560
|
+
const status = extractStatus(error2);
|
|
21561
|
+
if (status === void 0) {
|
|
21562
|
+
return true;
|
|
21563
|
+
}
|
|
21564
|
+
return isTransientHttpStatus(status);
|
|
21565
|
+
}
|
|
21566
|
+
function isAmbiguousMutationFailure(error2) {
|
|
21567
|
+
const status = extractStatus(error2);
|
|
21568
|
+
if (status === void 0) {
|
|
21569
|
+
return true;
|
|
21570
|
+
}
|
|
21571
|
+
return isTransientHttpStatus(status);
|
|
21572
|
+
}
|
|
21573
|
+
var SAFE_READ_RETRY = {
|
|
21574
|
+
maxAttempts: 3,
|
|
21575
|
+
delayMs: 2e3,
|
|
21576
|
+
backoffMultiplier: 2,
|
|
21577
|
+
shouldRetry: (error2) => shouldRetryReadError(error2)
|
|
21578
|
+
};
|
|
21543
21579
|
|
|
21544
21580
|
// src/lib/postman/base-urls.ts
|
|
21545
21581
|
var POSTMAN_ENDPOINT_PROFILES = {
|
|
@@ -21754,6 +21790,27 @@ var MAX_PROVIDER_SERVICE_PAGES = 100;
|
|
|
21754
21790
|
function isExpiredAuthError(status, body) {
|
|
21755
21791
|
return status === 401 || body.includes("UNAUTHENTICATED") || body.includes("authenticationError");
|
|
21756
21792
|
}
|
|
21793
|
+
function normalizeRepoUrl(url) {
|
|
21794
|
+
return url.trim().replace(/\/+$/, "").toLowerCase();
|
|
21795
|
+
}
|
|
21796
|
+
async function mutateOnceThenReconcile(options) {
|
|
21797
|
+
const existing = await options.findExisting();
|
|
21798
|
+
if (existing !== null) {
|
|
21799
|
+
return existing;
|
|
21800
|
+
}
|
|
21801
|
+
try {
|
|
21802
|
+
return await options.mutate();
|
|
21803
|
+
} catch (error2) {
|
|
21804
|
+
if (!isAmbiguousMutationFailure(error2)) {
|
|
21805
|
+
throw error2;
|
|
21806
|
+
}
|
|
21807
|
+
const adopted = await options.findExisting();
|
|
21808
|
+
if (adopted !== null) {
|
|
21809
|
+
return adopted;
|
|
21810
|
+
}
|
|
21811
|
+
throw error2;
|
|
21812
|
+
}
|
|
21813
|
+
}
|
|
21757
21814
|
var BifrostCatalogClient = class {
|
|
21758
21815
|
tokenProvider;
|
|
21759
21816
|
teamId;
|
|
@@ -21790,8 +21847,9 @@ var BifrostCatalogClient = class {
|
|
|
21790
21847
|
}
|
|
21791
21848
|
/**
|
|
21792
21849
|
* Build Bifrost proxy headers.
|
|
21793
|
-
* x-entity-team-id is ONLY included when teamId is present (
|
|
21794
|
-
* Non-org-mode tokens must OMIT it so Bifrost resolves team
|
|
21850
|
+
* x-entity-team-id is ONLY included when teamId is present (explicit input /
|
|
21851
|
+
* POSTMAN_TEAM_ID). Non-org-mode tokens must OMIT it so Bifrost resolves team
|
|
21852
|
+
* from the access token. Team is never inferred from PMAK.
|
|
21795
21853
|
*/
|
|
21796
21854
|
headers() {
|
|
21797
21855
|
const h = {
|
|
@@ -21819,7 +21877,7 @@ var BifrostCatalogClient = class {
|
|
|
21819
21877
|
mask: createSecretMasker(this.secretValues)
|
|
21820
21878
|
};
|
|
21821
21879
|
}
|
|
21822
|
-
async proxyRequest(method, path6, body = {}, operation = "api-catalog request") {
|
|
21880
|
+
async proxyRequest(method, path6, body = {}, operation = "api-catalog request", allowAuthReplay = false) {
|
|
21823
21881
|
const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
|
|
21824
21882
|
method: "POST",
|
|
21825
21883
|
headers: this.headers(),
|
|
@@ -21833,7 +21891,7 @@ var BifrostCatalogClient = class {
|
|
|
21833
21891
|
let response = await send2();
|
|
21834
21892
|
if (!response.ok) {
|
|
21835
21893
|
const bodyText = await response.text().catch(() => "");
|
|
21836
|
-
if (isExpiredAuthError(response.status, bodyText) && this.tokenProvider.canRefresh()) {
|
|
21894
|
+
if (allowAuthReplay && isExpiredAuthError(response.status, bodyText) && this.tokenProvider.canRefresh()) {
|
|
21837
21895
|
try {
|
|
21838
21896
|
const refreshed = await this.tokenProvider.refresh();
|
|
21839
21897
|
this.registerAccessToken(refreshed);
|
|
@@ -21884,7 +21942,7 @@ var BifrostCatalogClient = class {
|
|
|
21884
21942
|
}
|
|
21885
21943
|
return data;
|
|
21886
21944
|
}
|
|
21887
|
-
async akitaProxyRequest(method, path6, body = {}, operation = "Insights request") {
|
|
21945
|
+
async akitaProxyRequest(method, path6, body = {}, operation = "Insights request", allowAuthReplay = false) {
|
|
21888
21946
|
const send2 = async () => this.fetchFn(this.bifrostProxyUrl, {
|
|
21889
21947
|
method: "POST",
|
|
21890
21948
|
headers: this.headers(),
|
|
@@ -21898,7 +21956,7 @@ var BifrostCatalogClient = class {
|
|
|
21898
21956
|
let response = await send2();
|
|
21899
21957
|
if (!response.ok) {
|
|
21900
21958
|
const text = await response.text().catch(() => "");
|
|
21901
|
-
if (isExpiredAuthError(response.status, text) && this.tokenProvider.canRefresh()) {
|
|
21959
|
+
if (allowAuthReplay && isExpiredAuthError(response.status, text) && this.tokenProvider.canRefresh()) {
|
|
21902
21960
|
try {
|
|
21903
21961
|
const refreshed = await this.tokenProvider.refresh();
|
|
21904
21962
|
this.registerAccessToken(refreshed);
|
|
@@ -21927,6 +21985,17 @@ ${advised.message}` : advised.message : text;
|
|
|
21927
21985
|
const data = await response.json();
|
|
21928
21986
|
return { ok: true, status: response.status, data, errorText: "" };
|
|
21929
21987
|
}
|
|
21988
|
+
throwAkitaFailure(status, errorText, operation, path6) {
|
|
21989
|
+
const httpErr = new HttpError({
|
|
21990
|
+
method: "POST",
|
|
21991
|
+
url: `bifrost:akita:${path6}`,
|
|
21992
|
+
status,
|
|
21993
|
+
statusText: status >= 500 ? "Error" : "Client Error",
|
|
21994
|
+
responseBody: errorText
|
|
21995
|
+
});
|
|
21996
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext(operation));
|
|
21997
|
+
throw advised ?? httpErr;
|
|
21998
|
+
}
|
|
21930
21999
|
async listDiscoveredServices() {
|
|
21931
22000
|
return retry(
|
|
21932
22001
|
async () => {
|
|
@@ -21954,26 +22023,80 @@ ${advised.message}` : advised.message : text;
|
|
|
21954
22023
|
}
|
|
21955
22024
|
return allItems;
|
|
21956
22025
|
},
|
|
21957
|
-
|
|
22026
|
+
SAFE_READ_RETRY
|
|
21958
22027
|
);
|
|
21959
22028
|
}
|
|
21960
|
-
|
|
22029
|
+
/** List discovered + integrated services for exact link reconciliation. */
|
|
22030
|
+
async listServicesForReconcile() {
|
|
21961
22031
|
return retry(
|
|
21962
22032
|
async () => {
|
|
22033
|
+
const allItems = [];
|
|
22034
|
+
let cursor = null;
|
|
22035
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
22036
|
+
for (let page = 0; page < MAX_DISCOVERED_SERVICE_PAGES; page += 1) {
|
|
22037
|
+
const query = cursor ? `?cursor=${encodeURIComponent(cursor)}` : "";
|
|
22038
|
+
const data = await this.proxyRequest(
|
|
22039
|
+
"GET",
|
|
22040
|
+
`/api/v1/onboarding/discovered-services${query}`,
|
|
22041
|
+
{},
|
|
22042
|
+
"service link reconciliation"
|
|
22043
|
+
);
|
|
22044
|
+
allItems.push(...data.items || []);
|
|
22045
|
+
if (data.total && allItems.length >= data.total) {
|
|
22046
|
+
break;
|
|
22047
|
+
}
|
|
22048
|
+
const nextCursor = data.nextCursor || null;
|
|
22049
|
+
if (!nextCursor || seenCursors.has(nextCursor)) {
|
|
22050
|
+
break;
|
|
22051
|
+
}
|
|
22052
|
+
seenCursors.add(nextCursor);
|
|
22053
|
+
cursor = nextCursor;
|
|
22054
|
+
}
|
|
22055
|
+
return allItems;
|
|
22056
|
+
},
|
|
22057
|
+
SAFE_READ_RETRY
|
|
22058
|
+
);
|
|
22059
|
+
}
|
|
22060
|
+
async findPreparedCollection(serviceId, workspaceId) {
|
|
22061
|
+
const services = await this.listServicesForReconcile();
|
|
22062
|
+
const match = services.find(
|
|
22063
|
+
(service) => service.id === serviceId && Boolean(service.collectionId) && service.workspaceId === workspaceId
|
|
22064
|
+
);
|
|
22065
|
+
return match?.collectionId ? String(match.collectionId) : null;
|
|
22066
|
+
}
|
|
22067
|
+
async findGitLink(params) {
|
|
22068
|
+
const services = await this.listServicesForReconcile();
|
|
22069
|
+
const match = services.find((service) => {
|
|
22070
|
+
if (service.id !== params.serviceId) return false;
|
|
22071
|
+
if (service.workspaceId !== params.workspaceId) return false;
|
|
22072
|
+
if (service.environmentId !== params.environmentId) return false;
|
|
22073
|
+
if (!service.gitRepositoryUrl) return false;
|
|
22074
|
+
return normalizeRepoUrl(service.gitRepositoryUrl) === normalizeRepoUrl(params.gitRepositoryUrl);
|
|
22075
|
+
});
|
|
22076
|
+
return match ? true : null;
|
|
22077
|
+
}
|
|
22078
|
+
async prepareCollection(serviceId, workspaceId) {
|
|
22079
|
+
return mutateOnceThenReconcile({
|
|
22080
|
+
findExisting: () => this.findPreparedCollection(serviceId, workspaceId),
|
|
22081
|
+
mutate: async () => {
|
|
21963
22082
|
const data = await this.proxyRequest(
|
|
21964
22083
|
"POST",
|
|
21965
22084
|
"/api/v1/onboarding/prepare-collection",
|
|
21966
22085
|
{ service_id: String(serviceId), workspace_id: workspaceId },
|
|
21967
|
-
"collection preparation"
|
|
22086
|
+
"collection preparation",
|
|
22087
|
+
false
|
|
21968
22088
|
);
|
|
22089
|
+
if (!data?.id) {
|
|
22090
|
+
throw new Error("prepare-collection succeeded without a collection id");
|
|
22091
|
+
}
|
|
21969
22092
|
return data.id;
|
|
21970
|
-
}
|
|
21971
|
-
|
|
21972
|
-
);
|
|
22093
|
+
}
|
|
22094
|
+
});
|
|
21973
22095
|
}
|
|
21974
22096
|
async onboardGit(params) {
|
|
21975
|
-
await
|
|
21976
|
-
|
|
22097
|
+
await mutateOnceThenReconcile({
|
|
22098
|
+
findExisting: () => this.findGitLink(params),
|
|
22099
|
+
mutate: async () => {
|
|
21977
22100
|
const body = {
|
|
21978
22101
|
via_integrations: false,
|
|
21979
22102
|
git_service_name: "github",
|
|
@@ -21989,24 +22112,32 @@ ${advised.message}` : advised.message : text;
|
|
|
21989
22112
|
"POST",
|
|
21990
22113
|
"/api/v1/onboarding/git",
|
|
21991
22114
|
body,
|
|
21992
|
-
"git onboarding"
|
|
22115
|
+
"git onboarding",
|
|
22116
|
+
false
|
|
21993
22117
|
);
|
|
21994
|
-
|
|
21995
|
-
|
|
21996
|
-
);
|
|
22118
|
+
return true;
|
|
22119
|
+
}
|
|
22120
|
+
});
|
|
21997
22121
|
}
|
|
21998
|
-
async
|
|
22122
|
+
async listProviderServices() {
|
|
21999
22123
|
const allServices = [];
|
|
22000
22124
|
let page = 1;
|
|
22001
22125
|
const pageSize = 100;
|
|
22002
22126
|
for (let pageCount = 0; pageCount < MAX_PROVIDER_SERVICE_PAGES; pageCount += 1) {
|
|
22003
22127
|
const result = await this.akitaProxyRequest(
|
|
22004
22128
|
"GET",
|
|
22005
|
-
`/v2/api-catalog/services?
|
|
22129
|
+
`/v2/api-catalog/services?populate_endpoints=false&populate_discovery_metadata=true&page=${page}&page_size=${pageSize}`,
|
|
22006
22130
|
{},
|
|
22007
22131
|
"provider service resolution"
|
|
22008
22132
|
);
|
|
22009
|
-
if (!result.ok || !result.data)
|
|
22133
|
+
if (!result.ok || !result.data) {
|
|
22134
|
+
this.throwAkitaFailure(
|
|
22135
|
+
result.status,
|
|
22136
|
+
result.errorText,
|
|
22137
|
+
"provider service resolution",
|
|
22138
|
+
"GET /v2/api-catalog/services"
|
|
22139
|
+
);
|
|
22140
|
+
}
|
|
22010
22141
|
const services = result.data.services || [];
|
|
22011
22142
|
allServices.push(...services);
|
|
22012
22143
|
if (result.data.total && allServices.length >= result.data.total) {
|
|
@@ -22017,86 +22148,225 @@ ${advised.message}` : advised.message : text;
|
|
|
22017
22148
|
}
|
|
22018
22149
|
page++;
|
|
22019
22150
|
}
|
|
22151
|
+
return allServices;
|
|
22152
|
+
}
|
|
22153
|
+
async resolveProviderServiceId(projectName, clusterName) {
|
|
22154
|
+
const allServices = await retry(
|
|
22155
|
+
async () => this.listProviderServices(),
|
|
22156
|
+
SAFE_READ_RETRY
|
|
22157
|
+
);
|
|
22020
22158
|
if (clusterName) {
|
|
22021
22159
|
const fullName = `${clusterName}/${projectName}`;
|
|
22022
22160
|
const exactMatch = allServices.find((s) => s.name === fullName);
|
|
22023
22161
|
return exactMatch?.id || null;
|
|
22024
22162
|
}
|
|
22025
|
-
const
|
|
22163
|
+
const finalSegmentMatches = allServices.filter(
|
|
22026
22164
|
(s) => getFinalServiceSegment(s.name) === projectName
|
|
22027
22165
|
);
|
|
22028
|
-
if (
|
|
22029
|
-
|
|
22166
|
+
if (finalSegmentMatches.length > 1) {
|
|
22167
|
+
throw new Error(
|
|
22168
|
+
`Ambiguous Insights provider service "${projectName}": multiple final-segment matches (${finalSegmentMatches.map((s) => s.name).join(", ")}). Provide cluster-name to select the canonical service identity.`
|
|
22169
|
+
);
|
|
22170
|
+
}
|
|
22171
|
+
if (finalSegmentMatches.length === 1) return finalSegmentMatches[0].id;
|
|
22172
|
+
const bracketedMatches = allServices.filter(
|
|
22030
22173
|
(s) => getFinalServiceSegment(s.name).includes(`[${projectName}]`)
|
|
22031
22174
|
);
|
|
22032
|
-
|
|
22175
|
+
if (bracketedMatches.length > 1) {
|
|
22176
|
+
throw new Error(
|
|
22177
|
+
`Ambiguous Insights provider service "${projectName}": multiple bracketed matches. Provide cluster-name to select the canonical service identity.`
|
|
22178
|
+
);
|
|
22179
|
+
}
|
|
22180
|
+
return bracketedMatches[0]?.id || null;
|
|
22181
|
+
}
|
|
22182
|
+
async findAcknowledgedOnboarding(providerServiceId, workspaceId, systemEnvironmentId) {
|
|
22183
|
+
const services = await this.listProviderServices();
|
|
22184
|
+
const match = services.find(
|
|
22185
|
+
(service) => service.id === providerServiceId && service.workspace_id === workspaceId && service.system_env === systemEnvironmentId && (service.status === "onboarded" || service.status === "integrated" || Boolean(service.workspace_id))
|
|
22186
|
+
);
|
|
22187
|
+
return match ? true : null;
|
|
22033
22188
|
}
|
|
22034
22189
|
async acknowledgeOnboarding(providerServiceId, workspaceId, systemEnvironmentId) {
|
|
22035
|
-
|
|
22036
|
-
|
|
22037
|
-
|
|
22038
|
-
|
|
22039
|
-
|
|
22040
|
-
|
|
22041
|
-
|
|
22042
|
-
|
|
22043
|
-
|
|
22190
|
+
await mutateOnceThenReconcile({
|
|
22191
|
+
findExisting: () => this.findAcknowledgedOnboarding(providerServiceId, workspaceId, systemEnvironmentId),
|
|
22192
|
+
mutate: async () => {
|
|
22193
|
+
const result = await this.akitaProxyRequest(
|
|
22194
|
+
"POST",
|
|
22195
|
+
"/v2/api-catalog/services/onboard",
|
|
22196
|
+
{
|
|
22197
|
+
services: [{
|
|
22198
|
+
service_id: providerServiceId,
|
|
22199
|
+
workspace_id: workspaceId,
|
|
22200
|
+
system_env: systemEnvironmentId
|
|
22201
|
+
}]
|
|
22202
|
+
},
|
|
22203
|
+
"Insights onboarding acknowledgment",
|
|
22204
|
+
false
|
|
22205
|
+
);
|
|
22206
|
+
if (!result.ok) {
|
|
22207
|
+
this.throwAkitaFailure(
|
|
22208
|
+
result.status,
|
|
22209
|
+
result.errorText,
|
|
22210
|
+
"Insights onboarding acknowledgment",
|
|
22211
|
+
"POST /v2/api-catalog/services/onboard"
|
|
22212
|
+
);
|
|
22213
|
+
}
|
|
22214
|
+
return true;
|
|
22215
|
+
}
|
|
22216
|
+
});
|
|
22217
|
+
}
|
|
22218
|
+
async findWorkspaceAcknowledged(workspaceId) {
|
|
22219
|
+
const result = await retry(
|
|
22220
|
+
async () => {
|
|
22221
|
+
const response = await this.akitaProxyRequest(
|
|
22222
|
+
"GET",
|
|
22223
|
+
`/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
|
|
22224
|
+
{},
|
|
22225
|
+
"workspace onboarding acknowledgment status"
|
|
22226
|
+
);
|
|
22227
|
+
if (!response.ok) {
|
|
22228
|
+
this.throwAkitaFailure(
|
|
22229
|
+
response.status,
|
|
22230
|
+
response.errorText,
|
|
22231
|
+
"workspace onboarding acknowledgment status",
|
|
22232
|
+
`GET /v2/workspaces/${workspaceId}/onboarding/acknowledge`
|
|
22233
|
+
);
|
|
22234
|
+
}
|
|
22235
|
+
return response;
|
|
22044
22236
|
},
|
|
22045
|
-
|
|
22237
|
+
SAFE_READ_RETRY
|
|
22046
22238
|
);
|
|
22047
|
-
|
|
22048
|
-
throw new Error(`Insights acknowledge failed: ${result.status} ${result.errorText}`);
|
|
22049
|
-
}
|
|
22239
|
+
return result.data?.onboarding_acknowledged ? true : null;
|
|
22050
22240
|
}
|
|
22051
22241
|
async acknowledgeWorkspace(workspaceId) {
|
|
22052
|
-
|
|
22053
|
-
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
|
|
22057
|
-
|
|
22058
|
-
|
|
22059
|
-
|
|
22060
|
-
|
|
22242
|
+
await mutateOnceThenReconcile({
|
|
22243
|
+
findExisting: () => this.findWorkspaceAcknowledged(workspaceId),
|
|
22244
|
+
mutate: async () => {
|
|
22245
|
+
const result = await this.akitaProxyRequest(
|
|
22246
|
+
"POST",
|
|
22247
|
+
`/v2/workspaces/${workspaceId}/onboarding/acknowledge`,
|
|
22248
|
+
{},
|
|
22249
|
+
"workspace onboarding acknowledgment",
|
|
22250
|
+
false
|
|
22251
|
+
);
|
|
22252
|
+
if (!result.ok) {
|
|
22253
|
+
this.throwAkitaFailure(
|
|
22254
|
+
result.status,
|
|
22255
|
+
result.errorText,
|
|
22256
|
+
"workspace onboarding acknowledgment",
|
|
22257
|
+
`POST /v2/workspaces/${workspaceId}/onboarding/acknowledge`
|
|
22258
|
+
);
|
|
22259
|
+
}
|
|
22260
|
+
return true;
|
|
22261
|
+
}
|
|
22262
|
+
});
|
|
22061
22263
|
}
|
|
22062
|
-
|
|
22063
|
-
// application-binding endpoint (POST /v2/agent/api-catalog/workspaces/:id/
|
|
22064
|
-
// applications) showed x-access-token is rejected identically to the x-api-key
|
|
22065
|
-
// control: both return 401 {"message":"Postman User not found"} for a
|
|
22066
|
-
// service-account credential, because the observability service has no
|
|
22067
|
-
// "Postman User" for a service account. The access token offers no improvement
|
|
22068
|
-
// over the API key here, so this route is not migrated to access-token-primary
|
|
22069
|
-
// (the suite-wide migration explicitly leaves probe-failed routes on PMAK).
|
|
22070
|
-
async createApplication(workspaceId, systemEnv) {
|
|
22264
|
+
async findApplication(workspaceId, systemEnv, expectedServiceId) {
|
|
22071
22265
|
const response = await this.fetchFn(
|
|
22072
22266
|
`${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
|
|
22073
22267
|
{
|
|
22074
|
-
method: "
|
|
22268
|
+
method: "GET",
|
|
22075
22269
|
headers: {
|
|
22076
22270
|
"x-api-key": this.apiKey,
|
|
22077
22271
|
"x-postman-env": this.observabilityEnv,
|
|
22078
22272
|
"Content-Type": "application/json"
|
|
22079
|
-
}
|
|
22080
|
-
body: JSON.stringify({ system_env: systemEnv })
|
|
22273
|
+
}
|
|
22081
22274
|
}
|
|
22082
22275
|
);
|
|
22083
22276
|
if (!response.ok) {
|
|
22084
22277
|
const httpErr = await HttpError.fromResponse(response, {
|
|
22085
|
-
method: "
|
|
22086
|
-
url: `observability:
|
|
22278
|
+
method: "GET",
|
|
22279
|
+
url: `observability:listApplications(${workspaceId})`,
|
|
22087
22280
|
secretValues: this.secretValues
|
|
22088
22281
|
});
|
|
22089
|
-
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
|
|
22282
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding lookup"));
|
|
22090
22283
|
throw advised ?? httpErr;
|
|
22091
22284
|
}
|
|
22092
|
-
|
|
22285
|
+
const data = await response.json();
|
|
22286
|
+
const matches = (data.applications || []).filter(
|
|
22287
|
+
(app) => app.application_id && app.service_id && app.system_env === systemEnv && (!expectedServiceId || app.service_id === expectedServiceId)
|
|
22288
|
+
);
|
|
22289
|
+
if (matches.length > 1) {
|
|
22290
|
+
throw new Error(
|
|
22291
|
+
`Multiple application bindings match workspace ${workspaceId}, system environment ${systemEnv}, and service ${expectedServiceId || "(unspecified)"}`
|
|
22292
|
+
);
|
|
22293
|
+
}
|
|
22294
|
+
const match = matches[0];
|
|
22295
|
+
if (!match?.application_id || !match.service_id) {
|
|
22296
|
+
return null;
|
|
22297
|
+
}
|
|
22298
|
+
return {
|
|
22299
|
+
application_id: String(match.application_id),
|
|
22300
|
+
service_id: String(match.service_id)
|
|
22301
|
+
};
|
|
22302
|
+
}
|
|
22303
|
+
// PMAK-only by proven exception. A live probe against the observability
|
|
22304
|
+
// application-binding endpoint (POST /v2/agent/api-catalog/workspaces/:id/
|
|
22305
|
+
// applications) showed x-access-token is rejected identically to the x-api-key
|
|
22306
|
+
// control: both return 401 {"message":"Postman User not found"} for a
|
|
22307
|
+
// service-account credential, because the observability service has no
|
|
22308
|
+
// "Postman User" for a service account. The access token offers no improvement
|
|
22309
|
+
// over the API key here, so this route is not migrated to access-token-primary
|
|
22310
|
+
// (the suite-wide migration explicitly leaves probe-failed routes on PMAK).
|
|
22311
|
+
async createApplication(workspaceId, systemEnv, expectedServiceId) {
|
|
22312
|
+
return mutateOnceThenReconcile({
|
|
22313
|
+
findExisting: () => retry(
|
|
22314
|
+
() => this.findApplication(workspaceId, systemEnv, expectedServiceId),
|
|
22315
|
+
SAFE_READ_RETRY
|
|
22316
|
+
),
|
|
22317
|
+
mutate: async () => {
|
|
22318
|
+
const response = await this.fetchFn(
|
|
22319
|
+
`${this.observabilityBaseUrl}/v2/agent/api-catalog/workspaces/${workspaceId}/applications`,
|
|
22320
|
+
{
|
|
22321
|
+
method: "POST",
|
|
22322
|
+
headers: {
|
|
22323
|
+
"x-api-key": this.apiKey,
|
|
22324
|
+
"x-postman-env": this.observabilityEnv,
|
|
22325
|
+
"Content-Type": "application/json"
|
|
22326
|
+
},
|
|
22327
|
+
body: JSON.stringify({ system_env: systemEnv })
|
|
22328
|
+
}
|
|
22329
|
+
);
|
|
22330
|
+
if (!response.ok) {
|
|
22331
|
+
const httpErr = await HttpError.fromResponse(response, {
|
|
22332
|
+
method: "POST",
|
|
22333
|
+
url: `observability:createApplication(${workspaceId})`,
|
|
22334
|
+
secretValues: this.secretValues
|
|
22335
|
+
});
|
|
22336
|
+
const advised = adviseFromHttpError(httpErr, this.adviceContext("application binding"));
|
|
22337
|
+
throw advised ?? httpErr;
|
|
22338
|
+
}
|
|
22339
|
+
const created = await response.json();
|
|
22340
|
+
if (expectedServiceId && created.service_id !== expectedServiceId) {
|
|
22341
|
+
throw new Error(
|
|
22342
|
+
`Application binding credential/scope mismatch: expected service ${expectedServiceId}, received ${created.service_id}`
|
|
22343
|
+
);
|
|
22344
|
+
}
|
|
22345
|
+
return created;
|
|
22346
|
+
}
|
|
22347
|
+
});
|
|
22093
22348
|
}
|
|
22094
22349
|
async getTeamVerificationToken(workspaceId) {
|
|
22095
|
-
const result = await
|
|
22096
|
-
|
|
22097
|
-
|
|
22098
|
-
|
|
22099
|
-
|
|
22350
|
+
const result = await retry(
|
|
22351
|
+
async () => {
|
|
22352
|
+
const page = await this.akitaProxyRequest(
|
|
22353
|
+
"GET",
|
|
22354
|
+
`/v2/workspaces/${workspaceId}/team-verification-token`,
|
|
22355
|
+
{},
|
|
22356
|
+
"team verification token retrieval"
|
|
22357
|
+
);
|
|
22358
|
+
if (!page.ok && (page.status === 408 || page.status === 429 || page.status >= 500)) {
|
|
22359
|
+
throw new HttpError({
|
|
22360
|
+
method: "GET",
|
|
22361
|
+
url: `bifrost:akita:GET /v2/workspaces/${workspaceId}/team-verification-token`,
|
|
22362
|
+
status: page.status,
|
|
22363
|
+
statusText: "Error",
|
|
22364
|
+
responseBody: page.errorText
|
|
22365
|
+
});
|
|
22366
|
+
}
|
|
22367
|
+
return page;
|
|
22368
|
+
},
|
|
22369
|
+
SAFE_READ_RETRY
|
|
22100
22370
|
);
|
|
22101
22371
|
if (!result.ok || !result.data) return null;
|
|
22102
22372
|
return result.data.team_verification_token || null;
|
|
@@ -22129,24 +22399,87 @@ ${advised.message}` : advised.message : text;
|
|
|
22129
22399
|
return String(apikey.key);
|
|
22130
22400
|
}
|
|
22131
22401
|
};
|
|
22402
|
+
function buildCanonicalServiceIdentity(service, projectName, clusterName, providerServiceId) {
|
|
22403
|
+
const derivedCluster = clusterName || (service.name.includes("/") ? service.name.slice(0, service.name.lastIndexOf("/")) : null);
|
|
22404
|
+
return {
|
|
22405
|
+
serviceId: service.id,
|
|
22406
|
+
serviceName: service.name,
|
|
22407
|
+
clusterName: derivedCluster,
|
|
22408
|
+
projectName,
|
|
22409
|
+
...providerServiceId ? { providerServiceId } : {}
|
|
22410
|
+
};
|
|
22411
|
+
}
|
|
22132
22412
|
function findDiscoveredService(services, projectName, clusterName) {
|
|
22133
22413
|
if (clusterName) {
|
|
22134
22414
|
const fullName = `${clusterName}/${projectName}`;
|
|
22135
|
-
|
|
22415
|
+
const match = services.find((s) => s.name === fullName);
|
|
22416
|
+
if (match) {
|
|
22417
|
+
match.canonicalIdentity = buildCanonicalServiceIdentity(match, projectName, clusterName);
|
|
22418
|
+
}
|
|
22419
|
+
return match;
|
|
22136
22420
|
}
|
|
22137
|
-
const
|
|
22421
|
+
const finalSegmentMatches = services.filter(
|
|
22138
22422
|
(service) => getFinalServiceSegment(service.name) === projectName
|
|
22139
22423
|
);
|
|
22140
|
-
if (
|
|
22141
|
-
|
|
22424
|
+
if (finalSegmentMatches.length > 1) {
|
|
22425
|
+
throw new Error(
|
|
22426
|
+
`Ambiguous discovered service "${projectName}": multiple final-segment matches (${finalSegmentMatches.map((s) => s.name).join(", ")}). cluster-name is required to select the canonical service identity.`
|
|
22427
|
+
);
|
|
22428
|
+
}
|
|
22429
|
+
if (finalSegmentMatches.length === 1) {
|
|
22430
|
+
const match = finalSegmentMatches[0];
|
|
22431
|
+
match.canonicalIdentity = buildCanonicalServiceIdentity(match, projectName);
|
|
22432
|
+
return match;
|
|
22433
|
+
}
|
|
22434
|
+
const bracketedMatches = services.filter(
|
|
22142
22435
|
(service) => getFinalServiceSegment(service.name).includes(`[${projectName}]`)
|
|
22143
22436
|
);
|
|
22437
|
+
if (bracketedMatches.length > 1) {
|
|
22438
|
+
throw new Error(
|
|
22439
|
+
`Ambiguous discovered service "${projectName}": multiple bracketed matches. cluster-name is required to select the canonical service identity.`
|
|
22440
|
+
);
|
|
22441
|
+
}
|
|
22442
|
+
if (bracketedMatches.length === 1) {
|
|
22443
|
+
const match = bracketedMatches[0];
|
|
22444
|
+
match.canonicalIdentity = buildCanonicalServiceIdentity(match, projectName);
|
|
22445
|
+
return match;
|
|
22446
|
+
}
|
|
22447
|
+
return void 0;
|
|
22144
22448
|
}
|
|
22145
22449
|
function getFinalServiceSegment(serviceName) {
|
|
22146
22450
|
const lastSlash = serviceName.lastIndexOf("/");
|
|
22147
22451
|
return lastSlash === -1 ? serviceName : serviceName.slice(lastSlash + 1);
|
|
22148
22452
|
}
|
|
22149
22453
|
|
|
22454
|
+
// src/lib/input.ts
|
|
22455
|
+
function normalizeInputValue(value) {
|
|
22456
|
+
return String(value ?? "").trim();
|
|
22457
|
+
}
|
|
22458
|
+
function runnerInputEnvName(name) {
|
|
22459
|
+
return `INPUT_${name.replace(/ /g, "_").toUpperCase()}`;
|
|
22460
|
+
}
|
|
22461
|
+
function normalizedInputEnvName(name) {
|
|
22462
|
+
return `INPUT_${name.replace(/-/g, "_").toUpperCase()}`;
|
|
22463
|
+
}
|
|
22464
|
+
function getInput2(name, env = process.env) {
|
|
22465
|
+
const normalizedName = normalizedInputEnvName(name);
|
|
22466
|
+
const runnerName = runnerInputEnvName(name);
|
|
22467
|
+
const normalizedRaw = env[normalizedName];
|
|
22468
|
+
const runnerRaw = runnerName === normalizedName ? void 0 : env[runnerName];
|
|
22469
|
+
const hasNormalized = normalizedRaw !== void 0;
|
|
22470
|
+
const hasRunner = runnerRaw !== void 0;
|
|
22471
|
+
if (hasNormalized && hasRunner) {
|
|
22472
|
+
const normalizedValue = normalizeInputValue(normalizedRaw);
|
|
22473
|
+
const runnerValue = normalizeInputValue(runnerRaw);
|
|
22474
|
+
if (normalizedValue !== runnerValue) {
|
|
22475
|
+
throw new Error(
|
|
22476
|
+
`Conflicting values for ${name}: ${normalizedName}=${JSON.stringify(normalizedValue)} vs ${runnerName}=${JSON.stringify(runnerValue)}`
|
|
22477
|
+
);
|
|
22478
|
+
}
|
|
22479
|
+
}
|
|
22480
|
+
return normalizeInputValue(hasNormalized ? normalizedRaw : runnerRaw);
|
|
22481
|
+
}
|
|
22482
|
+
|
|
22150
22483
|
// node_modules/@postman-cse/automation-telemetry-core/dist/ci-context.js
|
|
22151
22484
|
function norm(value) {
|
|
22152
22485
|
const trimmed = (value ?? "").trim();
|
|
@@ -22303,7 +22636,7 @@ function normalize(value) {
|
|
|
22303
22636
|
const trimmed = (value ?? "").trim();
|
|
22304
22637
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
22305
22638
|
}
|
|
22306
|
-
function
|
|
22639
|
+
function normalizeRepoUrl2(url) {
|
|
22307
22640
|
const raw = normalize(url);
|
|
22308
22641
|
if (!raw) {
|
|
22309
22642
|
return void 0;
|
|
@@ -22371,7 +22704,7 @@ function classifyRefKind(env = process.env) {
|
|
|
22371
22704
|
return "unknown";
|
|
22372
22705
|
}
|
|
22373
22706
|
function detectRepoContext(input, env = process.env) {
|
|
22374
|
-
const repoUrl =
|
|
22707
|
+
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);
|
|
22375
22708
|
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);
|
|
22376
22709
|
const ref = normalize(input.ref) ?? normalize(env.GITHUB_REF_NAME) ?? normalize(env.CI_COMMIT_REF_NAME) ?? normalize(env.BITBUCKET_BRANCH) ?? normalize(env.BUILD_SOURCEBRANCHNAME);
|
|
22377
22710
|
const sha = normalize(input.sha) ?? normalize(env.GITHUB_SHA) ?? normalize(env.CI_COMMIT_SHA) ?? normalize(env.BITBUCKET_COMMIT) ?? normalize(env.BUILD_SOURCEVERSION);
|
|
@@ -22548,7 +22881,7 @@ var DEFAULT_POSTMAN_BIFROST_BASE = PROD_ENDPOINTS.bifrostBaseUrl;
|
|
|
22548
22881
|
var DEFAULT_POSTMAN_IAPUB_BASE = PROD_ENDPOINTS.iapubBaseUrl;
|
|
22549
22882
|
var DEFAULT_POSTMAN_OBSERVABILITY_BASE = PROD_ENDPOINTS.observabilityBaseUrl;
|
|
22550
22883
|
function parsePreflightMode(value) {
|
|
22551
|
-
const normalized = String(value || "
|
|
22884
|
+
const normalized = String(value || "enforce").trim().toLowerCase();
|
|
22552
22885
|
if (normalized === "enforce" || normalized === "warn") {
|
|
22553
22886
|
return normalized;
|
|
22554
22887
|
}
|
|
@@ -22556,6 +22889,27 @@ function parsePreflightMode(value) {
|
|
|
22556
22889
|
`Unsupported credential-preflight "${value}". Supported values: enforce, warn`
|
|
22557
22890
|
);
|
|
22558
22891
|
}
|
|
22892
|
+
function parseServiceNotFoundPolicy(value) {
|
|
22893
|
+
const normalized = String(value || "fail").trim().toLowerCase();
|
|
22894
|
+
if (normalized === "fail" || normalized === "warn") {
|
|
22895
|
+
return normalized;
|
|
22896
|
+
}
|
|
22897
|
+
throw new Error(
|
|
22898
|
+
`Unsupported service-not-found-policy "${value}". Supported values: fail, warn`
|
|
22899
|
+
);
|
|
22900
|
+
}
|
|
22901
|
+
function parseCreateApiKey(value) {
|
|
22902
|
+
const normalized = String(value || "false").trim().toLowerCase();
|
|
22903
|
+
if (normalized === "true") {
|
|
22904
|
+
return true;
|
|
22905
|
+
}
|
|
22906
|
+
if (normalized === "false" || normalized === "") {
|
|
22907
|
+
return false;
|
|
22908
|
+
}
|
|
22909
|
+
throw new Error(
|
|
22910
|
+
`Unsupported create-api-key "${value}". Supported values: true, false`
|
|
22911
|
+
);
|
|
22912
|
+
}
|
|
22559
22913
|
function trimTrailingSlash(value) {
|
|
22560
22914
|
return value.replace(/\/+$/, "");
|
|
22561
22915
|
}
|
|
@@ -22574,29 +22928,12 @@ async function validateApiKey(apiKey, apiBase = DEFAULT_POSTMAN_API_BASE) {
|
|
|
22574
22928
|
const teamId = data?.user?.teamId ? String(data.user.teamId) : void 0;
|
|
22575
22929
|
return { valid: true, teamId };
|
|
22576
22930
|
}
|
|
22577
|
-
async function getTeams(apiKey, apiBase = DEFAULT_POSTMAN_API_BASE) {
|
|
22578
|
-
try {
|
|
22579
|
-
const res = await fetch(`${trimTrailingSlash(apiBase)}/teams`, {
|
|
22580
|
-
method: "GET",
|
|
22581
|
-
headers: { "x-api-key": apiKey }
|
|
22582
|
-
});
|
|
22583
|
-
if (!res.ok) return [];
|
|
22584
|
-
const data = await res.json();
|
|
22585
|
-
return (data?.data ?? []).filter((t) => t?.id && t?.name).map((t) => ({
|
|
22586
|
-
id: Number(t.id),
|
|
22587
|
-
name: String(t.name),
|
|
22588
|
-
...t.organizationId != null ? { organizationId: Number(t.organizationId) } : {}
|
|
22589
|
-
}));
|
|
22590
|
-
} catch {
|
|
22591
|
-
return [];
|
|
22592
|
-
}
|
|
22593
|
-
}
|
|
22594
22931
|
function clamp(value, min, max, fallback) {
|
|
22595
22932
|
const parsed = Number.isFinite(value) ? value : fallback;
|
|
22596
22933
|
return Math.min(max, Math.max(min, parsed));
|
|
22597
22934
|
}
|
|
22598
22935
|
function resolveInputs(env = process.env) {
|
|
22599
|
-
const get = (name, fallback = "") =>
|
|
22936
|
+
const get = (name, fallback = "") => getInput2(name, env) || fallback;
|
|
22600
22937
|
const projectName = get("project-name");
|
|
22601
22938
|
if (!projectName) throw new Error("project-name is required");
|
|
22602
22939
|
const postmanAccessToken = get("postman-access-token");
|
|
@@ -22637,7 +22974,9 @@ function resolveInputs(env = process.env) {
|
|
|
22637
22974
|
postmanApiKey,
|
|
22638
22975
|
postmanTeamId,
|
|
22639
22976
|
githubToken: get("github-token", env.GITHUB_TOKEN || ""),
|
|
22640
|
-
credentialPreflight: parsePreflightMode(get("credential-preflight", "
|
|
22977
|
+
credentialPreflight: parsePreflightMode(get("credential-preflight", "enforce")),
|
|
22978
|
+
createApiKey: parseCreateApiKey(get("create-api-key", "false")),
|
|
22979
|
+
serviceNotFoundPolicy: parseServiceNotFoundPolicy(get("service-not-found-policy", "fail")),
|
|
22641
22980
|
pollTimeoutSeconds: clamp(rawTimeout, POLL_TIMEOUT_MIN, POLL_TIMEOUT_MAX, POLL_TIMEOUT_DEFAULT),
|
|
22642
22981
|
pollIntervalSeconds: clamp(rawInterval, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, POLL_INTERVAL_DEFAULT),
|
|
22643
22982
|
postmanRegion,
|
|
@@ -22663,6 +23002,7 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22663
23002
|
const timeoutMs = inputs.pollTimeoutSeconds * 1e3;
|
|
22664
23003
|
const intervalMs = inputs.pollIntervalSeconds * 1e3;
|
|
22665
23004
|
const startTime = Date.now();
|
|
23005
|
+
const policy = inputs.serviceNotFoundPolicy ?? "fail";
|
|
22666
23006
|
reporter.info(`Looking for discovered service matching "${inputs.clusterName ? `${inputs.clusterName}/` : ""}${inputs.projectName}"...`);
|
|
22667
23007
|
let match = void 0;
|
|
22668
23008
|
while (Date.now() - startTime < timeoutMs) {
|
|
@@ -22677,15 +23017,35 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22677
23017
|
await sleepFn(intervalMs);
|
|
22678
23018
|
}
|
|
22679
23019
|
if (!match) {
|
|
22680
|
-
|
|
22681
|
-
|
|
22682
|
-
|
|
22683
|
-
|
|
22684
|
-
|
|
22685
|
-
|
|
22686
|
-
|
|
22687
|
-
|
|
22688
|
-
|
|
23020
|
+
const message = `Service "${inputs.projectName}" not found in discovered services after ${inputs.pollTimeoutSeconds}s`;
|
|
23021
|
+
if (policy === "warn") {
|
|
23022
|
+
reporter.warning(message);
|
|
23023
|
+
return {
|
|
23024
|
+
discoveredServiceId: 0,
|
|
23025
|
+
discoveredServiceName: "",
|
|
23026
|
+
collectionId: "",
|
|
23027
|
+
applicationId: "",
|
|
23028
|
+
verificationToken: null,
|
|
23029
|
+
status: "not-found"
|
|
23030
|
+
};
|
|
23031
|
+
}
|
|
23032
|
+
throw new Error(`${message}. Full linking requires a discovered service (service-not-found-policy=fail).`);
|
|
23033
|
+
}
|
|
23034
|
+
const canonicalBase = match.canonicalIdentity ?? buildCanonicalServiceIdentity(match, inputs.projectName, inputs.clusterName || void 0);
|
|
23035
|
+
const providerServiceId = await client.resolveProviderServiceId(
|
|
23036
|
+
inputs.projectName,
|
|
23037
|
+
inputs.clusterName || void 0
|
|
23038
|
+
);
|
|
23039
|
+
if (!providerServiceId) {
|
|
23040
|
+
throw new Error(
|
|
23041
|
+
`Insights provider service "${canonicalBase.serviceName}" was not found. Full linking requires one exact canonical service identity.`
|
|
23042
|
+
);
|
|
23043
|
+
}
|
|
23044
|
+
const sysEnvId = inputs.systemEnvironmentId || match.systemEnvironmentId || "";
|
|
23045
|
+
if (!sysEnvId) {
|
|
23046
|
+
throw new Error(
|
|
23047
|
+
`No system environment id is available for "${canonicalBase.serviceName}"; refusing partial linking writes.`
|
|
23048
|
+
);
|
|
22689
23049
|
}
|
|
22690
23050
|
reporter.info(`Preparing collection for service ${match.id} in workspace ${inputs.workspaceId}...`);
|
|
22691
23051
|
const collectionId = await client.prepareCollection(match.id, inputs.workspaceId);
|
|
@@ -22705,27 +23065,12 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22705
23065
|
} else {
|
|
22706
23066
|
reporter.info(`Skipping git onboarding for non-GitHub repo: ${repoUrl}`);
|
|
22707
23067
|
}
|
|
22708
|
-
|
|
22709
|
-
|
|
22710
|
-
|
|
22711
|
-
);
|
|
22712
|
-
|
|
22713
|
-
|
|
22714
|
-
const sysEnvId = inputs.systemEnvironmentId || match.systemEnvironmentId || "";
|
|
22715
|
-
if (sysEnvId) {
|
|
22716
|
-
reporter.info(`Acknowledging Insights onboarding for ${providerServiceId}...`);
|
|
22717
|
-
await client.acknowledgeOnboarding(providerServiceId, inputs.workspaceId, sysEnvId);
|
|
22718
|
-
reporter.info(`Insights acknowledged: ${providerServiceId}`);
|
|
22719
|
-
reporter.info(`Creating application binding for workspace ${inputs.workspaceId} with system_env ${sysEnvId}...`);
|
|
22720
|
-
const appResult = await client.createApplication(inputs.workspaceId, sysEnvId);
|
|
22721
|
-
applicationId = appResult.application_id;
|
|
22722
|
-
reporter.info(`Application binding created: ${appResult.application_id} for service ${appResult.service_id}`);
|
|
22723
|
-
} else {
|
|
22724
|
-
reporter.warning("No systemEnvironmentId available; skipping Insights acknowledgment and application binding");
|
|
22725
|
-
}
|
|
22726
|
-
} else {
|
|
22727
|
-
reporter.warning("Could not resolve Akita provider service ID; skipping acknowledgment and application binding");
|
|
22728
|
-
}
|
|
23068
|
+
reporter.info(`Acknowledging Insights onboarding for ${providerServiceId}...`);
|
|
23069
|
+
await client.acknowledgeOnboarding(providerServiceId, inputs.workspaceId, sysEnvId);
|
|
23070
|
+
reporter.info(`Insights acknowledged: ${providerServiceId}`);
|
|
23071
|
+
reporter.info(`Creating application binding for workspace ${inputs.workspaceId} with system_env ${sysEnvId}...`);
|
|
23072
|
+
const appResult = await client.createApplication(inputs.workspaceId, sysEnvId, providerServiceId);
|
|
23073
|
+
reporter.info(`Application binding created: ${appResult.application_id} for service ${appResult.service_id}`);
|
|
22729
23074
|
reporter.info(`Acknowledging workspace onboarding for ${inputs.workspaceId}...`);
|
|
22730
23075
|
await client.acknowledgeWorkspace(inputs.workspaceId);
|
|
22731
23076
|
reporter.info("Workspace onboarding acknowledged");
|
|
@@ -22741,9 +23086,13 @@ async function runOnboarding(inputs, client, sleepFn = sleep, reporter = core_ex
|
|
|
22741
23086
|
discoveredServiceId: match.id,
|
|
22742
23087
|
discoveredServiceName: match.name,
|
|
22743
23088
|
collectionId,
|
|
22744
|
-
applicationId,
|
|
23089
|
+
applicationId: appResult.application_id,
|
|
22745
23090
|
verificationToken,
|
|
22746
|
-
status: "success"
|
|
23091
|
+
status: "success",
|
|
23092
|
+
canonicalIdentity: {
|
|
23093
|
+
...canonicalBase,
|
|
23094
|
+
...providerServiceId ? { providerServiceId } : {}
|
|
23095
|
+
}
|
|
22747
23096
|
};
|
|
22748
23097
|
}
|
|
22749
23098
|
async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
@@ -22752,6 +23101,7 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
22752
23101
|
let keyValid = false;
|
|
22753
23102
|
let pmakIdentity;
|
|
22754
23103
|
const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
|
|
23104
|
+
const createApiKey = inputs.createApiKey === true;
|
|
22755
23105
|
if (apiKey) {
|
|
22756
23106
|
const result = await validateApiKey(apiKey, apiBase);
|
|
22757
23107
|
keyValid = result.valid;
|
|
@@ -22762,49 +23112,36 @@ async function resolveApiKeyAndTeamId(inputs, client, reporter = core_exports) {
|
|
|
22762
23112
|
}
|
|
22763
23113
|
}
|
|
22764
23114
|
if (!keyValid) {
|
|
22765
|
-
|
|
22766
|
-
|
|
23115
|
+
if (!createApiKey) {
|
|
23116
|
+
if (apiKey) {
|
|
23117
|
+
throw new Error(
|
|
23118
|
+
"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."
|
|
23119
|
+
);
|
|
23120
|
+
}
|
|
23121
|
+
throw new Error(
|
|
23122
|
+
"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."
|
|
23123
|
+
);
|
|
23124
|
+
}
|
|
23125
|
+
reporter.info("create-api-key=true: generating a durable Postman API key via Bifrost identity service...");
|
|
23126
|
+
const keyName = `insights-onboarding-${inputs.projectName}`;
|
|
22767
23127
|
apiKey = await client.createApiKey(keyName);
|
|
22768
23128
|
reporter.setSecret(apiKey);
|
|
22769
23129
|
client.setApiKey(apiKey);
|
|
22770
|
-
|
|
22771
|
-
|
|
22772
|
-
|
|
22773
|
-
if (!resolvedTeamId && apiKey) {
|
|
22774
|
-
try {
|
|
22775
|
-
const teams = await getTeams(apiKey, apiBase);
|
|
22776
|
-
if (teams.length > 1 && teams.every((t) => t.organizationId == null)) {
|
|
22777
|
-
reporter.warning(
|
|
22778
|
-
"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."
|
|
22779
|
-
);
|
|
22780
|
-
}
|
|
22781
|
-
const isOrgMode = teams.some((t) => t.organizationId != null);
|
|
22782
|
-
if (isOrgMode) {
|
|
22783
|
-
if (teams.length === 1) {
|
|
22784
|
-
resolvedTeamId = String(teams[0].id);
|
|
22785
|
-
reporter.info(
|
|
22786
|
-
`Org-mode account detected. Using sub-team ${teams[0].id} (${teams[0].name ?? "unknown"}) for Bifrost calls.`
|
|
22787
|
-
);
|
|
22788
|
-
} else {
|
|
22789
|
-
const meResult = await validateApiKey(apiKey, apiBase);
|
|
22790
|
-
const meTeamId = meResult.teamId ? parseInt(meResult.teamId, 10) : NaN;
|
|
22791
|
-
if (!Number.isNaN(meTeamId) && teams.some((t) => t.id === meTeamId)) {
|
|
22792
|
-
resolvedTeamId = String(meTeamId);
|
|
22793
|
-
reporter.info(
|
|
22794
|
-
`Org-mode account detected. Using sub-team ${meTeamId} (from /me) for Bifrost calls.`
|
|
22795
|
-
);
|
|
22796
|
-
}
|
|
22797
|
-
}
|
|
22798
|
-
}
|
|
22799
|
-
} catch {
|
|
23130
|
+
const createdIdentity = await validateApiKey(apiKey, apiBase);
|
|
23131
|
+
if (!createdIdentity.valid) {
|
|
23132
|
+
throw new Error("The explicitly created postman-api-key could not be validated; refusing linking writes.");
|
|
22800
23133
|
}
|
|
23134
|
+
pmakIdentity = { source: "pmak/me", teamId: createdIdentity.teamId };
|
|
23135
|
+
reporter.info(`New API key created successfully (${keyName}).`);
|
|
22801
23136
|
}
|
|
22802
|
-
if (
|
|
22803
|
-
reporter.info(`Using postman-team-id for Bifrost headers: ${
|
|
23137
|
+
if (teamId) {
|
|
23138
|
+
reporter.info(`Using explicit postman-team-id for Bifrost headers: ${teamId}`);
|
|
22804
23139
|
} else {
|
|
22805
|
-
reporter.info(
|
|
23140
|
+
reporter.info(
|
|
23141
|
+
"No postman-team-id / POSTMAN_TEAM_ID provided; omitting x-entity-team-id so Bifrost resolves team from the access token."
|
|
23142
|
+
);
|
|
22806
23143
|
}
|
|
22807
|
-
return { apiKey, teamId
|
|
23144
|
+
return { apiKey, teamId, pmakIdentity };
|
|
22808
23145
|
}
|
|
22809
23146
|
async function runCredentialPreflightForInputs(inputs, pmak, reporter, fetchImpl, liveAccessToken) {
|
|
22810
23147
|
const accessToken = liveAccessToken ?? inputs.postmanAccessToken;
|
|
@@ -22862,24 +23199,54 @@ async function runAction() {
|
|
|
22862
23199
|
inputs.postmanTeamId,
|
|
22863
23200
|
inputs.postmanApiKey
|
|
22864
23201
|
);
|
|
22865
|
-
const { apiKey, teamId, pmakIdentity } = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
|
|
22866
|
-
const activeTokenProvider = apiKey !== inputs.postmanApiKey ? new AccessTokenProvider({
|
|
22867
|
-
accessToken: tokenProvider.current(),
|
|
22868
|
-
apiKey,
|
|
22869
|
-
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
22870
|
-
onToken: (token) => setSecret(token)
|
|
22871
|
-
}) : tokenProvider;
|
|
22872
23202
|
const telemetry = createTelemetryContext({ action: "postman-insights-onboarding-action", actionVersion: resolveActionVersion2(), logger: core_exports });
|
|
22873
|
-
telemetry.setTeamId(inputs.postmanTeamId
|
|
23203
|
+
telemetry.setTeamId(inputs.postmanTeamId);
|
|
22874
23204
|
let result;
|
|
22875
23205
|
try {
|
|
23206
|
+
let apiKey = inputs.postmanApiKey;
|
|
23207
|
+
let pmakIdentity;
|
|
23208
|
+
const apiBase = inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE;
|
|
23209
|
+
if (apiKey) {
|
|
23210
|
+
const validated = await validateApiKey(apiKey, apiBase);
|
|
23211
|
+
if (validated.valid) {
|
|
23212
|
+
pmakIdentity = { source: "pmak/me", teamId: validated.teamId };
|
|
23213
|
+
} else if (!inputs.createApiKey) {
|
|
23214
|
+
throw new Error(
|
|
23215
|
+
"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."
|
|
23216
|
+
);
|
|
23217
|
+
} else {
|
|
23218
|
+
warning("Provided postman-api-key is invalid or expired.");
|
|
23219
|
+
}
|
|
23220
|
+
} else if (!inputs.createApiKey) {
|
|
23221
|
+
throw new Error(
|
|
23222
|
+
"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."
|
|
23223
|
+
);
|
|
23224
|
+
}
|
|
22876
23225
|
await runCredentialPreflightForInputs(
|
|
22877
23226
|
inputs,
|
|
22878
23227
|
pmakIdentity,
|
|
22879
23228
|
core_exports,
|
|
22880
23229
|
void 0,
|
|
22881
|
-
|
|
23230
|
+
tokenProvider.current()
|
|
22882
23231
|
);
|
|
23232
|
+
const resolved = await resolveApiKeyAndTeamId(inputs, preliminaryClient, core_exports);
|
|
23233
|
+
apiKey = resolved.apiKey;
|
|
23234
|
+
const teamId = resolved.teamId;
|
|
23235
|
+
if (resolved.pmakIdentity?.teamId !== pmakIdentity?.teamId) {
|
|
23236
|
+
await runCredentialPreflightForInputs(
|
|
23237
|
+
inputs,
|
|
23238
|
+
resolved.pmakIdentity,
|
|
23239
|
+
core_exports,
|
|
23240
|
+
void 0,
|
|
23241
|
+
tokenProvider.current()
|
|
23242
|
+
);
|
|
23243
|
+
}
|
|
23244
|
+
const activeTokenProvider = apiKey !== inputs.postmanApiKey ? new AccessTokenProvider({
|
|
23245
|
+
accessToken: tokenProvider.current(),
|
|
23246
|
+
apiKey,
|
|
23247
|
+
apiBaseUrl: inputs.postmanApiBase || DEFAULT_POSTMAN_API_BASE,
|
|
23248
|
+
onToken: (token) => setSecret(token)
|
|
23249
|
+
}) : tokenProvider;
|
|
22883
23250
|
const client = createInsightsBifrostClient(inputs, activeTokenProvider, teamId, apiKey);
|
|
22884
23251
|
result = await runOnboarding(inputs, client, sleep, core_exports);
|
|
22885
23252
|
} catch (error2) {
|