@postman-cse/onboarding-repo-sync 2.5.0 → 2.6.1
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 +1 -0
- package/action.yml +4 -0
- package/dist/action.cjs +203 -13
- package/dist/cli.cjs +204 -13
- package/dist/index.cjs +203 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -185,6 +185,7 @@ with:
|
|
|
185
185
|
| `postman-api-key` | Postman API key (PMAK). Used only to mint/re-mint the access token (via postman-resolve-service-token-action) and for the Postman CLI spec-lint login. Asset operations (environments, collections, mocks, monitors) run on the access-token gateway and do not use the PMAK. Optional when a valid postman-access-token is supplied; required only for the CLI lint path. | no | |
|
|
186
186
|
| `postman-access-token` | Postman access token minted by postman-resolve-service-token-action. Required for all asset operations (environment create/get/update, collection read, mock, monitor) which run through the access-token gateway. Also used for workspace linking, system environment association, and generated API-key creation. When omitted, the action mints one from postman-api-key (service-account PMAK); if that mint also fails the action fails fast — PMAK is never an asset-routing fallback. | no | |
|
|
187
187
|
| `team-id` | Postman team ID resolved by postman-resolve-service-token-action. Primary team scope for all downstream actions; included as x-entity-team-id in org-mode Bifrost calls. Falls back to POSTMAN_TEAM_ID when omitted. Set explicitly for org-mode teams. | no | `""` |
|
|
188
|
+
| `secrets-resolver` | Cloud secret store the generated environments seed credential slots for: none (default, no secret-store variables are added), aws (AWS Secrets Manager), azure (Azure Key Vault), or gcp (Google Secret Manager). Must match the secrets-resolver value passed to the bootstrap and smoke-flow actions. | no | `none` |
|
|
188
189
|
| `credential-preflight` | Credential identity preflight policy. warn (default) logs a note and continues when postman-api-key and postman-access-token resolve to different parent orgs; enforce fails the run on that condition before any workspace is created. Both modes warn when postman-access-token is not a service-account token. | no | `warn` |
|
|
189
190
|
| `branch-strategy` | Branch-aware sync strategy. legacy (default) keeps branch-blind behavior; publish-gate restricts canonical writes to the canonical branch and skips repo-sync on other branches; preview additionally maintains suffixed per-branch preview asset sets. | no | `legacy` |
|
|
190
191
|
| `canonical-branch` | Explicit canonical branch (the sole writer of canonical assets and tracked state). Defaults to the provider-resolved default branch; required on providers without a default-branch variable (Bitbucket, Azure DevOps) when branch-strategy is not legacy. | no | |
|
package/action.yml
CHANGED
|
@@ -134,6 +134,10 @@ inputs:
|
|
|
134
134
|
description: Postman team ID resolved by postman-resolve-service-token-action. Primary team scope for all downstream actions; included as x-entity-team-id in org-mode Bifrost calls. Falls back to POSTMAN_TEAM_ID when omitted. Set explicitly for org-mode teams.
|
|
135
135
|
required: false
|
|
136
136
|
default: ""
|
|
137
|
+
secrets-resolver:
|
|
138
|
+
description: 'Cloud secret store the generated environments seed credential slots for: none (default, no secret-store variables are added), aws (AWS Secrets Manager), azure (Azure Key Vault), or gcp (Google Secret Manager). Must match the secrets-resolver value passed to the bootstrap and smoke-flow actions.'
|
|
139
|
+
required: false
|
|
140
|
+
default: "none"
|
|
137
141
|
credential-preflight:
|
|
138
142
|
description: 'Credential identity preflight policy. warn (default) logs a note and continues when postman-api-key and postman-access-token resolve to different parent orgs; enforce fails the run on that condition before any workspace is created. Both modes warn when postman-access-token is not a service-account token.'
|
|
139
143
|
required: false
|
package/dist/action.cjs
CHANGED
|
@@ -135759,6 +135759,49 @@ function createLogger(options) {
|
|
|
135759
135759
|
return root;
|
|
135760
135760
|
}
|
|
135761
135761
|
|
|
135762
|
+
// node_modules/@postman-cse/automation-core/dist/secrets-resolver.js
|
|
135763
|
+
var SECRETS_RESOLVER_PROVIDERS = ["none", "aws", "azure", "gcp"];
|
|
135764
|
+
var DEFAULT_SECRETS_RESOLVER_PROVIDER = "none";
|
|
135765
|
+
function parseSecretsResolverProvider(value, fallback = DEFAULT_SECRETS_RESOLVER_PROVIDER) {
|
|
135766
|
+
const raw = String(value ?? "").trim().toLowerCase();
|
|
135767
|
+
if (!raw)
|
|
135768
|
+
return fallback;
|
|
135769
|
+
if (raw === "true")
|
|
135770
|
+
return "aws";
|
|
135771
|
+
if (raw === "false" || raw === "off")
|
|
135772
|
+
return "none";
|
|
135773
|
+
if (SECRETS_RESOLVER_PROVIDERS.includes(raw)) {
|
|
135774
|
+
return raw;
|
|
135775
|
+
}
|
|
135776
|
+
throw new Error(`SECRETS_RESOLVER_PROVIDER_INVALID: expected one of ${SECRETS_RESOLVER_PROVIDERS.join(", ")} (or legacy true/false), received "${value}"`);
|
|
135777
|
+
}
|
|
135778
|
+
function secretsResolverEnvironmentKeys(provider) {
|
|
135779
|
+
switch (provider) {
|
|
135780
|
+
case "aws":
|
|
135781
|
+
return [
|
|
135782
|
+
{ key: "AWS_ACCESS_KEY_ID", secret: true },
|
|
135783
|
+
{ key: "AWS_SECRET_ACCESS_KEY", secret: true },
|
|
135784
|
+
{ key: "AWS_REGION", secret: false },
|
|
135785
|
+
{ key: "AWS_SECRET_NAME", secret: false }
|
|
135786
|
+
];
|
|
135787
|
+
case "azure":
|
|
135788
|
+
return [
|
|
135789
|
+
{ key: "AZURE_KEY_VAULT_NAME", secret: false },
|
|
135790
|
+
{ key: "AZURE_SECRET_NAME", secret: false },
|
|
135791
|
+
{ key: "AZURE_ACCESS_TOKEN", secret: true }
|
|
135792
|
+
];
|
|
135793
|
+
case "gcp":
|
|
135794
|
+
return [
|
|
135795
|
+
{ key: "GCP_PROJECT_ID", secret: false },
|
|
135796
|
+
{ key: "GCP_SECRET_NAME", secret: false },
|
|
135797
|
+
{ key: "GCP_ACCESS_TOKEN", secret: true }
|
|
135798
|
+
];
|
|
135799
|
+
case "none":
|
|
135800
|
+
default:
|
|
135801
|
+
return [];
|
|
135802
|
+
}
|
|
135803
|
+
}
|
|
135804
|
+
|
|
135762
135805
|
// src/action-version.ts
|
|
135763
135806
|
var import_node_fs3 = require("node:fs");
|
|
135764
135807
|
var import_node_path2 = require("node:path");
|
|
@@ -136962,6 +137005,12 @@ var postmanRepoSyncActionContract = {
|
|
|
136962
137005
|
required: false,
|
|
136963
137006
|
default: ""
|
|
136964
137007
|
},
|
|
137008
|
+
"secrets-resolver": {
|
|
137009
|
+
description: "Cloud secret store the generated environments seed credential slots for (none, aws, azure, gcp). Must match the value passed to bootstrap and smoke-flow.",
|
|
137010
|
+
required: false,
|
|
137011
|
+
default: "none",
|
|
137012
|
+
allowedValues: ["none", "aws", "azure", "gcp"]
|
|
137013
|
+
},
|
|
136965
137014
|
"credential-preflight": {
|
|
136966
137015
|
description: "Credential identity preflight policy. warn (default) logs a note and continues when postman-api-key and postman-access-token resolve to different parent orgs; enforce fails the run on that condition before any workspace is created. Both modes warn when postman-access-token is not a service-account token.",
|
|
136967
137016
|
required: false,
|
|
@@ -137519,6 +137568,12 @@ var MockContractError = class extends Error {
|
|
|
137519
137568
|
this.name = "MockContractError";
|
|
137520
137569
|
}
|
|
137521
137570
|
};
|
|
137571
|
+
var AmbiguousMonitorRebindError = class extends Error {
|
|
137572
|
+
constructor(message, options) {
|
|
137573
|
+
super(message, options);
|
|
137574
|
+
this.name = "AmbiguousMonitorRebindError";
|
|
137575
|
+
}
|
|
137576
|
+
};
|
|
137522
137577
|
function requireMockVisibility(mock, requested) {
|
|
137523
137578
|
if (mock.visibility === "unknown") {
|
|
137524
137579
|
throw new MockContractError(
|
|
@@ -138355,6 +138410,73 @@ var PostmanGatewayAssetsClient = class {
|
|
|
138355
138410
|
);
|
|
138356
138411
|
return match?.uid ? { uid: match.uid, name: match.name } : null;
|
|
138357
138412
|
}
|
|
138413
|
+
pickRebindMonitorCandidates(monitors, monitorName, environment) {
|
|
138414
|
+
const nameOnly = monitors.filter((monitor) => monitor.name === monitorName);
|
|
138415
|
+
const nameOnlyEnvironments = [
|
|
138416
|
+
...new Set(nameOnly.map((monitor) => monitor.environmentUid || "(none)"))
|
|
138417
|
+
];
|
|
138418
|
+
const candidates = nameOnly.filter((monitor) => monitor.environmentUid === environment);
|
|
138419
|
+
return { candidates, nameOnlyMatchCount: nameOnly.length, nameOnlyEnvironments };
|
|
138420
|
+
}
|
|
138421
|
+
/**
|
|
138422
|
+
* Rebind the sole same-name monitor in this workspace onto the current
|
|
138423
|
+
* collection UID.
|
|
138424
|
+
*
|
|
138425
|
+
* The canonical Smoke collection can legitimately change UID (a bootstrap
|
|
138426
|
+
* re-import after a marker/stranger miss, or an operator rebuild). The
|
|
138427
|
+
* monitor still points at the previous UID, so `findMonitorByCollection`
|
|
138428
|
+
* (which requires the full collection+environment+name triple) misses and a
|
|
138429
|
+
* second monitor with the same name would be created on every run, orphaning
|
|
138430
|
+
* the old one. Name plus environment is the stable identity across a
|
|
138431
|
+
* collection re-import, so recover it here instead.
|
|
138432
|
+
*
|
|
138433
|
+
* Returns null when nothing needs rebinding (no same-name monitor, or it is
|
|
138434
|
+
* already bound to this collection). Refuses to guess when several same-name
|
|
138435
|
+
* monitors match, matching `selectExactMatch` semantics elsewhere.
|
|
138436
|
+
*/
|
|
138437
|
+
async rebindMonitorByName(name, collectionUid, environmentUid) {
|
|
138438
|
+
const monitorName = String(name ?? "").trim();
|
|
138439
|
+
const collection = String(collectionUid ?? "").trim();
|
|
138440
|
+
const environment = String(environmentUid ?? "").trim();
|
|
138441
|
+
if (!monitorName || !collection) {
|
|
138442
|
+
return null;
|
|
138443
|
+
}
|
|
138444
|
+
const monitors = await this.listMonitors();
|
|
138445
|
+
const { candidates, nameOnlyMatchCount, nameOnlyEnvironments } = this.pickRebindMonitorCandidates(
|
|
138446
|
+
monitors,
|
|
138447
|
+
monitorName,
|
|
138448
|
+
environment
|
|
138449
|
+
);
|
|
138450
|
+
let match;
|
|
138451
|
+
try {
|
|
138452
|
+
match = this.selectExactMatch(
|
|
138453
|
+
"monitor",
|
|
138454
|
+
`workspace ${this.workspaceId}, name "${monitorName}", and environment ${environment || "(none)"}`,
|
|
138455
|
+
candidates
|
|
138456
|
+
);
|
|
138457
|
+
} catch (error2) {
|
|
138458
|
+
const message = error2 instanceof Error && nameOnlyMatchCount > 0 ? `${error2.message} Same-name monitor(s) also exist on environment(s): ${nameOnlyEnvironments.join(", ")}.` : error2 instanceof Error ? error2.message : String(error2);
|
|
138459
|
+
throw new AmbiguousMonitorRebindError(message, { cause: error2 });
|
|
138460
|
+
}
|
|
138461
|
+
if (!match?.uid) {
|
|
138462
|
+
if (nameOnlyMatchCount > 0) {
|
|
138463
|
+
}
|
|
138464
|
+
return null;
|
|
138465
|
+
}
|
|
138466
|
+
if (match.collectionUid === collection) {
|
|
138467
|
+
return null;
|
|
138468
|
+
}
|
|
138469
|
+
await this.gateway.requestJson(
|
|
138470
|
+
{
|
|
138471
|
+
service: "monitors",
|
|
138472
|
+
method: "put",
|
|
138473
|
+
path: `/jobTemplates/${this.toModelId(match.uid)}?_etc=true`,
|
|
138474
|
+
body: { collection }
|
|
138475
|
+
},
|
|
138476
|
+
{ retryTransient: false }
|
|
138477
|
+
);
|
|
138478
|
+
return { uid: match.uid, previousCollectionUid: match.collectionUid };
|
|
138479
|
+
}
|
|
138358
138480
|
async runMonitor(uid) {
|
|
138359
138481
|
await this.gateway.requestJson(
|
|
138360
138482
|
{
|
|
@@ -139527,6 +139649,7 @@ function resolveInputs(env = process.env) {
|
|
|
139527
139649
|
env
|
|
139528
139650
|
);
|
|
139529
139651
|
const environments = parseJsonArray(getInput2("environments-json", env) || '["prod"]');
|
|
139652
|
+
const secretsResolverProvider = parseSecretsResolverProvider(getInput2("secrets-resolver", env));
|
|
139530
139653
|
const systemEnvMap = parseJsonMap(getInput2("system-env-map-json", env) || "{}");
|
|
139531
139654
|
const environmentUids = parseJsonMap(getInput2("environment-uids-json", env) || "{}");
|
|
139532
139655
|
const envRuntimeUrls = parseJsonMap(getInput2("env-runtime-urls-json", env) || "{}");
|
|
@@ -139544,6 +139667,7 @@ function resolveInputs(env = process.env) {
|
|
|
139544
139667
|
specContentChanged: parseBooleanInput(getInput2("spec-content-changed", env), true),
|
|
139545
139668
|
specPath: getInput2("spec-path", env),
|
|
139546
139669
|
collectionSyncMode: normalizeCollectionSyncMode(getInput2("collection-sync-mode", env) || "refresh"),
|
|
139670
|
+
secretsResolverProvider,
|
|
139547
139671
|
specSyncMode: normalizeSpecSyncMode(getInput2("spec-sync-mode", env) || "update"),
|
|
139548
139672
|
releaseLabel: normalizeReleaseLabel(getInput2("release-label", env)) || void 0,
|
|
139549
139673
|
environments: environments.length > 0 ? environments : ["prod"],
|
|
@@ -139633,7 +139757,20 @@ function assertBranchAssetIds(inputs, decision, branchOwnedIds = process.env.POS
|
|
|
139633
139757
|
);
|
|
139634
139758
|
}
|
|
139635
139759
|
}
|
|
139760
|
+
var CREDENTIAL_SLOT_PREFIXES = ["AWS_", "AZURE_", "GCP_"];
|
|
139761
|
+
function isCredentialSlotKey(key) {
|
|
139762
|
+
return typeof key === "string" && CREDENTIAL_SLOT_PREFIXES.some((prefix) => key.startsWith(prefix));
|
|
139763
|
+
}
|
|
139636
139764
|
function buildEnvironmentValues(envName, baseUrl, options = {}) {
|
|
139765
|
+
const seeded = secretsResolverEnvironmentKeys(options.secretsResolverProvider ?? "none").map((entry) => ({
|
|
139766
|
+
key: entry.key,
|
|
139767
|
+
value: entry.key.endsWith("_SECRET_NAME") && !entry.secret ? `api-credentials-${envName}` : entry.defaultValue ?? "",
|
|
139768
|
+
type: entry.secret ? "secret" : "default"
|
|
139769
|
+
}));
|
|
139770
|
+
const seededKeys = new Set(seeded.map((entry) => entry.key));
|
|
139771
|
+
const preserved = (options.preservedCredentialValues ?? []).filter(
|
|
139772
|
+
(entry) => isCredentialSlotKey(entry.key) && !seededKeys.has(entry.key)
|
|
139773
|
+
);
|
|
139637
139774
|
return [
|
|
139638
139775
|
{ key: "baseUrl", value: baseUrl, type: "default" },
|
|
139639
139776
|
// A private mock refuses anonymous calls, so the manual-validation environment
|
|
@@ -139642,10 +139779,11 @@ function buildEnvironmentValues(envName, baseUrl, options = {}) {
|
|
|
139642
139779
|
...options.privateMockAuth ? [{ key: PRIVATE_MOCK_AUTH_VARIABLE2, value: "", type: "secret" }] : [],
|
|
139643
139780
|
{ key: "CI", value: "false", type: "default" },
|
|
139644
139781
|
{ key: "RESPONSE_TIME_THRESHOLD", value: "2000", type: "default" },
|
|
139645
|
-
|
|
139646
|
-
|
|
139647
|
-
|
|
139648
|
-
|
|
139782
|
+
// Provider-scoped credential slots. `none` (the default) seeds nothing, so a
|
|
139783
|
+
// consumer that does not use a cloud secret store gets a clean environment
|
|
139784
|
+
// instead of four dead AWS variables.
|
|
139785
|
+
...seeded,
|
|
139786
|
+
...preserved
|
|
139649
139787
|
];
|
|
139650
139788
|
}
|
|
139651
139789
|
var LEGACY_BASELINE_COLLECTION_PREFIX = "[Baseline]";
|
|
@@ -140131,19 +140269,28 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
|
|
|
140131
140269
|
}
|
|
140132
140270
|
if (existingUid) {
|
|
140133
140271
|
let marker = assetMarker;
|
|
140134
|
-
|
|
140135
|
-
|
|
140136
|
-
|
|
140137
|
-
|
|
140138
|
-
|
|
140272
|
+
let priorValues = [];
|
|
140273
|
+
try {
|
|
140274
|
+
const existing = await dependencies.postman.getEnvironment(existingUid);
|
|
140275
|
+
const values3 = existing?.data?.values ?? existing?.values ?? [];
|
|
140276
|
+
priorValues = values3.filter((value) => typeof value?.key === "string").map((value) => ({
|
|
140277
|
+
key: value.key,
|
|
140278
|
+
value: typeof value.value === "string" ? value.value : "",
|
|
140279
|
+
type: value.type === "secret" ? "secret" : "default"
|
|
140280
|
+
}));
|
|
140281
|
+
if (marker) {
|
|
140282
|
+
const prior = priorValues.find((value) => value.key === "x-pm-onboarding")?.value;
|
|
140139
140283
|
const priorMarker = parseAssetMarker(prior ? `x-pm-onboarding: ${prior}` : void 0);
|
|
140140
140284
|
if (priorMarker?.repo === marker.repo && priorMarker.rawBranch === marker.rawBranch) {
|
|
140141
140285
|
marker = { ...marker, createdAt: priorMarker.createdAt };
|
|
140142
140286
|
}
|
|
140143
|
-
} catch {
|
|
140144
140287
|
}
|
|
140288
|
+
} catch {
|
|
140145
140289
|
}
|
|
140146
|
-
const values2 = buildEnvironmentValues(envName, runtimeUrl
|
|
140290
|
+
const values2 = buildEnvironmentValues(envName, runtimeUrl, {
|
|
140291
|
+
secretsResolverProvider: inputs.secretsResolverProvider,
|
|
140292
|
+
preservedCredentialValues: priorValues
|
|
140293
|
+
});
|
|
140147
140294
|
if (marker) values2.push({ key: "x-pm-onboarding", value: JSON.stringify(marker), type: "default" });
|
|
140148
140295
|
try {
|
|
140149
140296
|
await dependencies.postman.updateEnvironment(existingUid, displayName, values2);
|
|
@@ -140163,7 +140310,9 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
|
|
|
140163
140310
|
dependencies.core.setOutput("environment-uids-json", JSON.stringify(envUids));
|
|
140164
140311
|
continue;
|
|
140165
140312
|
}
|
|
140166
|
-
const values = buildEnvironmentValues(envName, runtimeUrl
|
|
140313
|
+
const values = buildEnvironmentValues(envName, runtimeUrl, {
|
|
140314
|
+
secretsResolverProvider: inputs.secretsResolverProvider
|
|
140315
|
+
});
|
|
140167
140316
|
if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
|
|
140168
140317
|
try {
|
|
140169
140318
|
envUids[envName] = await dependencies.postman.createEnvironment(
|
|
@@ -140192,7 +140341,10 @@ async function upsertMockEnvironment(inputs, dependencies, assetProjectName, moc
|
|
|
140192
140341
|
return "";
|
|
140193
140342
|
}
|
|
140194
140343
|
const displayName = `${assetProjectName} - Mock`;
|
|
140195
|
-
const values = buildEnvironmentValues("mock", mockUrl, {
|
|
140344
|
+
const values = buildEnvironmentValues("mock", mockUrl, {
|
|
140345
|
+
privateMockAuth,
|
|
140346
|
+
secretsResolverProvider: inputs.secretsResolverProvider
|
|
140347
|
+
});
|
|
140196
140348
|
const mask = resolveRepoSyncMasker(dependencies);
|
|
140197
140349
|
try {
|
|
140198
140350
|
const discovered = await dependencies.postman.findEnvironmentByName(
|
|
@@ -141463,6 +141615,43 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
141463
141615
|
);
|
|
141464
141616
|
}
|
|
141465
141617
|
}
|
|
141618
|
+
if (!resolvedMonitorId && inputs.smokeCollectionId && dependencies.postman.rebindMonitorByName) {
|
|
141619
|
+
try {
|
|
141620
|
+
const rebound = await dependencies.postman.rebindMonitorByName(
|
|
141621
|
+
monitorName,
|
|
141622
|
+
inputs.smokeCollectionId,
|
|
141623
|
+
monitorEnvUid
|
|
141624
|
+
);
|
|
141625
|
+
if (rebound) {
|
|
141626
|
+
resolvedMonitorId = rebound.uid;
|
|
141627
|
+
dependencies.core.info(
|
|
141628
|
+
`Rebound existing monitor ${rebound.uid} ("${monitorName}") from collection ${rebound.previousCollectionUid} to ${inputs.smokeCollectionId}`
|
|
141629
|
+
);
|
|
141630
|
+
}
|
|
141631
|
+
} catch (error2) {
|
|
141632
|
+
if (error2 instanceof AmbiguousMonitorRebindError) {
|
|
141633
|
+
throw new Error(
|
|
141634
|
+
formatOrchestrationIssue({
|
|
141635
|
+
operation: "Monitor rebind",
|
|
141636
|
+
entity: `monitor "${monitorName}" workspace ${inputs.workspaceId} collection ${inputs.smokeCollectionId} environment ${monitorEnvUid}`,
|
|
141637
|
+
cause: error2,
|
|
141638
|
+
remediation: monitorRemediation,
|
|
141639
|
+
mask
|
|
141640
|
+
}),
|
|
141641
|
+
{ cause: error2 }
|
|
141642
|
+
);
|
|
141643
|
+
}
|
|
141644
|
+
dependencies.core.warning(
|
|
141645
|
+
formatOrchestrationIssue({
|
|
141646
|
+
operation: "Monitor rebind",
|
|
141647
|
+
entity: `monitor "${monitorName}" workspace ${inputs.workspaceId} collection ${inputs.smokeCollectionId} environment ${monitorEnvUid}`,
|
|
141648
|
+
cause: error2,
|
|
141649
|
+
remediation: monitorRemediation,
|
|
141650
|
+
mask
|
|
141651
|
+
})
|
|
141652
|
+
);
|
|
141653
|
+
}
|
|
141654
|
+
}
|
|
141466
141655
|
if (!resolvedMonitorId) {
|
|
141467
141656
|
try {
|
|
141468
141657
|
resolvedMonitorId = await dependencies.postman.createMonitor(
|
|
@@ -141884,6 +142073,7 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
|
|
|
141884
142073
|
listMonitors: gatewayAssets.listMonitors.bind(gatewayAssets),
|
|
141885
142074
|
monitorExists: gatewayAssets.monitorExists.bind(gatewayAssets),
|
|
141886
142075
|
findMonitorByCollection: gatewayAssets.findMonitorByCollection.bind(gatewayAssets),
|
|
142076
|
+
rebindMonitorByName: gatewayAssets.rebindMonitorByName.bind(gatewayAssets),
|
|
141887
142077
|
runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets),
|
|
141888
142078
|
listEnvironments: gatewayAssets.listEnvironments.bind(gatewayAssets),
|
|
141889
142079
|
// GC path (preview/channel retention machine — lib/repo/preview-gc.ts).
|
package/dist/cli.cjs
CHANGED
|
@@ -133864,6 +133864,49 @@ function createLogger(options) {
|
|
|
133864
133864
|
return root;
|
|
133865
133865
|
}
|
|
133866
133866
|
|
|
133867
|
+
// node_modules/@postman-cse/automation-core/dist/secrets-resolver.js
|
|
133868
|
+
var SECRETS_RESOLVER_PROVIDERS = ["none", "aws", "azure", "gcp"];
|
|
133869
|
+
var DEFAULT_SECRETS_RESOLVER_PROVIDER = "none";
|
|
133870
|
+
function parseSecretsResolverProvider(value, fallback = DEFAULT_SECRETS_RESOLVER_PROVIDER) {
|
|
133871
|
+
const raw = String(value ?? "").trim().toLowerCase();
|
|
133872
|
+
if (!raw)
|
|
133873
|
+
return fallback;
|
|
133874
|
+
if (raw === "true")
|
|
133875
|
+
return "aws";
|
|
133876
|
+
if (raw === "false" || raw === "off")
|
|
133877
|
+
return "none";
|
|
133878
|
+
if (SECRETS_RESOLVER_PROVIDERS.includes(raw)) {
|
|
133879
|
+
return raw;
|
|
133880
|
+
}
|
|
133881
|
+
throw new Error(`SECRETS_RESOLVER_PROVIDER_INVALID: expected one of ${SECRETS_RESOLVER_PROVIDERS.join(", ")} (or legacy true/false), received "${value}"`);
|
|
133882
|
+
}
|
|
133883
|
+
function secretsResolverEnvironmentKeys(provider) {
|
|
133884
|
+
switch (provider) {
|
|
133885
|
+
case "aws":
|
|
133886
|
+
return [
|
|
133887
|
+
{ key: "AWS_ACCESS_KEY_ID", secret: true },
|
|
133888
|
+
{ key: "AWS_SECRET_ACCESS_KEY", secret: true },
|
|
133889
|
+
{ key: "AWS_REGION", secret: false },
|
|
133890
|
+
{ key: "AWS_SECRET_NAME", secret: false }
|
|
133891
|
+
];
|
|
133892
|
+
case "azure":
|
|
133893
|
+
return [
|
|
133894
|
+
{ key: "AZURE_KEY_VAULT_NAME", secret: false },
|
|
133895
|
+
{ key: "AZURE_SECRET_NAME", secret: false },
|
|
133896
|
+
{ key: "AZURE_ACCESS_TOKEN", secret: true }
|
|
133897
|
+
];
|
|
133898
|
+
case "gcp":
|
|
133899
|
+
return [
|
|
133900
|
+
{ key: "GCP_PROJECT_ID", secret: false },
|
|
133901
|
+
{ key: "GCP_SECRET_NAME", secret: false },
|
|
133902
|
+
{ key: "GCP_ACCESS_TOKEN", secret: true }
|
|
133903
|
+
];
|
|
133904
|
+
case "none":
|
|
133905
|
+
default:
|
|
133906
|
+
return [];
|
|
133907
|
+
}
|
|
133908
|
+
}
|
|
133909
|
+
|
|
133867
133910
|
// src/action-version.ts
|
|
133868
133911
|
var import_node_fs3 = require("node:fs");
|
|
133869
133912
|
var import_node_path2 = require("node:path");
|
|
@@ -135067,6 +135110,12 @@ var postmanRepoSyncActionContract = {
|
|
|
135067
135110
|
required: false,
|
|
135068
135111
|
default: ""
|
|
135069
135112
|
},
|
|
135113
|
+
"secrets-resolver": {
|
|
135114
|
+
description: "Cloud secret store the generated environments seed credential slots for (none, aws, azure, gcp). Must match the value passed to bootstrap and smoke-flow.",
|
|
135115
|
+
required: false,
|
|
135116
|
+
default: "none",
|
|
135117
|
+
allowedValues: ["none", "aws", "azure", "gcp"]
|
|
135118
|
+
},
|
|
135070
135119
|
"credential-preflight": {
|
|
135071
135120
|
description: "Credential identity preflight policy. warn (default) logs a note and continues when postman-api-key and postman-access-token resolve to different parent orgs; enforce fails the run on that condition before any workspace is created. Both modes warn when postman-access-token is not a service-account token.",
|
|
135072
135121
|
required: false,
|
|
@@ -135624,6 +135673,12 @@ var MockContractError = class extends Error {
|
|
|
135624
135673
|
this.name = "MockContractError";
|
|
135625
135674
|
}
|
|
135626
135675
|
};
|
|
135676
|
+
var AmbiguousMonitorRebindError = class extends Error {
|
|
135677
|
+
constructor(message, options) {
|
|
135678
|
+
super(message, options);
|
|
135679
|
+
this.name = "AmbiguousMonitorRebindError";
|
|
135680
|
+
}
|
|
135681
|
+
};
|
|
135627
135682
|
function requireMockVisibility(mock, requested) {
|
|
135628
135683
|
if (mock.visibility === "unknown") {
|
|
135629
135684
|
throw new MockContractError(
|
|
@@ -136460,6 +136515,73 @@ var PostmanGatewayAssetsClient = class {
|
|
|
136460
136515
|
);
|
|
136461
136516
|
return match?.uid ? { uid: match.uid, name: match.name } : null;
|
|
136462
136517
|
}
|
|
136518
|
+
pickRebindMonitorCandidates(monitors, monitorName, environment) {
|
|
136519
|
+
const nameOnly = monitors.filter((monitor) => monitor.name === monitorName);
|
|
136520
|
+
const nameOnlyEnvironments = [
|
|
136521
|
+
...new Set(nameOnly.map((monitor) => monitor.environmentUid || "(none)"))
|
|
136522
|
+
];
|
|
136523
|
+
const candidates = nameOnly.filter((monitor) => monitor.environmentUid === environment);
|
|
136524
|
+
return { candidates, nameOnlyMatchCount: nameOnly.length, nameOnlyEnvironments };
|
|
136525
|
+
}
|
|
136526
|
+
/**
|
|
136527
|
+
* Rebind the sole same-name monitor in this workspace onto the current
|
|
136528
|
+
* collection UID.
|
|
136529
|
+
*
|
|
136530
|
+
* The canonical Smoke collection can legitimately change UID (a bootstrap
|
|
136531
|
+
* re-import after a marker/stranger miss, or an operator rebuild). The
|
|
136532
|
+
* monitor still points at the previous UID, so `findMonitorByCollection`
|
|
136533
|
+
* (which requires the full collection+environment+name triple) misses and a
|
|
136534
|
+
* second monitor with the same name would be created on every run, orphaning
|
|
136535
|
+
* the old one. Name plus environment is the stable identity across a
|
|
136536
|
+
* collection re-import, so recover it here instead.
|
|
136537
|
+
*
|
|
136538
|
+
* Returns null when nothing needs rebinding (no same-name monitor, or it is
|
|
136539
|
+
* already bound to this collection). Refuses to guess when several same-name
|
|
136540
|
+
* monitors match, matching `selectExactMatch` semantics elsewhere.
|
|
136541
|
+
*/
|
|
136542
|
+
async rebindMonitorByName(name, collectionUid, environmentUid) {
|
|
136543
|
+
const monitorName = String(name ?? "").trim();
|
|
136544
|
+
const collection = String(collectionUid ?? "").trim();
|
|
136545
|
+
const environment = String(environmentUid ?? "").trim();
|
|
136546
|
+
if (!monitorName || !collection) {
|
|
136547
|
+
return null;
|
|
136548
|
+
}
|
|
136549
|
+
const monitors = await this.listMonitors();
|
|
136550
|
+
const { candidates, nameOnlyMatchCount, nameOnlyEnvironments } = this.pickRebindMonitorCandidates(
|
|
136551
|
+
monitors,
|
|
136552
|
+
monitorName,
|
|
136553
|
+
environment
|
|
136554
|
+
);
|
|
136555
|
+
let match;
|
|
136556
|
+
try {
|
|
136557
|
+
match = this.selectExactMatch(
|
|
136558
|
+
"monitor",
|
|
136559
|
+
`workspace ${this.workspaceId}, name "${monitorName}", and environment ${environment || "(none)"}`,
|
|
136560
|
+
candidates
|
|
136561
|
+
);
|
|
136562
|
+
} catch (error) {
|
|
136563
|
+
const message = error instanceof Error && nameOnlyMatchCount > 0 ? `${error.message} Same-name monitor(s) also exist on environment(s): ${nameOnlyEnvironments.join(", ")}.` : error instanceof Error ? error.message : String(error);
|
|
136564
|
+
throw new AmbiguousMonitorRebindError(message, { cause: error });
|
|
136565
|
+
}
|
|
136566
|
+
if (!match?.uid) {
|
|
136567
|
+
if (nameOnlyMatchCount > 0) {
|
|
136568
|
+
}
|
|
136569
|
+
return null;
|
|
136570
|
+
}
|
|
136571
|
+
if (match.collectionUid === collection) {
|
|
136572
|
+
return null;
|
|
136573
|
+
}
|
|
136574
|
+
await this.gateway.requestJson(
|
|
136575
|
+
{
|
|
136576
|
+
service: "monitors",
|
|
136577
|
+
method: "put",
|
|
136578
|
+
path: `/jobTemplates/${this.toModelId(match.uid)}?_etc=true`,
|
|
136579
|
+
body: { collection }
|
|
136580
|
+
},
|
|
136581
|
+
{ retryTransient: false }
|
|
136582
|
+
);
|
|
136583
|
+
return { uid: match.uid, previousCollectionUid: match.collectionUid };
|
|
136584
|
+
}
|
|
136463
136585
|
async runMonitor(uid) {
|
|
136464
136586
|
await this.gateway.requestJson(
|
|
136465
136587
|
{
|
|
@@ -137577,6 +137699,7 @@ function resolveInputs(env = process.env) {
|
|
|
137577
137699
|
env
|
|
137578
137700
|
);
|
|
137579
137701
|
const environments = parseJsonArray(getInput("environments-json", env) || '["prod"]');
|
|
137702
|
+
const secretsResolverProvider = parseSecretsResolverProvider(getInput("secrets-resolver", env));
|
|
137580
137703
|
const systemEnvMap = parseJsonMap(getInput("system-env-map-json", env) || "{}");
|
|
137581
137704
|
const environmentUids = parseJsonMap(getInput("environment-uids-json", env) || "{}");
|
|
137582
137705
|
const envRuntimeUrls = parseJsonMap(getInput("env-runtime-urls-json", env) || "{}");
|
|
@@ -137594,6 +137717,7 @@ function resolveInputs(env = process.env) {
|
|
|
137594
137717
|
specContentChanged: parseBooleanInput(getInput("spec-content-changed", env), true),
|
|
137595
137718
|
specPath: getInput("spec-path", env),
|
|
137596
137719
|
collectionSyncMode: normalizeCollectionSyncMode(getInput("collection-sync-mode", env) || "refresh"),
|
|
137720
|
+
secretsResolverProvider,
|
|
137597
137721
|
specSyncMode: normalizeSpecSyncMode(getInput("spec-sync-mode", env) || "update"),
|
|
137598
137722
|
releaseLabel: normalizeReleaseLabel(getInput("release-label", env)) || void 0,
|
|
137599
137723
|
environments: environments.length > 0 ? environments : ["prod"],
|
|
@@ -137683,7 +137807,20 @@ function assertBranchAssetIds(inputs, decision, branchOwnedIds = process.env.POS
|
|
|
137683
137807
|
);
|
|
137684
137808
|
}
|
|
137685
137809
|
}
|
|
137810
|
+
var CREDENTIAL_SLOT_PREFIXES = ["AWS_", "AZURE_", "GCP_"];
|
|
137811
|
+
function isCredentialSlotKey(key) {
|
|
137812
|
+
return typeof key === "string" && CREDENTIAL_SLOT_PREFIXES.some((prefix) => key.startsWith(prefix));
|
|
137813
|
+
}
|
|
137686
137814
|
function buildEnvironmentValues(envName, baseUrl, options = {}) {
|
|
137815
|
+
const seeded = secretsResolverEnvironmentKeys(options.secretsResolverProvider ?? "none").map((entry) => ({
|
|
137816
|
+
key: entry.key,
|
|
137817
|
+
value: entry.key.endsWith("_SECRET_NAME") && !entry.secret ? `api-credentials-${envName}` : entry.defaultValue ?? "",
|
|
137818
|
+
type: entry.secret ? "secret" : "default"
|
|
137819
|
+
}));
|
|
137820
|
+
const seededKeys = new Set(seeded.map((entry) => entry.key));
|
|
137821
|
+
const preserved = (options.preservedCredentialValues ?? []).filter(
|
|
137822
|
+
(entry) => isCredentialSlotKey(entry.key) && !seededKeys.has(entry.key)
|
|
137823
|
+
);
|
|
137687
137824
|
return [
|
|
137688
137825
|
{ key: "baseUrl", value: baseUrl, type: "default" },
|
|
137689
137826
|
// A private mock refuses anonymous calls, so the manual-validation environment
|
|
@@ -137692,10 +137829,11 @@ function buildEnvironmentValues(envName, baseUrl, options = {}) {
|
|
|
137692
137829
|
...options.privateMockAuth ? [{ key: PRIVATE_MOCK_AUTH_VARIABLE2, value: "", type: "secret" }] : [],
|
|
137693
137830
|
{ key: "CI", value: "false", type: "default" },
|
|
137694
137831
|
{ key: "RESPONSE_TIME_THRESHOLD", value: "2000", type: "default" },
|
|
137695
|
-
|
|
137696
|
-
|
|
137697
|
-
|
|
137698
|
-
|
|
137832
|
+
// Provider-scoped credential slots. `none` (the default) seeds nothing, so a
|
|
137833
|
+
// consumer that does not use a cloud secret store gets a clean environment
|
|
137834
|
+
// instead of four dead AWS variables.
|
|
137835
|
+
...seeded,
|
|
137836
|
+
...preserved
|
|
137699
137837
|
];
|
|
137700
137838
|
}
|
|
137701
137839
|
var LEGACY_BASELINE_COLLECTION_PREFIX = "[Baseline]";
|
|
@@ -138039,19 +138177,28 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
|
|
|
138039
138177
|
}
|
|
138040
138178
|
if (existingUid) {
|
|
138041
138179
|
let marker = assetMarker;
|
|
138042
|
-
|
|
138043
|
-
|
|
138044
|
-
|
|
138045
|
-
|
|
138046
|
-
|
|
138180
|
+
let priorValues = [];
|
|
138181
|
+
try {
|
|
138182
|
+
const existing = await dependencies.postman.getEnvironment(existingUid);
|
|
138183
|
+
const values3 = existing?.data?.values ?? existing?.values ?? [];
|
|
138184
|
+
priorValues = values3.filter((value) => typeof value?.key === "string").map((value) => ({
|
|
138185
|
+
key: value.key,
|
|
138186
|
+
value: typeof value.value === "string" ? value.value : "",
|
|
138187
|
+
type: value.type === "secret" ? "secret" : "default"
|
|
138188
|
+
}));
|
|
138189
|
+
if (marker) {
|
|
138190
|
+
const prior = priorValues.find((value) => value.key === "x-pm-onboarding")?.value;
|
|
138047
138191
|
const priorMarker = parseAssetMarker(prior ? `x-pm-onboarding: ${prior}` : void 0);
|
|
138048
138192
|
if (priorMarker?.repo === marker.repo && priorMarker.rawBranch === marker.rawBranch) {
|
|
138049
138193
|
marker = { ...marker, createdAt: priorMarker.createdAt };
|
|
138050
138194
|
}
|
|
138051
|
-
} catch {
|
|
138052
138195
|
}
|
|
138196
|
+
} catch {
|
|
138053
138197
|
}
|
|
138054
|
-
const values2 = buildEnvironmentValues(envName, runtimeUrl
|
|
138198
|
+
const values2 = buildEnvironmentValues(envName, runtimeUrl, {
|
|
138199
|
+
secretsResolverProvider: inputs.secretsResolverProvider,
|
|
138200
|
+
preservedCredentialValues: priorValues
|
|
138201
|
+
});
|
|
138055
138202
|
if (marker) values2.push({ key: "x-pm-onboarding", value: JSON.stringify(marker), type: "default" });
|
|
138056
138203
|
try {
|
|
138057
138204
|
await dependencies.postman.updateEnvironment(existingUid, displayName, values2);
|
|
@@ -138071,7 +138218,9 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
|
|
|
138071
138218
|
dependencies.core.setOutput("environment-uids-json", JSON.stringify(envUids));
|
|
138072
138219
|
continue;
|
|
138073
138220
|
}
|
|
138074
|
-
const values = buildEnvironmentValues(envName, runtimeUrl
|
|
138221
|
+
const values = buildEnvironmentValues(envName, runtimeUrl, {
|
|
138222
|
+
secretsResolverProvider: inputs.secretsResolverProvider
|
|
138223
|
+
});
|
|
138075
138224
|
if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
|
|
138076
138225
|
try {
|
|
138077
138226
|
envUids[envName] = await dependencies.postman.createEnvironment(
|
|
@@ -138100,7 +138249,10 @@ async function upsertMockEnvironment(inputs, dependencies, assetProjectName, moc
|
|
|
138100
138249
|
return "";
|
|
138101
138250
|
}
|
|
138102
138251
|
const displayName = `${assetProjectName} - Mock`;
|
|
138103
|
-
const values = buildEnvironmentValues("mock", mockUrl, {
|
|
138252
|
+
const values = buildEnvironmentValues("mock", mockUrl, {
|
|
138253
|
+
privateMockAuth,
|
|
138254
|
+
secretsResolverProvider: inputs.secretsResolverProvider
|
|
138255
|
+
});
|
|
138104
138256
|
const mask = resolveRepoSyncMasker(dependencies);
|
|
138105
138257
|
try {
|
|
138106
138258
|
const discovered = await dependencies.postman.findEnvironmentByName(
|
|
@@ -139371,6 +139523,43 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
139371
139523
|
);
|
|
139372
139524
|
}
|
|
139373
139525
|
}
|
|
139526
|
+
if (!resolvedMonitorId && inputs.smokeCollectionId && dependencies.postman.rebindMonitorByName) {
|
|
139527
|
+
try {
|
|
139528
|
+
const rebound = await dependencies.postman.rebindMonitorByName(
|
|
139529
|
+
monitorName,
|
|
139530
|
+
inputs.smokeCollectionId,
|
|
139531
|
+
monitorEnvUid
|
|
139532
|
+
);
|
|
139533
|
+
if (rebound) {
|
|
139534
|
+
resolvedMonitorId = rebound.uid;
|
|
139535
|
+
dependencies.core.info(
|
|
139536
|
+
`Rebound existing monitor ${rebound.uid} ("${monitorName}") from collection ${rebound.previousCollectionUid} to ${inputs.smokeCollectionId}`
|
|
139537
|
+
);
|
|
139538
|
+
}
|
|
139539
|
+
} catch (error) {
|
|
139540
|
+
if (error instanceof AmbiguousMonitorRebindError) {
|
|
139541
|
+
throw new Error(
|
|
139542
|
+
formatOrchestrationIssue({
|
|
139543
|
+
operation: "Monitor rebind",
|
|
139544
|
+
entity: `monitor "${monitorName}" workspace ${inputs.workspaceId} collection ${inputs.smokeCollectionId} environment ${monitorEnvUid}`,
|
|
139545
|
+
cause: error,
|
|
139546
|
+
remediation: monitorRemediation,
|
|
139547
|
+
mask
|
|
139548
|
+
}),
|
|
139549
|
+
{ cause: error }
|
|
139550
|
+
);
|
|
139551
|
+
}
|
|
139552
|
+
dependencies.core.warning(
|
|
139553
|
+
formatOrchestrationIssue({
|
|
139554
|
+
operation: "Monitor rebind",
|
|
139555
|
+
entity: `monitor "${monitorName}" workspace ${inputs.workspaceId} collection ${inputs.smokeCollectionId} environment ${monitorEnvUid}`,
|
|
139556
|
+
cause: error,
|
|
139557
|
+
remediation: monitorRemediation,
|
|
139558
|
+
mask
|
|
139559
|
+
})
|
|
139560
|
+
);
|
|
139561
|
+
}
|
|
139562
|
+
}
|
|
139374
139563
|
if (!resolvedMonitorId) {
|
|
139375
139564
|
try {
|
|
139376
139565
|
resolvedMonitorId = await dependencies.postman.createMonitor(
|
|
@@ -139792,6 +139981,7 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
|
|
|
139792
139981
|
listMonitors: gatewayAssets.listMonitors.bind(gatewayAssets),
|
|
139793
139982
|
monitorExists: gatewayAssets.monitorExists.bind(gatewayAssets),
|
|
139794
139983
|
findMonitorByCollection: gatewayAssets.findMonitorByCollection.bind(gatewayAssets),
|
|
139984
|
+
rebindMonitorByName: gatewayAssets.rebindMonitorByName.bind(gatewayAssets),
|
|
139795
139985
|
runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets),
|
|
139796
139986
|
listEnvironments: gatewayAssets.listEnvironments.bind(gatewayAssets),
|
|
139797
139987
|
// GC path (preview/channel retention machine — lib/repo/preview-gc.ts).
|
|
@@ -140292,6 +140482,7 @@ var CLI_INPUT_NAMES = [
|
|
|
140292
140482
|
"spec-content-changed",
|
|
140293
140483
|
"spec-path",
|
|
140294
140484
|
"team-id",
|
|
140485
|
+
"secrets-resolver",
|
|
140295
140486
|
"postman-region",
|
|
140296
140487
|
"postman-stack",
|
|
140297
140488
|
"branch-strategy",
|
package/dist/index.cjs
CHANGED
|
@@ -135782,6 +135782,49 @@ function createLogger(options) {
|
|
|
135782
135782
|
return root;
|
|
135783
135783
|
}
|
|
135784
135784
|
|
|
135785
|
+
// node_modules/@postman-cse/automation-core/dist/secrets-resolver.js
|
|
135786
|
+
var SECRETS_RESOLVER_PROVIDERS = ["none", "aws", "azure", "gcp"];
|
|
135787
|
+
var DEFAULT_SECRETS_RESOLVER_PROVIDER = "none";
|
|
135788
|
+
function parseSecretsResolverProvider(value, fallback = DEFAULT_SECRETS_RESOLVER_PROVIDER) {
|
|
135789
|
+
const raw = String(value ?? "").trim().toLowerCase();
|
|
135790
|
+
if (!raw)
|
|
135791
|
+
return fallback;
|
|
135792
|
+
if (raw === "true")
|
|
135793
|
+
return "aws";
|
|
135794
|
+
if (raw === "false" || raw === "off")
|
|
135795
|
+
return "none";
|
|
135796
|
+
if (SECRETS_RESOLVER_PROVIDERS.includes(raw)) {
|
|
135797
|
+
return raw;
|
|
135798
|
+
}
|
|
135799
|
+
throw new Error(`SECRETS_RESOLVER_PROVIDER_INVALID: expected one of ${SECRETS_RESOLVER_PROVIDERS.join(", ")} (or legacy true/false), received "${value}"`);
|
|
135800
|
+
}
|
|
135801
|
+
function secretsResolverEnvironmentKeys(provider) {
|
|
135802
|
+
switch (provider) {
|
|
135803
|
+
case "aws":
|
|
135804
|
+
return [
|
|
135805
|
+
{ key: "AWS_ACCESS_KEY_ID", secret: true },
|
|
135806
|
+
{ key: "AWS_SECRET_ACCESS_KEY", secret: true },
|
|
135807
|
+
{ key: "AWS_REGION", secret: false },
|
|
135808
|
+
{ key: "AWS_SECRET_NAME", secret: false }
|
|
135809
|
+
];
|
|
135810
|
+
case "azure":
|
|
135811
|
+
return [
|
|
135812
|
+
{ key: "AZURE_KEY_VAULT_NAME", secret: false },
|
|
135813
|
+
{ key: "AZURE_SECRET_NAME", secret: false },
|
|
135814
|
+
{ key: "AZURE_ACCESS_TOKEN", secret: true }
|
|
135815
|
+
];
|
|
135816
|
+
case "gcp":
|
|
135817
|
+
return [
|
|
135818
|
+
{ key: "GCP_PROJECT_ID", secret: false },
|
|
135819
|
+
{ key: "GCP_SECRET_NAME", secret: false },
|
|
135820
|
+
{ key: "GCP_ACCESS_TOKEN", secret: true }
|
|
135821
|
+
];
|
|
135822
|
+
case "none":
|
|
135823
|
+
default:
|
|
135824
|
+
return [];
|
|
135825
|
+
}
|
|
135826
|
+
}
|
|
135827
|
+
|
|
135785
135828
|
// src/action-version.ts
|
|
135786
135829
|
var import_node_fs3 = require("node:fs");
|
|
135787
135830
|
var import_node_path2 = require("node:path");
|
|
@@ -136985,6 +137028,12 @@ var postmanRepoSyncActionContract = {
|
|
|
136985
137028
|
required: false,
|
|
136986
137029
|
default: ""
|
|
136987
137030
|
},
|
|
137031
|
+
"secrets-resolver": {
|
|
137032
|
+
description: "Cloud secret store the generated environments seed credential slots for (none, aws, azure, gcp). Must match the value passed to bootstrap and smoke-flow.",
|
|
137033
|
+
required: false,
|
|
137034
|
+
default: "none",
|
|
137035
|
+
allowedValues: ["none", "aws", "azure", "gcp"]
|
|
137036
|
+
},
|
|
136988
137037
|
"credential-preflight": {
|
|
136989
137038
|
description: "Credential identity preflight policy. warn (default) logs a note and continues when postman-api-key and postman-access-token resolve to different parent orgs; enforce fails the run on that condition before any workspace is created. Both modes warn when postman-access-token is not a service-account token.",
|
|
136990
137039
|
required: false,
|
|
@@ -137542,6 +137591,12 @@ var MockContractError = class extends Error {
|
|
|
137542
137591
|
this.name = "MockContractError";
|
|
137543
137592
|
}
|
|
137544
137593
|
};
|
|
137594
|
+
var AmbiguousMonitorRebindError = class extends Error {
|
|
137595
|
+
constructor(message, options) {
|
|
137596
|
+
super(message, options);
|
|
137597
|
+
this.name = "AmbiguousMonitorRebindError";
|
|
137598
|
+
}
|
|
137599
|
+
};
|
|
137545
137600
|
function requireMockVisibility(mock, requested) {
|
|
137546
137601
|
if (mock.visibility === "unknown") {
|
|
137547
137602
|
throw new MockContractError(
|
|
@@ -138378,6 +138433,73 @@ var PostmanGatewayAssetsClient = class {
|
|
|
138378
138433
|
);
|
|
138379
138434
|
return match?.uid ? { uid: match.uid, name: match.name } : null;
|
|
138380
138435
|
}
|
|
138436
|
+
pickRebindMonitorCandidates(monitors, monitorName, environment) {
|
|
138437
|
+
const nameOnly = monitors.filter((monitor) => monitor.name === monitorName);
|
|
138438
|
+
const nameOnlyEnvironments = [
|
|
138439
|
+
...new Set(nameOnly.map((monitor) => monitor.environmentUid || "(none)"))
|
|
138440
|
+
];
|
|
138441
|
+
const candidates = nameOnly.filter((monitor) => monitor.environmentUid === environment);
|
|
138442
|
+
return { candidates, nameOnlyMatchCount: nameOnly.length, nameOnlyEnvironments };
|
|
138443
|
+
}
|
|
138444
|
+
/**
|
|
138445
|
+
* Rebind the sole same-name monitor in this workspace onto the current
|
|
138446
|
+
* collection UID.
|
|
138447
|
+
*
|
|
138448
|
+
* The canonical Smoke collection can legitimately change UID (a bootstrap
|
|
138449
|
+
* re-import after a marker/stranger miss, or an operator rebuild). The
|
|
138450
|
+
* monitor still points at the previous UID, so `findMonitorByCollection`
|
|
138451
|
+
* (which requires the full collection+environment+name triple) misses and a
|
|
138452
|
+
* second monitor with the same name would be created on every run, orphaning
|
|
138453
|
+
* the old one. Name plus environment is the stable identity across a
|
|
138454
|
+
* collection re-import, so recover it here instead.
|
|
138455
|
+
*
|
|
138456
|
+
* Returns null when nothing needs rebinding (no same-name monitor, or it is
|
|
138457
|
+
* already bound to this collection). Refuses to guess when several same-name
|
|
138458
|
+
* monitors match, matching `selectExactMatch` semantics elsewhere.
|
|
138459
|
+
*/
|
|
138460
|
+
async rebindMonitorByName(name, collectionUid, environmentUid) {
|
|
138461
|
+
const monitorName = String(name ?? "").trim();
|
|
138462
|
+
const collection = String(collectionUid ?? "").trim();
|
|
138463
|
+
const environment = String(environmentUid ?? "").trim();
|
|
138464
|
+
if (!monitorName || !collection) {
|
|
138465
|
+
return null;
|
|
138466
|
+
}
|
|
138467
|
+
const monitors = await this.listMonitors();
|
|
138468
|
+
const { candidates, nameOnlyMatchCount, nameOnlyEnvironments } = this.pickRebindMonitorCandidates(
|
|
138469
|
+
monitors,
|
|
138470
|
+
monitorName,
|
|
138471
|
+
environment
|
|
138472
|
+
);
|
|
138473
|
+
let match;
|
|
138474
|
+
try {
|
|
138475
|
+
match = this.selectExactMatch(
|
|
138476
|
+
"monitor",
|
|
138477
|
+
`workspace ${this.workspaceId}, name "${monitorName}", and environment ${environment || "(none)"}`,
|
|
138478
|
+
candidates
|
|
138479
|
+
);
|
|
138480
|
+
} catch (error2) {
|
|
138481
|
+
const message = error2 instanceof Error && nameOnlyMatchCount > 0 ? `${error2.message} Same-name monitor(s) also exist on environment(s): ${nameOnlyEnvironments.join(", ")}.` : error2 instanceof Error ? error2.message : String(error2);
|
|
138482
|
+
throw new AmbiguousMonitorRebindError(message, { cause: error2 });
|
|
138483
|
+
}
|
|
138484
|
+
if (!match?.uid) {
|
|
138485
|
+
if (nameOnlyMatchCount > 0) {
|
|
138486
|
+
}
|
|
138487
|
+
return null;
|
|
138488
|
+
}
|
|
138489
|
+
if (match.collectionUid === collection) {
|
|
138490
|
+
return null;
|
|
138491
|
+
}
|
|
138492
|
+
await this.gateway.requestJson(
|
|
138493
|
+
{
|
|
138494
|
+
service: "monitors",
|
|
138495
|
+
method: "put",
|
|
138496
|
+
path: `/jobTemplates/${this.toModelId(match.uid)}?_etc=true`,
|
|
138497
|
+
body: { collection }
|
|
138498
|
+
},
|
|
138499
|
+
{ retryTransient: false }
|
|
138500
|
+
);
|
|
138501
|
+
return { uid: match.uid, previousCollectionUid: match.collectionUid };
|
|
138502
|
+
}
|
|
138381
138503
|
async runMonitor(uid) {
|
|
138382
138504
|
await this.gateway.requestJson(
|
|
138383
138505
|
{
|
|
@@ -139550,6 +139672,7 @@ function resolveInputs(env = process.env) {
|
|
|
139550
139672
|
env
|
|
139551
139673
|
);
|
|
139552
139674
|
const environments = parseJsonArray(getInput2("environments-json", env) || '["prod"]');
|
|
139675
|
+
const secretsResolverProvider = parseSecretsResolverProvider(getInput2("secrets-resolver", env));
|
|
139553
139676
|
const systemEnvMap = parseJsonMap(getInput2("system-env-map-json", env) || "{}");
|
|
139554
139677
|
const environmentUids = parseJsonMap(getInput2("environment-uids-json", env) || "{}");
|
|
139555
139678
|
const envRuntimeUrls = parseJsonMap(getInput2("env-runtime-urls-json", env) || "{}");
|
|
@@ -139567,6 +139690,7 @@ function resolveInputs(env = process.env) {
|
|
|
139567
139690
|
specContentChanged: parseBooleanInput(getInput2("spec-content-changed", env), true),
|
|
139568
139691
|
specPath: getInput2("spec-path", env),
|
|
139569
139692
|
collectionSyncMode: normalizeCollectionSyncMode(getInput2("collection-sync-mode", env) || "refresh"),
|
|
139693
|
+
secretsResolverProvider,
|
|
139570
139694
|
specSyncMode: normalizeSpecSyncMode(getInput2("spec-sync-mode", env) || "update"),
|
|
139571
139695
|
releaseLabel: normalizeReleaseLabel(getInput2("release-label", env)) || void 0,
|
|
139572
139696
|
environments: environments.length > 0 ? environments : ["prod"],
|
|
@@ -139656,7 +139780,20 @@ function assertBranchAssetIds(inputs, decision, branchOwnedIds = process.env.POS
|
|
|
139656
139780
|
);
|
|
139657
139781
|
}
|
|
139658
139782
|
}
|
|
139783
|
+
var CREDENTIAL_SLOT_PREFIXES = ["AWS_", "AZURE_", "GCP_"];
|
|
139784
|
+
function isCredentialSlotKey(key) {
|
|
139785
|
+
return typeof key === "string" && CREDENTIAL_SLOT_PREFIXES.some((prefix) => key.startsWith(prefix));
|
|
139786
|
+
}
|
|
139659
139787
|
function buildEnvironmentValues(envName, baseUrl, options = {}) {
|
|
139788
|
+
const seeded = secretsResolverEnvironmentKeys(options.secretsResolverProvider ?? "none").map((entry) => ({
|
|
139789
|
+
key: entry.key,
|
|
139790
|
+
value: entry.key.endsWith("_SECRET_NAME") && !entry.secret ? `api-credentials-${envName}` : entry.defaultValue ?? "",
|
|
139791
|
+
type: entry.secret ? "secret" : "default"
|
|
139792
|
+
}));
|
|
139793
|
+
const seededKeys = new Set(seeded.map((entry) => entry.key));
|
|
139794
|
+
const preserved = (options.preservedCredentialValues ?? []).filter(
|
|
139795
|
+
(entry) => isCredentialSlotKey(entry.key) && !seededKeys.has(entry.key)
|
|
139796
|
+
);
|
|
139660
139797
|
return [
|
|
139661
139798
|
{ key: "baseUrl", value: baseUrl, type: "default" },
|
|
139662
139799
|
// A private mock refuses anonymous calls, so the manual-validation environment
|
|
@@ -139665,10 +139802,11 @@ function buildEnvironmentValues(envName, baseUrl, options = {}) {
|
|
|
139665
139802
|
...options.privateMockAuth ? [{ key: PRIVATE_MOCK_AUTH_VARIABLE2, value: "", type: "secret" }] : [],
|
|
139666
139803
|
{ key: "CI", value: "false", type: "default" },
|
|
139667
139804
|
{ key: "RESPONSE_TIME_THRESHOLD", value: "2000", type: "default" },
|
|
139668
|
-
|
|
139669
|
-
|
|
139670
|
-
|
|
139671
|
-
|
|
139805
|
+
// Provider-scoped credential slots. `none` (the default) seeds nothing, so a
|
|
139806
|
+
// consumer that does not use a cloud secret store gets a clean environment
|
|
139807
|
+
// instead of four dead AWS variables.
|
|
139808
|
+
...seeded,
|
|
139809
|
+
...preserved
|
|
139672
139810
|
];
|
|
139673
139811
|
}
|
|
139674
139812
|
var LEGACY_BASELINE_COLLECTION_PREFIX = "[Baseline]";
|
|
@@ -140154,19 +140292,28 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
|
|
|
140154
140292
|
}
|
|
140155
140293
|
if (existingUid) {
|
|
140156
140294
|
let marker = assetMarker;
|
|
140157
|
-
|
|
140158
|
-
|
|
140159
|
-
|
|
140160
|
-
|
|
140161
|
-
|
|
140295
|
+
let priorValues = [];
|
|
140296
|
+
try {
|
|
140297
|
+
const existing = await dependencies.postman.getEnvironment(existingUid);
|
|
140298
|
+
const values3 = existing?.data?.values ?? existing?.values ?? [];
|
|
140299
|
+
priorValues = values3.filter((value) => typeof value?.key === "string").map((value) => ({
|
|
140300
|
+
key: value.key,
|
|
140301
|
+
value: typeof value.value === "string" ? value.value : "",
|
|
140302
|
+
type: value.type === "secret" ? "secret" : "default"
|
|
140303
|
+
}));
|
|
140304
|
+
if (marker) {
|
|
140305
|
+
const prior = priorValues.find((value) => value.key === "x-pm-onboarding")?.value;
|
|
140162
140306
|
const priorMarker = parseAssetMarker(prior ? `x-pm-onboarding: ${prior}` : void 0);
|
|
140163
140307
|
if (priorMarker?.repo === marker.repo && priorMarker.rawBranch === marker.rawBranch) {
|
|
140164
140308
|
marker = { ...marker, createdAt: priorMarker.createdAt };
|
|
140165
140309
|
}
|
|
140166
|
-
} catch {
|
|
140167
140310
|
}
|
|
140311
|
+
} catch {
|
|
140168
140312
|
}
|
|
140169
|
-
const values2 = buildEnvironmentValues(envName, runtimeUrl
|
|
140313
|
+
const values2 = buildEnvironmentValues(envName, runtimeUrl, {
|
|
140314
|
+
secretsResolverProvider: inputs.secretsResolverProvider,
|
|
140315
|
+
preservedCredentialValues: priorValues
|
|
140316
|
+
});
|
|
140170
140317
|
if (marker) values2.push({ key: "x-pm-onboarding", value: JSON.stringify(marker), type: "default" });
|
|
140171
140318
|
try {
|
|
140172
140319
|
await dependencies.postman.updateEnvironment(existingUid, displayName, values2);
|
|
@@ -140186,7 +140333,9 @@ async function upsertEnvironments(inputs, dependencies, resourcesState, assetMar
|
|
|
140186
140333
|
dependencies.core.setOutput("environment-uids-json", JSON.stringify(envUids));
|
|
140187
140334
|
continue;
|
|
140188
140335
|
}
|
|
140189
|
-
const values = buildEnvironmentValues(envName, runtimeUrl
|
|
140336
|
+
const values = buildEnvironmentValues(envName, runtimeUrl, {
|
|
140337
|
+
secretsResolverProvider: inputs.secretsResolverProvider
|
|
140338
|
+
});
|
|
140190
140339
|
if (assetMarker) values.push({ key: "x-pm-onboarding", value: JSON.stringify(assetMarker), type: "default" });
|
|
140191
140340
|
try {
|
|
140192
140341
|
envUids[envName] = await dependencies.postman.createEnvironment(
|
|
@@ -140215,7 +140364,10 @@ async function upsertMockEnvironment(inputs, dependencies, assetProjectName, moc
|
|
|
140215
140364
|
return "";
|
|
140216
140365
|
}
|
|
140217
140366
|
const displayName = `${assetProjectName} - Mock`;
|
|
140218
|
-
const values = buildEnvironmentValues("mock", mockUrl, {
|
|
140367
|
+
const values = buildEnvironmentValues("mock", mockUrl, {
|
|
140368
|
+
privateMockAuth,
|
|
140369
|
+
secretsResolverProvider: inputs.secretsResolverProvider
|
|
140370
|
+
});
|
|
140219
140371
|
const mask = resolveRepoSyncMasker(dependencies);
|
|
140220
140372
|
try {
|
|
140221
140373
|
const discovered = await dependencies.postman.findEnvironmentByName(
|
|
@@ -141486,6 +141638,43 @@ async function runRepoSyncInner(inputs, dependencies) {
|
|
|
141486
141638
|
);
|
|
141487
141639
|
}
|
|
141488
141640
|
}
|
|
141641
|
+
if (!resolvedMonitorId && inputs.smokeCollectionId && dependencies.postman.rebindMonitorByName) {
|
|
141642
|
+
try {
|
|
141643
|
+
const rebound = await dependencies.postman.rebindMonitorByName(
|
|
141644
|
+
monitorName,
|
|
141645
|
+
inputs.smokeCollectionId,
|
|
141646
|
+
monitorEnvUid
|
|
141647
|
+
);
|
|
141648
|
+
if (rebound) {
|
|
141649
|
+
resolvedMonitorId = rebound.uid;
|
|
141650
|
+
dependencies.core.info(
|
|
141651
|
+
`Rebound existing monitor ${rebound.uid} ("${monitorName}") from collection ${rebound.previousCollectionUid} to ${inputs.smokeCollectionId}`
|
|
141652
|
+
);
|
|
141653
|
+
}
|
|
141654
|
+
} catch (error2) {
|
|
141655
|
+
if (error2 instanceof AmbiguousMonitorRebindError) {
|
|
141656
|
+
throw new Error(
|
|
141657
|
+
formatOrchestrationIssue({
|
|
141658
|
+
operation: "Monitor rebind",
|
|
141659
|
+
entity: `monitor "${monitorName}" workspace ${inputs.workspaceId} collection ${inputs.smokeCollectionId} environment ${monitorEnvUid}`,
|
|
141660
|
+
cause: error2,
|
|
141661
|
+
remediation: monitorRemediation,
|
|
141662
|
+
mask
|
|
141663
|
+
}),
|
|
141664
|
+
{ cause: error2 }
|
|
141665
|
+
);
|
|
141666
|
+
}
|
|
141667
|
+
dependencies.core.warning(
|
|
141668
|
+
formatOrchestrationIssue({
|
|
141669
|
+
operation: "Monitor rebind",
|
|
141670
|
+
entity: `monitor "${monitorName}" workspace ${inputs.workspaceId} collection ${inputs.smokeCollectionId} environment ${monitorEnvUid}`,
|
|
141671
|
+
cause: error2,
|
|
141672
|
+
remediation: monitorRemediation,
|
|
141673
|
+
mask
|
|
141674
|
+
})
|
|
141675
|
+
);
|
|
141676
|
+
}
|
|
141677
|
+
}
|
|
141489
141678
|
if (!resolvedMonitorId) {
|
|
141490
141679
|
try {
|
|
141491
141680
|
resolvedMonitorId = await dependencies.postman.createMonitor(
|
|
@@ -141907,6 +142096,7 @@ function createRepoSyncDependencies(inputs, resolved, factories, options = {}) {
|
|
|
141907
142096
|
listMonitors: gatewayAssets.listMonitors.bind(gatewayAssets),
|
|
141908
142097
|
monitorExists: gatewayAssets.monitorExists.bind(gatewayAssets),
|
|
141909
142098
|
findMonitorByCollection: gatewayAssets.findMonitorByCollection.bind(gatewayAssets),
|
|
142099
|
+
rebindMonitorByName: gatewayAssets.rebindMonitorByName.bind(gatewayAssets),
|
|
141910
142100
|
runMonitor: gatewayAssets.runMonitor.bind(gatewayAssets),
|
|
141911
142101
|
listEnvironments: gatewayAssets.listEnvironments.bind(gatewayAssets),
|
|
141912
142102
|
// GC path (preview/channel retention machine — lib/repo/preview-gc.ts).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postman-cse/onboarding-repo-sync",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "Postman repo sync GitHub Action.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@actions/core": "^3.0.1",
|
|
46
46
|
"@actions/exec": "^3.0.0",
|
|
47
|
-
"@postman-cse/automation-core": "^1.1
|
|
47
|
+
"@postman-cse/automation-core": "^1.2.1",
|
|
48
48
|
"@postman/runtime.models": "^1.13.1",
|
|
49
49
|
"@postman/v3.export": "0.2.28",
|
|
50
50
|
"js-yaml": "^5.2.1",
|