@tailor-platform/sdk 1.73.3 → 1.74.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/CHANGELOG.md +44 -0
- package/dist/{application-DgTCDMZY.mjs → application-BsH6tkZC.mjs} +96 -17
- package/dist/application-BsH6tkZC.mjs.map +1 -0
- package/dist/application-CSUhZMb5.mjs +3 -0
- package/dist/cli/commands/deploy/apply-phases.d.mts +1 -0
- package/dist/cli/commands/executor/trigger.d.mts +1 -1
- package/dist/cli/commands/tailordb/migrate/snapshot.d.mts +1 -16
- package/dist/cli/index.mjs +121 -43
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +4 -4
- package/dist/cli/lib.mjs +3 -3
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/shared/client.d.mts +1 -1
- package/dist/completion/zsh-worker.zsh +28 -3
- package/dist/configure/index.mjs +2 -2
- package/dist/configure/services/tailordb/schema.d.mts +5 -19
- package/dist/configure/types/field-runtime.d.mts +17 -0
- package/dist/configure/types/type.d.mts +1 -18
- package/dist/field-runtime-9bfXBAOK.mjs +183 -0
- package/dist/field-runtime-9bfXBAOK.mjs.map +1 -0
- package/dist/plugin/builtin/seed/index.mjs +1 -1
- package/dist/{repl-editor-BmQOyZJd.mjs → repl-editor-BCozyiNq.mjs} +3 -2
- package/dist/{repl-editor-BmQOyZJd.mjs.map → repl-editor-BCozyiNq.mjs.map} +1 -1
- package/dist/{runtime-SxHHEzVb.mjs → runtime-DV1EnfMs.mjs} +2130 -1538
- package/dist/runtime-DV1EnfMs.mjs.map +1 -0
- package/dist/{schema-DAdZSFOd.mjs → schema-Dl_y0m9e.mjs} +8 -167
- package/dist/schema-Dl_y0m9e.mjs.map +1 -0
- package/dist/secretmanager-Bd45j7an.mjs +98 -0
- package/dist/secretmanager-Bd45j7an.mjs.map +1 -0
- package/dist/{seed-DoMTMLcm.mjs → seed-fm0LeYP2.mjs} +14 -3
- package/dist/seed-fm0LeYP2.mjs.map +1 -0
- package/dist/{service-DiEtAsvc.mjs → service-BCRJ-3NV.mjs} +18 -1
- package/dist/{service-DiEtAsvc.mjs.map → service-BCRJ-3NV.mjs.map} +1 -1
- package/dist/service-CPMlIg3X.mjs +3 -0
- package/dist/tailor-proto/src/tailor/v1/aigateway_pb.d.mts +2 -2
- package/dist/tailor-proto/src/tailor/v1/aigateway_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/application_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/application_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/auth_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/auth_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/events_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/events_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/executor_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/executor_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/function_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/function_registry_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/function_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/http_adapter_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/idp_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/idp_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/metadata_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/metadata_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/meter_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/pipeline_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/pipeline_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/secret_manager_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/secret_manager_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/service_pb.d.mts +2 -2
- package/dist/tailor-proto/src/tailor/v1/stateflow_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/stateflow_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/staticwebsite_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/staticwebsite_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/tailordb_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/tailordb_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/telemetryrouter_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/telemetryrouter_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/workflow_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/workflow_resource_pb.d.mts +1 -1
- package/dist/tailor-proto/src/tailor/v1/workspace_pb.d.mts +2 -2
- package/dist/tailor-proto/src/tailor/v1/workspace_resource_pb.d.mts +1 -1
- package/dist/types-BwzB2okW.mjs +199 -0
- package/dist/types-BwzB2okW.mjs.map +1 -0
- package/dist/types-KlawuVKa.mjs +4 -0
- package/dist/vitest/index.d.mts +8 -1
- package/dist/vitest/index.mjs +778 -2
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mock.d.mts +8 -363
- package/dist/vitest/mocks/aigateway.d.mts +30 -0
- package/dist/vitest/mocks/authconnection.d.mts +28 -0
- package/dist/vitest/mocks/file.d.mts +41 -0
- package/dist/vitest/mocks/iconv.d.mts +27 -0
- package/dist/vitest/mocks/idp.d.mts +44 -0
- package/dist/vitest/mocks/secretmanager.d.mts +33 -0
- package/dist/vitest/mocks/tailordb.d.mts +71 -0
- package/dist/vitest/mocks/workflow.d.mts +115 -0
- package/dist/vitest/setup.mjs +1 -1
- package/docs/cli/application.md +21 -11
- package/docs/cli/setup.md +25 -0
- package/docs/cli-reference.md +1 -0
- package/package.json +15 -15
- package/dist/application-BAo_zFA4.mjs +0 -3
- package/dist/application-DgTCDMZY.mjs.map +0 -1
- package/dist/field-DOsJCPFa.mjs +0 -22
- package/dist/field-DOsJCPFa.mjs.map +0 -1
- package/dist/mock-Ce1GCi4-.mjs +0 -871
- package/dist/mock-Ce1GCi4-.mjs.map +0 -1
- package/dist/runtime-SxHHEzVb.mjs.map +0 -1
- package/dist/schema-DAdZSFOd.mjs.map +0 -1
- package/dist/seed-DoMTMLcm.mjs.map +0 -1
- package/dist/service-BXmdFwSH.mjs +0 -3
- package/dist/types-qhFZ8y9B.mjs +0 -4
- package/dist/types-ywCGpzDZ.mjs +0 -360
- package/dist/types-ywCGpzDZ.mjs.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { t as db } from "./schema-
|
|
1
|
+
import { t as db } from "./schema-Dl_y0m9e.mjs";
|
|
2
2
|
import { $ as CreateUserProfileConfigRequestSchema, A as UpdatePipelineServiceRequestSchema, At as Condition_Operator, B as UpdateExecutorExecutorRequestSchema, Ct as UserProfileProviderConfig_UserProfileProviderType, D as CreatePipelineResolverRequestSchema, Dt as ApplicationSchemaUpdateAttemptStatus, E as UpdateSecretManagerSecretRequestSchema, Et as UpdateApplicationRequestSchema, F as IdPPermissionOperator, G as CreateAuthHookRequestSchema, H as ExecutorTargetType, I as IdPPermissionPermit, J as CreateAuthOAuth2ClientRequestSchema, K as CreateAuthIDPConfigRequestSchema, L as FunctionExecution_Status, M as CreateIdPServiceRequestSchema, Mt as PageDirection, N as UpdateIdPServiceRequestSchema, O as CreatePipelineServiceRequestSchema, Ot as Subgraph_ServiceType, P as IdPLang, Q as CreateTenantConfigRequestSchema, S as UpdateStaticWebsiteRequestSchema, St as TenantProviderConfig_TenantProviderType, T as CreateSecretManagerVaultRequestSchema, Tt as GetApplicationSchemaHealthResponse_ApplicationSchemaHealthStatus, U as ExecutorTriggerType, V as ExecutorJobStatus, W as CreateAuthConnectionRequestSchema, X as CreateAuthSCIMResourceRequestSchema, Y as CreateAuthSCIMConfigRequestSchema, Z as CreateAuthServiceRequestSchema, _ as TailorDBType_Permission_Operator, _t as AuthSCIMAttribute_Mutability, a as WorkspacePlatformUserRole, at as UpdateAuthSCIMConfigRequestSchema, b as AddCustomDomainRequestSchema, bt as AuthSCIMConfig_AuthorizationType, c as UpdateWorkflowRequestSchema, ct as UpdateTenantConfigRequestSchema, d as CreateTailorDBServiceRequestSchema, dt as AuthConnection_Type, et as UpdateAuthConnectionRequestSchema, f as CreateTailorDBTypeRequestSchema, ft as AuthHookPoint, g as TailorDBGQLPermission_Permit, gt as AuthOAuth2Client_GrantType, h as TailorDBGQLPermission_Operator, ht as AuthOAuth2Client_ClientType, it as UpdateAuthOAuth2ClientRequestSchema, j as PipelineResolver_OperationType, jt as FilterSchema, k as UpdatePipelineResolverRequestSchema, kt as ConditionSchema, l as WorkflowExecution_Status, lt as UpdateUserProfileConfigRequestSchema, m as TailorDBGQLPermission_Action, mt as AuthInvokerSchema, nt as UpdateAuthIDPConfigRequestSchema, o as CreateWorkflowJobFunctionRequestSchema, ot as UpdateAuthSCIMResourceRequestSchema, p as UpdateTailorDBTypeRequestSchema, pt as AuthIDPConfig_AuthType, q as CreateAuthMachineUserRequestSchema, rt as UpdateAuthMachineUserRequestSchema, s as CreateWorkflowRequestSchema, st as UpdateAuthServiceRequestSchema, t as OperatorService, tt as UpdateAuthHookRequestSchema, u as WorkflowJobExecution_Status, ut as AuthConnection_Status, v as TailorDBType_Permission_Permit, vt as AuthSCIMAttribute_Type, w as CreateSecretManagerSecretRequestSchema, wt as CreateApplicationRequestSchema, x as CreateStaticWebsiteRequestSchema, y as TailorDBType_PermitAction, yt as AuthSCIMAttribute_Uniqueness, z as CreateExecutorExecutorRequestSchema } from "./service_pb-CIrhGwHk.mjs";
|
|
3
3
|
import { t as assertDefined } from "./assert-DBxo8jPo.mjs";
|
|
4
4
|
import { a as parseBoolean, i as symbols, n as logger, r as styles, t as CIPromptError } from "./logger-BEiZZ3qT.mjs";
|
|
5
|
-
import { $ as
|
|
6
|
-
import { o as loadFilesWithIgnores, t as createExecutorService } from "./service-
|
|
5
|
+
import { $ as getOrNull, A as hasUserTokenEntry, C as getDistDir, E as loadConfig, F as loadPlatformClientConfig, G as fetchAll, H as writePlatformConfig, J as fetchPaged, K as fetchAllTolerant, L as loadWorkspaceId, M as loadConfigPath, P as loadMachineUserName, Q as getOAuth2ClientId, R as platformConfigFromProfile, S as createBundleCache, T as hashFile, Z as getConsoleBaseUrl, at as byName, b as getPluginGenerationDependencies, c as HTTP_METHODS, d as buildResolverOperationHookExpr, et as getPlatformBaseUrl, f as assertUniqueLocalTailorDBTypeNames, g as platformBundleDefinePlugin, h as stringifyFunction, i as resolverBundleKey, it as resolveStaticWebsiteUrls, j as loadAccessToken, m as TailorDBTypeSchema, n as generatePluginFilesIfNeeded, nt as initOperatorClient, p as assertUniqueTailorDBTypeNamesWithExternal, q as fetchMachineUserToken, r as loadApplication, rt as isDefaultPlatform, t as defineApplication, u as buildExecutorArgsExpr, w as hashContent, x as hasGenerationHooks, z as readPlatformConfig } from "./application-BsH6tkZC.mjs";
|
|
6
|
+
import { o as loadFilesWithIgnores, t as createExecutorService } from "./service-BCRJ-3NV.mjs";
|
|
7
7
|
import { t as multiline } from "./multiline-sfHpTZZK.mjs";
|
|
8
8
|
import { t as readPackageJson } from "./package-json-8b0O9TlX.mjs";
|
|
9
9
|
import { i as userAgent } from "./secret-file-VSVGy1V0.mjs";
|
|
@@ -71,7 +71,9 @@ const durationArg = z.string().refine((val) => durationPattern.test(val), { mess
|
|
|
71
71
|
*/
|
|
72
72
|
function parseDuration(duration) {
|
|
73
73
|
const match = assertDefined(duration.match(durationPattern), `invalid duration format: ${duration}`);
|
|
74
|
-
|
|
74
|
+
const value = parseInt(assertDefined(match[1], "duration digits group missing"), 10);
|
|
75
|
+
const unit = assertDefined(match[2], "duration unit group missing");
|
|
76
|
+
return value * unitToMs[unit];
|
|
75
77
|
}
|
|
76
78
|
/**
|
|
77
79
|
* Schema for positive integer validation (from string input)
|
|
@@ -197,6 +199,18 @@ const configArg = { config: arg(z.string().default("tailor.config.ts"), {
|
|
|
197
199
|
}
|
|
198
200
|
}) };
|
|
199
201
|
/**
|
|
202
|
+
* Shared config arg for commands that accept one or more comma-separated config file paths
|
|
203
|
+
*/
|
|
204
|
+
const multiConfigArg = { config: arg(z.string().default("tailor.config.ts"), {
|
|
205
|
+
alias: "c",
|
|
206
|
+
description: "Path to SDK config file. Use comma-separated paths to deploy multiple apps together.",
|
|
207
|
+
env: "TAILOR_PLATFORM_SDK_CONFIG_PATH",
|
|
208
|
+
completion: {
|
|
209
|
+
type: "file",
|
|
210
|
+
extensions: ["ts"]
|
|
211
|
+
}
|
|
212
|
+
}) };
|
|
213
|
+
/**
|
|
200
214
|
* Arguments for commands that interact with deployed resources (includes config)
|
|
201
215
|
*/
|
|
202
216
|
const deploymentArgs = {
|
|
@@ -965,7 +979,7 @@ function createCacheStore(config) {
|
|
|
965
979
|
return path.join(config.cacheDir, BUNDLES_DIR);
|
|
966
980
|
}
|
|
967
981
|
function bundlePath(cacheKey) {
|
|
968
|
-
return path.join(bundlesDir(), `${cacheKey
|
|
982
|
+
return path.join(bundlesDir(), `${hashContent(cacheKey)}.js`);
|
|
969
983
|
}
|
|
970
984
|
function loadManifest() {
|
|
971
985
|
try {
|
|
@@ -1085,12 +1099,20 @@ function createCacheManager(options) {
|
|
|
1085
1099
|
enabled: true,
|
|
1086
1100
|
bundleCache: createBundleCache(store),
|
|
1087
1101
|
finalize() {
|
|
1088
|
-
const
|
|
1102
|
+
const currentManifest = store.getCurrentManifest() ?? {
|
|
1089
1103
|
version: 1,
|
|
1090
1104
|
sdkVersion: options.sdkVersion,
|
|
1091
1105
|
lockfileHash: options.lockfileHash,
|
|
1092
1106
|
entries: {}
|
|
1093
1107
|
};
|
|
1108
|
+
const latestManifest = store.loadManifest();
|
|
1109
|
+
const manifest = latestManifest?.sdkVersion === options.sdkVersion && latestManifest.lockfileHash === options.lockfileHash ? {
|
|
1110
|
+
...latestManifest,
|
|
1111
|
+
entries: {
|
|
1112
|
+
...latestManifest.entries,
|
|
1113
|
+
...currentManifest.entries
|
|
1114
|
+
}
|
|
1115
|
+
} : currentManifest;
|
|
1094
1116
|
manifest.sdkVersion = options.sdkVersion;
|
|
1095
1117
|
manifest.lockfileHash = options.lockfileHash;
|
|
1096
1118
|
store.saveManifest(manifest);
|
|
@@ -1827,14 +1849,7 @@ async function buildMetaRequest(params) {
|
|
|
1827
1849
|
*/
|
|
1828
1850
|
async function fetchExistingResourcesWithLabels(params) {
|
|
1829
1851
|
const { client, fetchPage, getName, getTrn } = params;
|
|
1830
|
-
const withoutLabel = await
|
|
1831
|
-
try {
|
|
1832
|
-
return await fetchPage(pageToken, maxPageSize);
|
|
1833
|
-
} catch (error) {
|
|
1834
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
1835
|
-
throw error;
|
|
1836
|
-
}
|
|
1837
|
-
});
|
|
1852
|
+
const withoutLabel = await fetchAllTolerant(fetchPage);
|
|
1838
1853
|
const existingResources = {};
|
|
1839
1854
|
await Promise.all(withoutLabel.map(async (resource) => {
|
|
1840
1855
|
const name = getName(resource);
|
|
@@ -1894,6 +1909,207 @@ function trackRemainingResourceOwner(params) {
|
|
|
1894
1909
|
return owned;
|
|
1895
1910
|
}
|
|
1896
1911
|
|
|
1912
|
+
//#endregion
|
|
1913
|
+
//#region src/cli/commands/deploy/staticwebsite.ts
|
|
1914
|
+
/**
|
|
1915
|
+
* Apply static website changes for the given phase.
|
|
1916
|
+
* @param client - Operator client instance
|
|
1917
|
+
* @param result - Planned static website changes
|
|
1918
|
+
* @param phase - Apply phase
|
|
1919
|
+
* @returns Promise that resolves when static websites are applied
|
|
1920
|
+
*/
|
|
1921
|
+
async function applyStaticWebsite(client, result, phase = "create-update") {
|
|
1922
|
+
const { changeSet, customDomainChangeSet } = result;
|
|
1923
|
+
if (phase === "create-update") {
|
|
1924
|
+
await Promise.all([...changeSet.creates.map(async (create) => {
|
|
1925
|
+
await client.createStaticWebsite(create.request);
|
|
1926
|
+
await client.setMetadata(create.metaRequest);
|
|
1927
|
+
}), ...changeSet.updates.map(async (update) => {
|
|
1928
|
+
await client.updateStaticWebsite(update.request);
|
|
1929
|
+
await client.setMetadata(update.metaRequest);
|
|
1930
|
+
})]);
|
|
1931
|
+
await Promise.all([...customDomainChangeSet.creates.map(async (add) => {
|
|
1932
|
+
await client.addCustomDomain(add.request);
|
|
1933
|
+
await client.setMetadata(add.metaRequest);
|
|
1934
|
+
}), ...customDomainChangeSet.deletes.map((del) => client.removeCustomDomain(del.request))]);
|
|
1935
|
+
} else await Promise.all(changeSet.deletes.map((del) => client.deleteStaticWebsite(del.request)));
|
|
1936
|
+
}
|
|
1937
|
+
function customDomainTrn(workspaceId, websiteName, domain) {
|
|
1938
|
+
return `trn:v1:workspace:${workspaceId}:staticwebsite:${websiteName}:custom_domain:${domain}`;
|
|
1939
|
+
}
|
|
1940
|
+
function normalizeComparableStaticWebsiteShape(input) {
|
|
1941
|
+
return {
|
|
1942
|
+
description: input.description,
|
|
1943
|
+
allowedIpAddresses: input.allowedIpAddresses.toSorted()
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
function normalizeComparableStaticWebsite(input) {
|
|
1947
|
+
return normalizeComparableStaticWebsiteShape({
|
|
1948
|
+
description: input.description || "",
|
|
1949
|
+
allowedIpAddresses: [...input.allowedIpAddresses || []]
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
function areStaticWebsitesEqual(existing, desired) {
|
|
1953
|
+
return areNormalizedEqual(normalizeComparableStaticWebsite(existing), normalizeComparableStaticWebsite(desired));
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Static website names expected to exist after this deploy run.
|
|
1957
|
+
* @param context - Planning context
|
|
1958
|
+
* @returns Names from the deploy-run scope when set, otherwise the application's own websites
|
|
1959
|
+
*/
|
|
1960
|
+
function expectedLocalStaticWebsiteNames(context) {
|
|
1961
|
+
return context.expectedLocalStaticWebsiteNames ?? new Set(context.application.staticWebsiteServices.map((website) => website.name));
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Plan static website changes based on current and desired state.
|
|
1965
|
+
* @param context - Planning context
|
|
1966
|
+
* @returns Planned changes
|
|
1967
|
+
*/
|
|
1968
|
+
async function planStaticWebsite(context) {
|
|
1969
|
+
const { client, workspaceId, application, forRemoval } = context;
|
|
1970
|
+
const changeSet = createChangeSet("StaticWebsites");
|
|
1971
|
+
const customDomainChangeSet = createChangeSet("CustomDomains");
|
|
1972
|
+
const conflicts = [];
|
|
1973
|
+
const unmanaged = [];
|
|
1974
|
+
const resourceOwners = /* @__PURE__ */ new Set();
|
|
1975
|
+
const existingWebsites = await fetchExistingResourcesWithLabels({
|
|
1976
|
+
client,
|
|
1977
|
+
fetchPage: async (pageToken, pageSize) => {
|
|
1978
|
+
const { staticwebsites, nextPageToken } = await client.listStaticWebsites({
|
|
1979
|
+
workspaceId,
|
|
1980
|
+
pageToken,
|
|
1981
|
+
pageSize
|
|
1982
|
+
});
|
|
1983
|
+
return [staticwebsites, nextPageToken];
|
|
1984
|
+
},
|
|
1985
|
+
getName: (resource) => resource.name,
|
|
1986
|
+
getTrn: (name) => resourceTrn(workspaceId, "staticwebsite", name)
|
|
1987
|
+
});
|
|
1988
|
+
const ownedWebsiteNames = /* @__PURE__ */ new Set();
|
|
1989
|
+
const staticWebsiteServices = forRemoval ? [] : application.staticWebsiteServices;
|
|
1990
|
+
for (const websiteService of staticWebsiteServices) {
|
|
1991
|
+
const config = websiteService;
|
|
1992
|
+
const name = websiteService.name;
|
|
1993
|
+
const existing = existingWebsites[name];
|
|
1994
|
+
const metaRequest = await buildMetaRequest({
|
|
1995
|
+
trn: resourceTrn(workspaceId, "staticwebsite", name),
|
|
1996
|
+
appName: application.name,
|
|
1997
|
+
appId: application.id
|
|
1998
|
+
});
|
|
1999
|
+
const desired = normalizeComparableStaticWebsite(config);
|
|
2000
|
+
const request = {
|
|
2001
|
+
workspaceId,
|
|
2002
|
+
staticwebsite: {
|
|
2003
|
+
name,
|
|
2004
|
+
description: config.description || "",
|
|
2005
|
+
allowedIpAddresses: config.allowedIpAddresses || []
|
|
2006
|
+
}
|
|
2007
|
+
};
|
|
2008
|
+
if (existing) {
|
|
2009
|
+
const owned = trackDesiredResourceOwnership({
|
|
2010
|
+
labels: existing.allLabels,
|
|
2011
|
+
ownerLabel: existing.label,
|
|
2012
|
+
appName: application.name,
|
|
2013
|
+
appId: application.id,
|
|
2014
|
+
resourceType: "StaticWebsite",
|
|
2015
|
+
resourceName: name,
|
|
2016
|
+
conflicts,
|
|
2017
|
+
unmanaged
|
|
2018
|
+
});
|
|
2019
|
+
if (owned && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areStaticWebsitesEqual(existing.resource, desired)) changeSet.unchanged.push({ name });
|
|
2020
|
+
else changeSet.updates.push({
|
|
2021
|
+
name,
|
|
2022
|
+
request,
|
|
2023
|
+
metaRequest
|
|
2024
|
+
});
|
|
2025
|
+
if (owned) ownedWebsiteNames.add(name);
|
|
2026
|
+
delete existingWebsites[name];
|
|
2027
|
+
} else {
|
|
2028
|
+
changeSet.creates.push({
|
|
2029
|
+
name,
|
|
2030
|
+
request,
|
|
2031
|
+
metaRequest
|
|
2032
|
+
});
|
|
2033
|
+
ownedWebsiteNames.add(name);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
Object.entries(existingWebsites).forEach(([name]) => {
|
|
2037
|
+
const entry = existingWebsites[name];
|
|
2038
|
+
const label = entry?.label;
|
|
2039
|
+
if (trackRemainingResourceOwner({
|
|
2040
|
+
labels: entry?.allLabels,
|
|
2041
|
+
ownerLabel: label,
|
|
2042
|
+
appName: application.name,
|
|
2043
|
+
appId: application.id,
|
|
2044
|
+
resourceOwners
|
|
2045
|
+
})) changeSet.deletes.push({
|
|
2046
|
+
name,
|
|
2047
|
+
request: {
|
|
2048
|
+
workspaceId,
|
|
2049
|
+
name
|
|
2050
|
+
}
|
|
2051
|
+
});
|
|
2052
|
+
});
|
|
2053
|
+
const desiredDomainsByWebsite = /* @__PURE__ */ new Map();
|
|
2054
|
+
for (const service of staticWebsiteServices) if (service.customDomains !== void 0 && ownedWebsiteNames.has(service.name)) desiredDomainsByWebsite.set(service.name, service.customDomains);
|
|
2055
|
+
const existingDomainsByWebsite = /* @__PURE__ */ new Map();
|
|
2056
|
+
const websitesToFetchDomains = [...ownedWebsiteNames].filter((name) => !changeSet.creates.some((c) => c.name === name));
|
|
2057
|
+
await Promise.all(websitesToFetchDomains.map(async (name) => {
|
|
2058
|
+
const domainsWithLabels = await getOrNull(async () => {
|
|
2059
|
+
const { customDomains } = await client.listCustomDomains({
|
|
2060
|
+
workspaceId,
|
|
2061
|
+
staticWebsiteName: name
|
|
2062
|
+
});
|
|
2063
|
+
return await Promise.all(customDomains.map(async (d) => {
|
|
2064
|
+
const { metadata } = await client.getMetadata({ trn: customDomainTrn(workspaceId, name, d.domain) });
|
|
2065
|
+
return {
|
|
2066
|
+
domain: d.domain,
|
|
2067
|
+
allLabels: metadata?.labels
|
|
2068
|
+
};
|
|
2069
|
+
}));
|
|
2070
|
+
});
|
|
2071
|
+
if (domainsWithLabels) existingDomainsByWebsite.set(name, domainsWithLabels);
|
|
2072
|
+
}));
|
|
2073
|
+
for (const name of ownedWebsiteNames) {
|
|
2074
|
+
const desired = new Set(desiredDomainsByWebsite.get(name) ?? []);
|
|
2075
|
+
const existingDomains = existingDomainsByWebsite.get(name) ?? [];
|
|
2076
|
+
const existingSet = new Set(existingDomains.map((d) => d.domain));
|
|
2077
|
+
const sdkOwnedDomains = new Set(existingDomains.filter((d) => isOwnedByApp(d.allLabels, application.name, application.id)).map((d) => d.domain));
|
|
2078
|
+
for (const domain of desired) if (!existingSet.has(domain)) {
|
|
2079
|
+
const metaRequest = await buildMetaRequest({
|
|
2080
|
+
trn: customDomainTrn(workspaceId, name, domain),
|
|
2081
|
+
appName: application.name,
|
|
2082
|
+
appId: application.id
|
|
2083
|
+
});
|
|
2084
|
+
customDomainChangeSet.creates.push({
|
|
2085
|
+
name: domain,
|
|
2086
|
+
request: {
|
|
2087
|
+
workspaceId,
|
|
2088
|
+
staticWebsiteName: name,
|
|
2089
|
+
domain
|
|
2090
|
+
},
|
|
2091
|
+
metaRequest
|
|
2092
|
+
});
|
|
2093
|
+
} else customDomainChangeSet.unchanged.push({ name: domain });
|
|
2094
|
+
if (desiredDomainsByWebsite.has(name)) {
|
|
2095
|
+
for (const domain of sdkOwnedDomains) if (!desired.has(domain)) customDomainChangeSet.deletes.push({
|
|
2096
|
+
name: domain,
|
|
2097
|
+
request: {
|
|
2098
|
+
workspaceId,
|
|
2099
|
+
domain
|
|
2100
|
+
}
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
return {
|
|
2105
|
+
changeSet,
|
|
2106
|
+
customDomainChangeSet,
|
|
2107
|
+
conflicts,
|
|
2108
|
+
unmanaged,
|
|
2109
|
+
resourceOwners
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
|
|
1897
2113
|
//#endregion
|
|
1898
2114
|
//#region src/cli/commands/deploy/aigateway.ts
|
|
1899
2115
|
/**
|
|
@@ -1956,7 +2172,7 @@ async function planAIGateway(context) {
|
|
|
1956
2172
|
getTrn: (name) => resourceTrn(workspaceId, "aigateway", name)
|
|
1957
2173
|
});
|
|
1958
2174
|
const aiGatewayServices = forRemoval ? [] : application.aiGatewayServices;
|
|
1959
|
-
const expectedLocalWebsites =
|
|
2175
|
+
const expectedLocalWebsites = expectedLocalStaticWebsiteNames(context);
|
|
1960
2176
|
for (const gatewayService of aiGatewayServices) {
|
|
1961
2177
|
const config = gatewayService;
|
|
1962
2178
|
const name = gatewayService.name;
|
|
@@ -2120,18 +2336,13 @@ function areApplicationsEqual(existing, desired) {
|
|
|
2120
2336
|
async function planApplication(context, httpAdapterBuildResult) {
|
|
2121
2337
|
const { client, workspaceId, application, forRemoval } = context;
|
|
2122
2338
|
const changeSet = createChangeSet("Applications");
|
|
2123
|
-
const existingApplications = await
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
return [applications, nextPageToken];
|
|
2131
|
-
} catch (error) {
|
|
2132
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
2133
|
-
throw error;
|
|
2134
|
-
}
|
|
2339
|
+
const existingApplications = await fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
2340
|
+
const { applications, nextPageToken } = await client.listApplications({
|
|
2341
|
+
workspaceId,
|
|
2342
|
+
pageToken,
|
|
2343
|
+
pageSize: maxPageSize
|
|
2344
|
+
});
|
|
2345
|
+
return [applications, nextPageToken];
|
|
2135
2346
|
});
|
|
2136
2347
|
if (forRemoval) {
|
|
2137
2348
|
const candidates = application.id ? existingApplications : existingApplications.filter((app) => app.name === application.name);
|
|
@@ -2156,8 +2367,9 @@ async function planApplication(context, httpAdapterBuildResult) {
|
|
|
2156
2367
|
if (idProvider) authIdpConfigName = idProvider.name;
|
|
2157
2368
|
} else if (application.config.auth) {
|
|
2158
2369
|
authNamespace = application.config.auth.name;
|
|
2159
|
-
|
|
2160
|
-
|
|
2370
|
+
if (context.externalAuthIdpConfigNames?.has(authNamespace)) authIdpConfigName = context.externalAuthIdpConfigNames.get(authNamespace);
|
|
2371
|
+
else {
|
|
2372
|
+
const idpConfigs = await fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
2161
2373
|
const { idpConfigs, nextPageToken } = await client.listAuthIDPConfigs({
|
|
2162
2374
|
workspaceId,
|
|
2163
2375
|
namespaceName: assertDefined(authNamespace, "authNamespace must be set before listing IDP configs"),
|
|
@@ -2165,14 +2377,11 @@ async function planApplication(context, httpAdapterBuildResult) {
|
|
|
2165
2377
|
pageSize: maxPageSize
|
|
2166
2378
|
});
|
|
2167
2379
|
return [idpConfigs, nextPageToken];
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2380
|
+
});
|
|
2381
|
+
if (idpConfigs.length > 0) {
|
|
2382
|
+
const [firstConfig] = idpConfigs;
|
|
2383
|
+
if (firstConfig) authIdpConfigName = firstConfig.name;
|
|
2171
2384
|
}
|
|
2172
|
-
});
|
|
2173
|
-
if (idpConfigs.length > 0) {
|
|
2174
|
-
const [firstConfig] = idpConfigs;
|
|
2175
|
-
if (firstConfig) authIdpConfigName = firstConfig.name;
|
|
2176
2385
|
}
|
|
2177
2386
|
}
|
|
2178
2387
|
const metaRequest = await buildMetaRequest({
|
|
@@ -2180,7 +2389,7 @@ async function planApplication(context, httpAdapterBuildResult) {
|
|
|
2180
2389
|
appName: application.name,
|
|
2181
2390
|
appId: application.id
|
|
2182
2391
|
});
|
|
2183
|
-
const expectedLocalWebsites =
|
|
2392
|
+
const expectedLocalWebsites = expectedLocalStaticWebsiteNames(context);
|
|
2184
2393
|
const resolvedCors = await resolveStaticWebsiteUrls(client, workspaceId, application.config.cors, "CORS", { expectedLocalNames: expectedLocalWebsites });
|
|
2185
2394
|
const httpAdapters = buildHttpAdapters(application, httpAdapterBuildResult);
|
|
2186
2395
|
const desired = normalizeComparableApplication(application, authNamespace, authIdpConfigName, resolvedCors, httpAdapters);
|
|
@@ -2234,13 +2443,10 @@ async function planApplication(context, httpAdapterBuildResult) {
|
|
|
2234
2443
|
return changeSet;
|
|
2235
2444
|
}
|
|
2236
2445
|
async function fetchAppLabels(client, workspaceId, appName) {
|
|
2237
|
-
|
|
2446
|
+
return (await getOrNull(async () => {
|
|
2238
2447
|
const { metadata } = await client.getMetadata({ trn: resourceTrn(workspaceId, "application", appName) });
|
|
2239
|
-
return metadata
|
|
2240
|
-
}
|
|
2241
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return;
|
|
2242
|
-
throw error;
|
|
2243
|
-
}
|
|
2448
|
+
return metadata;
|
|
2449
|
+
}))?.labels;
|
|
2244
2450
|
}
|
|
2245
2451
|
/**
|
|
2246
2452
|
* Build per-adapter diff lines for the application plan display. The platform
|
|
@@ -2424,18 +2630,13 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
|
|
|
2424
2630
|
const resourceOwners = /* @__PURE__ */ new Set();
|
|
2425
2631
|
const desiredConnections = {};
|
|
2426
2632
|
for (const auth of auths) for (const [name, config] of Object.entries(auth.connections)) desiredConnections[name] = config;
|
|
2427
|
-
const existingList = await
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
return [connections, nextPageToken];
|
|
2435
|
-
} catch (error) {
|
|
2436
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
2437
|
-
throw error;
|
|
2438
|
-
}
|
|
2633
|
+
const existingList = await fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
2634
|
+
const { connections, nextPageToken } = await client.listAuthConnections({
|
|
2635
|
+
workspaceId,
|
|
2636
|
+
pageToken,
|
|
2637
|
+
pageSize: maxPageSize
|
|
2638
|
+
});
|
|
2639
|
+
return [connections, nextPageToken];
|
|
2439
2640
|
});
|
|
2440
2641
|
const existingConnections = {};
|
|
2441
2642
|
await Promise.all(existingList.map(async (resource) => {
|
|
@@ -2455,14 +2656,15 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
|
|
|
2455
2656
|
appId
|
|
2456
2657
|
});
|
|
2457
2658
|
if (existing) {
|
|
2458
|
-
|
|
2659
|
+
trackDesiredResourceOwnership({
|
|
2660
|
+
labels: existing.allLabels,
|
|
2661
|
+
ownerLabel: existing.label,
|
|
2662
|
+
appName,
|
|
2663
|
+
appId,
|
|
2459
2664
|
resourceType: "Auth connection",
|
|
2460
2665
|
resourceName: name,
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
else unmanaged.push({
|
|
2464
|
-
resourceType: "Auth connection",
|
|
2465
|
-
resourceName: name
|
|
2666
|
+
conflicts,
|
|
2667
|
+
unmanaged
|
|
2466
2668
|
});
|
|
2467
2669
|
const secretChanged = hashValue(config.clientSecret) !== state.connections?.[name];
|
|
2468
2670
|
const updateMask = buildUpdateMask(existing.resource, config, secretChanged);
|
|
@@ -2488,12 +2690,13 @@ async function planAuthConnections(client, workspaceId, appName, appId, auths) {
|
|
|
2488
2690
|
}
|
|
2489
2691
|
for (const [name, entry] of Object.entries(existingConnections)) {
|
|
2490
2692
|
if (!entry) continue;
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2693
|
+
if (trackRemainingResourceOwner({
|
|
2694
|
+
labels: entry.allLabels,
|
|
2695
|
+
ownerLabel: entry.label,
|
|
2696
|
+
appName,
|
|
2697
|
+
appId,
|
|
2698
|
+
resourceOwners
|
|
2699
|
+
})) changeSet.deletes.push({
|
|
2497
2700
|
name,
|
|
2498
2701
|
request: {
|
|
2499
2702
|
workspaceId,
|
|
@@ -2656,9 +2859,9 @@ function authHookFunctionName(authName, hookPoint) {
|
|
|
2656
2859
|
function collectFunctionEntries(application, workflowJobs, bundledScripts) {
|
|
2657
2860
|
const entries = [];
|
|
2658
2861
|
for (const app of application.applications) for (const pipeline of app.resolverServices) for (const resolver of Object.values(pipeline.resolvers)) {
|
|
2659
|
-
const content = bundledScripts.resolvers.get(resolver.name);
|
|
2862
|
+
const content = bundledScripts.resolvers.get(resolverBundleKey(pipeline.namespace, resolver.name));
|
|
2660
2863
|
if (!content) {
|
|
2661
|
-
logger.warn(`Bundled code not found for resolver: ${resolver.name}`);
|
|
2864
|
+
logger.warn(`Bundled code not found for resolver: ${pipeline.namespace}/${resolver.name}`);
|
|
2662
2865
|
continue;
|
|
2663
2866
|
}
|
|
2664
2867
|
entries.push({
|
|
@@ -2762,15 +2965,15 @@ async function planFunctionRegistry(client, workspaceId, appName, appId, entries
|
|
|
2762
2965
|
appId
|
|
2763
2966
|
});
|
|
2764
2967
|
if (existing) {
|
|
2765
|
-
const owned =
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
else conflicts.push({
|
|
2968
|
+
const owned = trackDesiredResourceOwnership({
|
|
2969
|
+
labels: existing.allLabels,
|
|
2970
|
+
ownerLabel: existing.label,
|
|
2971
|
+
appName,
|
|
2972
|
+
appId,
|
|
2771
2973
|
resourceType: "Function registry",
|
|
2772
2974
|
resourceName: entry.name,
|
|
2773
|
-
|
|
2975
|
+
conflicts,
|
|
2976
|
+
unmanaged
|
|
2774
2977
|
});
|
|
2775
2978
|
if (existing.resource.contentHash === entry.contentHash && owned && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) changeSet.unchanged.push({ name: entry.name });
|
|
2776
2979
|
else changeSet.updates.push({
|
|
@@ -2787,10 +2990,13 @@ async function planFunctionRegistry(client, workspaceId, appName, appId, entries
|
|
|
2787
2990
|
}
|
|
2788
2991
|
for (const [name, existing] of Object.entries(existingMap)) {
|
|
2789
2992
|
if (!existing) continue;
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2993
|
+
if (trackRemainingResourceOwner({
|
|
2994
|
+
labels: existing.allLabels,
|
|
2995
|
+
ownerLabel: existing.label,
|
|
2996
|
+
appName,
|
|
2997
|
+
appId,
|
|
2998
|
+
resourceOwners
|
|
2999
|
+
})) changeSet.deletes.push({
|
|
2794
3000
|
name,
|
|
2795
3001
|
workspaceId
|
|
2796
3002
|
});
|
|
@@ -3276,15 +3482,12 @@ async function applyIdP(client, result, phase = "create-update") {
|
|
|
3276
3482
|
}), ...changeSet.client.updates.map(async (update) => {
|
|
3277
3483
|
const vaultName = idpClientVaultName(update.namespaceName, update.name);
|
|
3278
3484
|
const secretName = idpClientSecretName(update.namespaceName, update.name);
|
|
3279
|
-
|
|
3280
|
-
await client.getSecretManagerVault({
|
|
3485
|
+
if (await getOrNull(async () => {
|
|
3486
|
+
return await client.getSecretManagerVault({
|
|
3281
3487
|
workspaceId: update.workspaceId,
|
|
3282
3488
|
secretmanagerVaultName: vaultName
|
|
3283
3489
|
});
|
|
3284
|
-
|
|
3285
|
-
} catch (error) {
|
|
3286
|
-
if (!(error instanceof ConnectError && error.code === Code.NotFound)) throw error;
|
|
3287
|
-
}
|
|
3490
|
+
})) return;
|
|
3288
3491
|
await client.createSecretManagerVault({
|
|
3289
3492
|
workspaceId: update.workspaceId,
|
|
3290
3493
|
secretmanagerVaultName: vaultName
|
|
@@ -3314,7 +3517,7 @@ async function applyIdP(client, result, phase = "create-update") {
|
|
|
3314
3517
|
async function planIdP(context) {
|
|
3315
3518
|
const { client, workspaceId, application, forRemoval, forceApplyAll = false, idpUserTriggerTargets } = context;
|
|
3316
3519
|
const idps = forRemoval ? [] : application.idpServices;
|
|
3317
|
-
const expectedLocalWebsites =
|
|
3520
|
+
const expectedLocalWebsites = expectedLocalStaticWebsiteNames(context);
|
|
3318
3521
|
const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$3(client, workspaceId, application.name, application.id, idps, idpUserTriggerTargets ?? /* @__PURE__ */ new Set(), expectedLocalWebsites);
|
|
3319
3522
|
return {
|
|
3320
3523
|
changeSet: {
|
|
@@ -3484,17 +3687,16 @@ async function planServices$3(client, workspaceId, appName, appId, idps, idpUser
|
|
|
3484
3687
|
permission: protoPermission
|
|
3485
3688
|
};
|
|
3486
3689
|
if (existing) {
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
else conflicts.push({
|
|
3690
|
+
if (trackDesiredResourceOwnership({
|
|
3691
|
+
labels: existing.allLabels,
|
|
3692
|
+
ownerLabel: existing.label,
|
|
3693
|
+
appName,
|
|
3694
|
+
appId,
|
|
3493
3695
|
resourceType: "IdP service",
|
|
3494
3696
|
resourceName: idp.name,
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3697
|
+
conflicts,
|
|
3698
|
+
unmanaged
|
|
3699
|
+
}) && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areIdPServicesEqual(existing.resource, desired)) changeSet.unchanged.push({ name: namespaceName });
|
|
3498
3700
|
else changeSet.updates.push({
|
|
3499
3701
|
name: namespaceName,
|
|
3500
3702
|
request,
|
|
@@ -3509,10 +3711,13 @@ async function planServices$3(client, workspaceId, appName, appId, idps, idpUser
|
|
|
3509
3711
|
}
|
|
3510
3712
|
Object.entries(existingServices).forEach(([namespaceName]) => {
|
|
3511
3713
|
const entry = existingServices[namespaceName];
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3714
|
+
if (trackRemainingResourceOwner({
|
|
3715
|
+
labels: entry?.allLabels,
|
|
3716
|
+
ownerLabel: entry?.label,
|
|
3717
|
+
appName,
|
|
3718
|
+
appId,
|
|
3719
|
+
resourceOwners
|
|
3720
|
+
})) changeSet.deletes.push({
|
|
3516
3721
|
name: namespaceName,
|
|
3517
3722
|
request: {
|
|
3518
3723
|
workspaceId,
|
|
@@ -3530,19 +3735,14 @@ async function planServices$3(client, workspaceId, appName, appId, idps, idpUser
|
|
|
3530
3735
|
async function planClients(client, workspaceId, idps, deletedServices, forceApplyAll = false) {
|
|
3531
3736
|
const changeSet = createChangeSet("IdP clients");
|
|
3532
3737
|
const fetchClients = (namespaceName) => {
|
|
3533
|
-
return
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
return [clients, nextPageToken];
|
|
3542
|
-
} catch (error) {
|
|
3543
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
3544
|
-
throw error;
|
|
3545
|
-
}
|
|
3738
|
+
return fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
3739
|
+
const { clients, nextPageToken } = await client.listIdPClients({
|
|
3740
|
+
workspaceId,
|
|
3741
|
+
namespaceName,
|
|
3742
|
+
pageToken,
|
|
3743
|
+
pageSize: maxPageSize
|
|
3744
|
+
});
|
|
3745
|
+
return [clients, nextPageToken];
|
|
3546
3746
|
});
|
|
3547
3747
|
};
|
|
3548
3748
|
const clientsByIdp = await Promise.all(idps.map((idp) => fetchClients(idp.name)));
|
|
@@ -3701,7 +3901,7 @@ function protoIdPOperand(operand) {
|
|
|
3701
3901
|
*/
|
|
3702
3902
|
async function applyAuth(client, result, phase = "create-update") {
|
|
3703
3903
|
const { changeSet } = result;
|
|
3704
|
-
|
|
3904
|
+
const applyServices = async () => {
|
|
3705
3905
|
await Promise.all([...changeSet.service.creates.map(async (create) => {
|
|
3706
3906
|
await client.createAuthService(create.request);
|
|
3707
3907
|
await client.setMetadata(create.metaRequest);
|
|
@@ -3709,6 +3909,11 @@ async function applyAuth(client, result, phase = "create-update") {
|
|
|
3709
3909
|
await client.updateAuthService(update.request);
|
|
3710
3910
|
await client.setMetadata(update.metaRequest);
|
|
3711
3911
|
})]);
|
|
3912
|
+
};
|
|
3913
|
+
const applyMachineUsers = async () => {
|
|
3914
|
+
await Promise.all([...changeSet.machineUser.creates.map((create) => client.createAuthMachineUser(create.request)), ...changeSet.machineUser.updates.map((update) => client.updateAuthMachineUser(update.request))]);
|
|
3915
|
+
};
|
|
3916
|
+
const applyCreateUpdateDependents = async () => {
|
|
3712
3917
|
await applyAuthConnections(client, { changeSet: changeSet.connection }, "create-update");
|
|
3713
3918
|
await Promise.all([...changeSet.idpConfig.creates.map(async (create) => {
|
|
3714
3919
|
if (create.idpConfig.kind === "BuiltInIdP") assertDefined(create.request.idpConfig, "request missing idpConfig").config = await protoBuiltinIdPConfig(client, assertDefined(create.request.workspaceId, "request missing workspaceId"), create.idpConfig);
|
|
@@ -3719,7 +3924,6 @@ async function applyAuth(client, result, phase = "create-update") {
|
|
|
3719
3924
|
})]);
|
|
3720
3925
|
await Promise.all([...changeSet.userProfileConfig.creates.map((create) => client.createUserProfileConfig(create.request)), ...changeSet.userProfileConfig.updates.map((update) => client.updateUserProfileConfig(update.request))]);
|
|
3721
3926
|
await Promise.all([...changeSet.tenantConfig.creates.map((create) => client.createTenantConfig(create.request)), ...changeSet.tenantConfig.updates.map((update) => client.updateTenantConfig(update.request))]);
|
|
3722
|
-
await Promise.all([...changeSet.machineUser.creates.map((create) => client.createAuthMachineUser(create.request)), ...changeSet.machineUser.updates.map((update) => client.updateAuthMachineUser(update.request))]);
|
|
3723
3927
|
await Promise.all([...changeSet.authHook.creates.map((create) => client.createAuthHook(create.request)), ...changeSet.authHook.updates.map((update) => client.updateAuthHook(update.request))]);
|
|
3724
3928
|
await Promise.all([...changeSet.oauth2Client.creates.map(async (create) => {
|
|
3725
3929
|
const oauth2Client = assertDefined(create.request.oauth2Client, "request missing oauth2Client");
|
|
@@ -3738,7 +3942,13 @@ async function applyAuth(client, result, phase = "create-update") {
|
|
|
3738
3942
|
}
|
|
3739
3943
|
await Promise.all([...changeSet.scim.creates.map((create) => client.createAuthSCIMConfig(create.request)), ...changeSet.scim.updates.map((update) => client.updateAuthSCIMConfig(update.request))]);
|
|
3740
3944
|
await Promise.all([...changeSet.scimResource.creates.map((create) => client.createAuthSCIMResource(create.request)), ...changeSet.scimResource.updates.map((update) => client.updateAuthSCIMResource(update.request))]);
|
|
3741
|
-
}
|
|
3945
|
+
};
|
|
3946
|
+
if (phase === "create-update-prerequisites" || phase === "create-update") {
|
|
3947
|
+
await applyServices();
|
|
3948
|
+
await applyMachineUsers();
|
|
3949
|
+
}
|
|
3950
|
+
if (phase === "create-update-dependents" || phase === "create-update") await applyCreateUpdateDependents();
|
|
3951
|
+
if (phase === "delete-resources") {
|
|
3742
3952
|
await Promise.all(changeSet.scimResource.deletes.map((del) => client.deleteAuthSCIMResource(del.request)));
|
|
3743
3953
|
await Promise.all(changeSet.scim.deletes.map((del) => client.deleteAuthSCIMConfig(del.request)));
|
|
3744
3954
|
await Promise.all(changeSet.oauth2Client.deletes.map((del) => client.deleteAuthOAuth2Client(del.request)));
|
|
@@ -3748,7 +3958,7 @@ async function applyAuth(client, result, phase = "create-update") {
|
|
|
3748
3958
|
await Promise.all(changeSet.userProfileConfig.deletes.map((del) => client.deleteUserProfileConfig(del.request)));
|
|
3749
3959
|
await Promise.all(changeSet.idpConfig.deletes.map((del) => client.deleteAuthIDPConfig(del.request)));
|
|
3750
3960
|
await applyAuthConnections(client, { changeSet: changeSet.connection }, "delete-resources");
|
|
3751
|
-
} else await Promise.all(changeSet.service.deletes.map((del) => client.deleteAuthService(del.request)));
|
|
3961
|
+
} else if (phase === "delete-services") await Promise.all(changeSet.service.deletes.map((del) => client.deleteAuthService(del.request)));
|
|
3752
3962
|
}
|
|
3753
3963
|
/**
|
|
3754
3964
|
* Plan auth-related changes based on current and desired state.
|
|
@@ -3764,7 +3974,7 @@ async function planAuth(context) {
|
|
|
3764
3974
|
}
|
|
3765
3975
|
const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$2(client, workspaceId, application.name, application.id, auths, forceApplyAll);
|
|
3766
3976
|
const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
|
|
3767
|
-
const expectedLocalWebsites =
|
|
3977
|
+
const expectedLocalWebsites = expectedLocalStaticWebsiteNames(context);
|
|
3768
3978
|
const [idpConfigChangeSet, userProfileConfigChangeSet, tenantConfigChangeSet, machineUserChangeSet, authHookChangeSet, oauth2ClientChangeSet, scimChangeSet, scimResourceChangeSet, connectionResult] = await Promise.all([
|
|
3769
3979
|
planIdPConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
|
|
3770
3980
|
planUserProfileConfigs(client, workspaceId, auths, deletedServices, forceApplyAll),
|
|
@@ -3826,15 +4036,15 @@ async function planServices$2(client, workspaceId, appName, appId, auths, forceA
|
|
|
3826
4036
|
publishSessionEvents: config.publishSessionEvents
|
|
3827
4037
|
};
|
|
3828
4038
|
if (existing) {
|
|
3829
|
-
const owned =
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
else conflicts.push({
|
|
4039
|
+
const owned = trackDesiredResourceOwnership({
|
|
4040
|
+
labels: existing.allLabels,
|
|
4041
|
+
ownerLabel: existing.label,
|
|
4042
|
+
appName,
|
|
4043
|
+
appId,
|
|
3835
4044
|
resourceType: "Auth service",
|
|
3836
4045
|
resourceName: config.name,
|
|
3837
|
-
|
|
4046
|
+
conflicts,
|
|
4047
|
+
unmanaged
|
|
3838
4048
|
});
|
|
3839
4049
|
if (!forceApplyAll && existing.resource.publishSessionEvents === (config.publishSessionEvents ?? false) && owned) changeSet.unchanged.push({ name: config.name });
|
|
3840
4050
|
else changeSet.updates.push({
|
|
@@ -3851,10 +4061,13 @@ async function planServices$2(client, workspaceId, appName, appId, auths, forceA
|
|
|
3851
4061
|
}
|
|
3852
4062
|
Object.entries(existingServices).forEach(([namespaceName]) => {
|
|
3853
4063
|
const entry = existingServices[namespaceName];
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
4064
|
+
if (trackRemainingResourceOwner({
|
|
4065
|
+
labels: entry?.allLabels,
|
|
4066
|
+
ownerLabel: entry?.label,
|
|
4067
|
+
appName,
|
|
4068
|
+
appId,
|
|
4069
|
+
resourceOwners
|
|
4070
|
+
})) changeSet.deletes.push({
|
|
3858
4071
|
name: namespaceName,
|
|
3859
4072
|
request: {
|
|
3860
4073
|
workspaceId,
|
|
@@ -3872,19 +4085,14 @@ async function planServices$2(client, workspaceId, appName, appId, auths, forceA
|
|
|
3872
4085
|
async function planIdPConfigs(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
|
|
3873
4086
|
const changeSet = createChangeSet("Auth idpConfigs");
|
|
3874
4087
|
const fetchIdPConfigs = (namespaceName) => {
|
|
3875
|
-
return
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
return [idpConfigs, nextPageToken];
|
|
3884
|
-
} catch (error) {
|
|
3885
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
3886
|
-
throw error;
|
|
3887
|
-
}
|
|
4088
|
+
return fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
4089
|
+
const { idpConfigs, nextPageToken } = await client.listAuthIDPConfigs({
|
|
4090
|
+
workspaceId,
|
|
4091
|
+
namespaceName,
|
|
4092
|
+
pageToken,
|
|
4093
|
+
pageSize: maxPageSize
|
|
4094
|
+
});
|
|
4095
|
+
return [idpConfigs, nextPageToken];
|
|
3888
4096
|
});
|
|
3889
4097
|
};
|
|
3890
4098
|
for (const authService of auths) {
|
|
@@ -4041,27 +4249,21 @@ async function protoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
|
|
|
4041
4249
|
return config;
|
|
4042
4250
|
}
|
|
4043
4251
|
async function tryProtoBuiltinIdPConfig(client, workspaceId, builtinIdPConfig) {
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
idpService = await client.getIdPService({
|
|
4252
|
+
const idpService = await getOrNull(async () => {
|
|
4253
|
+
return await client.getIdPService({
|
|
4047
4254
|
workspaceId,
|
|
4048
4255
|
namespaceName: builtinIdPConfig.namespace
|
|
4049
4256
|
});
|
|
4050
|
-
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
let idpClient;
|
|
4055
|
-
try {
|
|
4056
|
-
idpClient = await client.getIdPClient({
|
|
4257
|
+
});
|
|
4258
|
+
if (!idpService) return void 0;
|
|
4259
|
+
const idpClient = await getOrNull(async () => {
|
|
4260
|
+
return await client.getIdPClient({
|
|
4057
4261
|
workspaceId,
|
|
4058
4262
|
namespaceName: builtinIdPConfig.namespace,
|
|
4059
4263
|
name: builtinIdPConfig.clientName
|
|
4060
4264
|
});
|
|
4061
|
-
}
|
|
4062
|
-
|
|
4063
|
-
throw error;
|
|
4064
|
-
}
|
|
4265
|
+
});
|
|
4266
|
+
if (!idpClient) return void 0;
|
|
4065
4267
|
const vaultName = idpClientVaultName(builtinIdPConfig.namespace, builtinIdPConfig.clientName);
|
|
4066
4268
|
const secretKey = idpClientSecretName(builtinIdPConfig.namespace, builtinIdPConfig.clientName);
|
|
4067
4269
|
return { config: {
|
|
@@ -4082,56 +4284,51 @@ async function planUserProfileConfigs(client, workspaceId, auths, deletedService
|
|
|
4082
4284
|
for (const auth of auths) {
|
|
4083
4285
|
const { config } = auth;
|
|
4084
4286
|
const name = `${config.name}-user-profile-config`;
|
|
4085
|
-
|
|
4086
|
-
|
|
4287
|
+
const existing = await getOrNull(async () => {
|
|
4288
|
+
return await client.getUserProfileConfig({
|
|
4087
4289
|
workspaceId,
|
|
4088
4290
|
namespaceName: config.name
|
|
4089
4291
|
});
|
|
4292
|
+
});
|
|
4293
|
+
if (!existing) {
|
|
4090
4294
|
const userProfileForUpdate = auth.userProfile;
|
|
4091
|
-
if (userProfileForUpdate) {
|
|
4092
|
-
const desired = protoUserProfileConfig(userProfileForUpdate);
|
|
4093
|
-
if (!forceApplyAll && areUserProfileConfigsEqual(userProfileProviderConfig ?? {}, desired)) changeSet.unchanged.push({ name });
|
|
4094
|
-
else changeSet.updates.push({
|
|
4095
|
-
name,
|
|
4096
|
-
request: {
|
|
4097
|
-
workspaceId,
|
|
4098
|
-
namespaceName: config.name,
|
|
4099
|
-
userProfileProviderConfig: desired
|
|
4100
|
-
}
|
|
4101
|
-
});
|
|
4102
|
-
} else changeSet.deletes.push({
|
|
4295
|
+
if (userProfileForUpdate) changeSet.creates.push({
|
|
4103
4296
|
name,
|
|
4104
4297
|
request: {
|
|
4105
4298
|
workspaceId,
|
|
4106
|
-
namespaceName: config.name
|
|
4299
|
+
namespaceName: config.name,
|
|
4300
|
+
userProfileProviderConfig: protoUserProfileConfig(userProfileForUpdate)
|
|
4107
4301
|
}
|
|
4108
4302
|
});
|
|
4109
|
-
|
|
4110
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) {
|
|
4111
|
-
const userProfileForCreate = auth.userProfile;
|
|
4112
|
-
if (userProfileForCreate) changeSet.creates.push({
|
|
4113
|
-
name,
|
|
4114
|
-
request: {
|
|
4115
|
-
workspaceId,
|
|
4116
|
-
namespaceName: config.name,
|
|
4117
|
-
userProfileProviderConfig: protoUserProfileConfig(userProfileForCreate)
|
|
4118
|
-
}
|
|
4119
|
-
});
|
|
4120
|
-
continue;
|
|
4121
|
-
}
|
|
4122
|
-
throw error;
|
|
4303
|
+
continue;
|
|
4123
4304
|
}
|
|
4305
|
+
const userProfileForUpdate = auth.userProfile;
|
|
4306
|
+
if (userProfileForUpdate) {
|
|
4307
|
+
const desired = protoUserProfileConfig(userProfileForUpdate);
|
|
4308
|
+
if (!forceApplyAll && areUserProfileConfigsEqual(existing.userProfileProviderConfig ?? {}, desired)) changeSet.unchanged.push({ name });
|
|
4309
|
+
else changeSet.updates.push({
|
|
4310
|
+
name,
|
|
4311
|
+
request: {
|
|
4312
|
+
workspaceId,
|
|
4313
|
+
namespaceName: config.name,
|
|
4314
|
+
userProfileProviderConfig: desired
|
|
4315
|
+
}
|
|
4316
|
+
});
|
|
4317
|
+
} else changeSet.deletes.push({
|
|
4318
|
+
name,
|
|
4319
|
+
request: {
|
|
4320
|
+
workspaceId,
|
|
4321
|
+
namespaceName: config.name
|
|
4322
|
+
}
|
|
4323
|
+
});
|
|
4124
4324
|
}
|
|
4125
4325
|
for (const namespaceName of deletedServices) {
|
|
4126
|
-
|
|
4127
|
-
await client.getUserProfileConfig({
|
|
4326
|
+
if (!await getOrNull(async () => {
|
|
4327
|
+
return await client.getUserProfileConfig({
|
|
4128
4328
|
workspaceId,
|
|
4129
4329
|
namespaceName
|
|
4130
4330
|
});
|
|
4131
|
-
}
|
|
4132
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) continue;
|
|
4133
|
-
throw error;
|
|
4134
|
-
}
|
|
4331
|
+
})) continue;
|
|
4135
4332
|
changeSet.deletes.push({
|
|
4136
4333
|
name: `${namespaceName}-user-profile-config`,
|
|
4137
4334
|
request: {
|
|
@@ -4165,54 +4362,49 @@ async function planTenantConfigs(client, workspaceId, auths, deletedServices, fo
|
|
|
4165
4362
|
for (const auth of auths) {
|
|
4166
4363
|
const { config } = auth;
|
|
4167
4364
|
const name = `${config.name}-tenant-config`;
|
|
4168
|
-
|
|
4169
|
-
|
|
4365
|
+
const existing = await getOrNull(async () => {
|
|
4366
|
+
return await client.getTenantConfig({
|
|
4170
4367
|
workspaceId,
|
|
4171
4368
|
namespaceName: config.name
|
|
4172
4369
|
});
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
else changeSet.updates.push({
|
|
4177
|
-
name,
|
|
4178
|
-
request: {
|
|
4179
|
-
workspaceId,
|
|
4180
|
-
namespaceName: config.name,
|
|
4181
|
-
tenantProviderConfig: desired
|
|
4182
|
-
}
|
|
4183
|
-
});
|
|
4184
|
-
} else changeSet.deletes.push({
|
|
4370
|
+
});
|
|
4371
|
+
if (!existing) {
|
|
4372
|
+
if (config.tenantProvider) changeSet.creates.push({
|
|
4185
4373
|
name,
|
|
4186
4374
|
request: {
|
|
4187
4375
|
workspaceId,
|
|
4188
|
-
namespaceName: config.name
|
|
4376
|
+
namespaceName: config.name,
|
|
4377
|
+
tenantProviderConfig: protoTenantConfig(config.tenantProvider)
|
|
4189
4378
|
}
|
|
4190
4379
|
});
|
|
4191
|
-
|
|
4192
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) {
|
|
4193
|
-
if (config.tenantProvider) changeSet.creates.push({
|
|
4194
|
-
name,
|
|
4195
|
-
request: {
|
|
4196
|
-
workspaceId,
|
|
4197
|
-
namespaceName: config.name,
|
|
4198
|
-
tenantProviderConfig: protoTenantConfig(config.tenantProvider)
|
|
4199
|
-
}
|
|
4200
|
-
});
|
|
4201
|
-
continue;
|
|
4202
|
-
}
|
|
4203
|
-
throw error;
|
|
4380
|
+
continue;
|
|
4204
4381
|
}
|
|
4382
|
+
if (config.tenantProvider) {
|
|
4383
|
+
const desired = protoTenantConfig(config.tenantProvider);
|
|
4384
|
+
if (!forceApplyAll && areTenantProviderConfigsEqual(existing.tenantProviderConfig, desired)) changeSet.unchanged.push({ name });
|
|
4385
|
+
else changeSet.updates.push({
|
|
4386
|
+
name,
|
|
4387
|
+
request: {
|
|
4388
|
+
workspaceId,
|
|
4389
|
+
namespaceName: config.name,
|
|
4390
|
+
tenantProviderConfig: desired
|
|
4391
|
+
}
|
|
4392
|
+
});
|
|
4393
|
+
} else changeSet.deletes.push({
|
|
4394
|
+
name,
|
|
4395
|
+
request: {
|
|
4396
|
+
workspaceId,
|
|
4397
|
+
namespaceName: config.name
|
|
4398
|
+
}
|
|
4399
|
+
});
|
|
4205
4400
|
}
|
|
4206
4401
|
for (const namespaceName of deletedServices) {
|
|
4207
|
-
|
|
4208
|
-
await client.getTenantConfig({
|
|
4402
|
+
if (!await getOrNull(async () => {
|
|
4403
|
+
return await client.getTenantConfig({
|
|
4209
4404
|
workspaceId,
|
|
4210
4405
|
namespaceName
|
|
4211
4406
|
});
|
|
4212
|
-
}
|
|
4213
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) continue;
|
|
4214
|
-
throw error;
|
|
4215
|
-
}
|
|
4407
|
+
})) continue;
|
|
4216
4408
|
changeSet.deletes.push({
|
|
4217
4409
|
name: `${namespaceName}-tenant-config`,
|
|
4218
4410
|
request: {
|
|
@@ -4239,19 +4431,14 @@ function protoTenantConfig(tenantConfig) {
|
|
|
4239
4431
|
async function planMachineUsers(client, workspaceId, auths, deletedServices, forceApplyAll = false) {
|
|
4240
4432
|
const changeSet = createChangeSet("Auth machineUsers");
|
|
4241
4433
|
const fetchMachineUsers = (authNamespace) => {
|
|
4242
|
-
return
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
return [machineUsers, nextPageToken];
|
|
4251
|
-
} catch (error) {
|
|
4252
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
4253
|
-
throw error;
|
|
4254
|
-
}
|
|
4434
|
+
return fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
4435
|
+
const { machineUsers, nextPageToken } = await client.listAuthMachineUsers({
|
|
4436
|
+
workspaceId,
|
|
4437
|
+
authNamespace,
|
|
4438
|
+
pageToken,
|
|
4439
|
+
pageSize: maxPageSize
|
|
4440
|
+
});
|
|
4441
|
+
return [machineUsers, nextPageToken];
|
|
4255
4442
|
});
|
|
4256
4443
|
};
|
|
4257
4444
|
for (const auth of auths) {
|
|
@@ -4378,19 +4565,14 @@ function areOAuth2ClientsEqual(existing, desired) {
|
|
|
4378
4565
|
async function planOAuth2Clients(client, workspaceId, auths, deletedServices, expectedLocalWebsites, forceApplyAll = false) {
|
|
4379
4566
|
const changeSet = createChangeSet("Auth oauth2Clients");
|
|
4380
4567
|
const fetchOAuth2Clients = (namespaceName) => {
|
|
4381
|
-
return
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
return [oauth2Clients, nextPageToken];
|
|
4390
|
-
} catch (error) {
|
|
4391
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
4392
|
-
throw error;
|
|
4393
|
-
}
|
|
4568
|
+
return fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
4569
|
+
const { oauth2Clients, nextPageToken } = await client.listAuthOAuth2Clients({
|
|
4570
|
+
workspaceId,
|
|
4571
|
+
namespaceName,
|
|
4572
|
+
pageToken,
|
|
4573
|
+
pageSize: maxPageSize
|
|
4574
|
+
});
|
|
4575
|
+
return [oauth2Clients, nextPageToken];
|
|
4394
4576
|
});
|
|
4395
4577
|
};
|
|
4396
4578
|
for (const auth of auths) {
|
|
@@ -4507,26 +4689,21 @@ async function planSCIMConfigs(client, workspaceId, auths, deletedServices) {
|
|
|
4507
4689
|
for (const auth of auths) {
|
|
4508
4690
|
const { config } = auth;
|
|
4509
4691
|
const name = `${config.name}-scim-config`;
|
|
4510
|
-
|
|
4511
|
-
await client.getAuthSCIMConfig({
|
|
4692
|
+
if (!await getOrNull(async () => {
|
|
4693
|
+
return await client.getAuthSCIMConfig({
|
|
4512
4694
|
workspaceId,
|
|
4513
4695
|
namespaceName: config.name
|
|
4514
4696
|
});
|
|
4515
|
-
}
|
|
4516
|
-
if (
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
continue;
|
|
4526
|
-
}
|
|
4527
|
-
throw error;
|
|
4528
|
-
}
|
|
4529
|
-
if (config.scim) changeSet.updates.push({
|
|
4697
|
+
})) {
|
|
4698
|
+
if (config.scim) changeSet.creates.push({
|
|
4699
|
+
name,
|
|
4700
|
+
request: {
|
|
4701
|
+
workspaceId,
|
|
4702
|
+
namespaceName: config.name,
|
|
4703
|
+
scimConfig: protoSCIMConfig(config.scim)
|
|
4704
|
+
}
|
|
4705
|
+
});
|
|
4706
|
+
} else if (config.scim) changeSet.updates.push({
|
|
4530
4707
|
name,
|
|
4531
4708
|
request: {
|
|
4532
4709
|
workspaceId,
|
|
@@ -4543,15 +4720,12 @@ async function planSCIMConfigs(client, workspaceId, auths, deletedServices) {
|
|
|
4543
4720
|
});
|
|
4544
4721
|
}
|
|
4545
4722
|
for (const namespaceName of deletedServices) {
|
|
4546
|
-
|
|
4547
|
-
await client.getAuthSCIMConfig({
|
|
4723
|
+
if (!await getOrNull(async () => {
|
|
4724
|
+
return await client.getAuthSCIMConfig({
|
|
4548
4725
|
workspaceId,
|
|
4549
4726
|
namespaceName
|
|
4550
4727
|
});
|
|
4551
|
-
}
|
|
4552
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) continue;
|
|
4553
|
-
throw error;
|
|
4554
|
-
}
|
|
4728
|
+
})) continue;
|
|
4555
4729
|
changeSet.deletes.push({
|
|
4556
4730
|
name: `${namespaceName}-scim-config`,
|
|
4557
4731
|
request: {
|
|
@@ -4588,16 +4762,13 @@ function protoSCIMConfig(scimConfig) {
|
|
|
4588
4762
|
async function planSCIMResources(client, workspaceId, auths, deletedServices) {
|
|
4589
4763
|
const changeSet = createChangeSet("Auth scimResources");
|
|
4590
4764
|
const fetchSCIMResources = async (namespaceName) => {
|
|
4591
|
-
|
|
4765
|
+
return await getOrNull(async () => {
|
|
4592
4766
|
const { scimResources } = await client.getAuthSCIMResources({
|
|
4593
4767
|
workspaceId,
|
|
4594
4768
|
namespaceName
|
|
4595
4769
|
});
|
|
4596
4770
|
return scimResources;
|
|
4597
|
-
}
|
|
4598
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [];
|
|
4599
|
-
throw error;
|
|
4600
|
-
}
|
|
4771
|
+
}) ?? [];
|
|
4601
4772
|
};
|
|
4602
4773
|
for (const auth of auths) {
|
|
4603
4774
|
const { config } = auth;
|
|
@@ -4755,18 +4926,14 @@ async function planAuthHooks(client, workspaceId, auths, deletedServices, forceA
|
|
|
4755
4926
|
for (const auth of auths) {
|
|
4756
4927
|
const { config } = auth;
|
|
4757
4928
|
const beforeLogin = config.hooks?.beforeLogin;
|
|
4758
|
-
|
|
4759
|
-
try {
|
|
4929
|
+
const existingHook = await getOrNull(async () => {
|
|
4760
4930
|
const { hook } = await client.getAuthHook({
|
|
4761
4931
|
workspaceId,
|
|
4762
4932
|
namespaceName: config.name,
|
|
4763
4933
|
hookPoint: AuthHookPoint.BEFORE_LOGIN
|
|
4764
4934
|
});
|
|
4765
|
-
|
|
4766
|
-
}
|
|
4767
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) existingHook = void 0;
|
|
4768
|
-
else throw error;
|
|
4769
|
-
}
|
|
4935
|
+
return hook;
|
|
4936
|
+
});
|
|
4770
4937
|
if (beforeLogin) {
|
|
4771
4938
|
const hookRequest = {
|
|
4772
4939
|
workspaceId,
|
|
@@ -4798,291 +4965,23 @@ async function planAuthHooks(client, workspaceId, auths, deletedServices, forceA
|
|
|
4798
4965
|
}
|
|
4799
4966
|
});
|
|
4800
4967
|
}
|
|
4801
|
-
for (const namespaceName of deletedServices)
|
|
4802
|
-
await client.getAuthHook({
|
|
4968
|
+
for (const namespaceName of deletedServices) if (await getOrNull(async () => {
|
|
4969
|
+
return await client.getAuthHook({
|
|
4803
4970
|
workspaceId,
|
|
4804
4971
|
namespaceName,
|
|
4805
4972
|
hookPoint: AuthHookPoint.BEFORE_LOGIN
|
|
4806
4973
|
});
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
} catch (error) {
|
|
4816
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) {} else throw error;
|
|
4817
|
-
}
|
|
4974
|
+
})) changeSet.deletes.push({
|
|
4975
|
+
name: `${namespaceName}/before-login`,
|
|
4976
|
+
request: {
|
|
4977
|
+
workspaceId,
|
|
4978
|
+
namespaceName,
|
|
4979
|
+
hookPoint: AuthHookPoint.BEFORE_LOGIN
|
|
4980
|
+
}
|
|
4981
|
+
});
|
|
4818
4982
|
return changeSet;
|
|
4819
4983
|
}
|
|
4820
4984
|
|
|
4821
|
-
//#endregion
|
|
4822
|
-
//#region src/cli/commands/deploy/config-id-injector.ts
|
|
4823
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4824
|
-
function findDefineConfigCalls(node, results) {
|
|
4825
|
-
if (!node || typeof node !== "object") return;
|
|
4826
|
-
const n = node;
|
|
4827
|
-
if (n.type === "CallExpression") {
|
|
4828
|
-
const ce = n;
|
|
4829
|
-
if (ce.callee.type === "Identifier" && ce.callee.name === "defineConfig") {
|
|
4830
|
-
const arg = ce.arguments[0];
|
|
4831
|
-
const configObj = arg && arg.type === "ObjectExpression" ? arg : null;
|
|
4832
|
-
results.push({
|
|
4833
|
-
callExpr: ce,
|
|
4834
|
-
configObj
|
|
4835
|
-
});
|
|
4836
|
-
}
|
|
4837
|
-
}
|
|
4838
|
-
for (const key of Object.keys(n)) {
|
|
4839
|
-
const child = n[key];
|
|
4840
|
-
if (Array.isArray(child)) for (const c of child) findDefineConfigCalls(c, results);
|
|
4841
|
-
else if (child && typeof child === "object") findDefineConfigCalls(child, results);
|
|
4842
|
-
}
|
|
4843
|
-
}
|
|
4844
|
-
function findIdProperty(obj) {
|
|
4845
|
-
for (const prop of obj.properties) {
|
|
4846
|
-
if (prop.type !== "Property") continue;
|
|
4847
|
-
if ((prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" ? prop.key.value : null) === "id") return prop;
|
|
4848
|
-
}
|
|
4849
|
-
return null;
|
|
4850
|
-
}
|
|
4851
|
-
/**
|
|
4852
|
-
* Ensure `tailor.config.ts` has an `id` property on the `defineConfig({...})`
|
|
4853
|
-
* argument. Generates a UUID when missing and writes it back to the file.
|
|
4854
|
-
* Returns null when the file does not contain a `defineConfig()` call (e.g.
|
|
4855
|
-
* a wrapper that re-exports another config).
|
|
4856
|
-
* @param configPath - Absolute path to the config file
|
|
4857
|
-
* @returns Resolved id and whether it was newly injected, or null if skipped
|
|
4858
|
-
*/
|
|
4859
|
-
async function ensureConfigId(configPath) {
|
|
4860
|
-
const source = await fs$1.promises.readFile(configPath, "utf-8");
|
|
4861
|
-
const { program } = parseSync(configPath, source);
|
|
4862
|
-
const calls = [];
|
|
4863
|
-
findDefineConfigCalls(program, calls);
|
|
4864
|
-
if (calls.length === 0) return null;
|
|
4865
|
-
if (calls.length > 1) throw new Error(`Multiple defineConfig() calls found in ${configPath}. Only one is supported.`);
|
|
4866
|
-
const { configObj } = assertDefined(calls[0], "defineConfig call site missing");
|
|
4867
|
-
if (!configObj) throw new Error(`defineConfig() argument must be an inline object literal in ${configPath} so the SDK can manage the 'id' field.`);
|
|
4868
|
-
const idProp = findIdProperty(configObj);
|
|
4869
|
-
if (idProp) {
|
|
4870
|
-
const value = idProp.value;
|
|
4871
|
-
if (value.type !== "Literal") throw new Error(`'id' field in ${configPath} must be a string literal. To use this config for a separate app, delete it.`);
|
|
4872
|
-
const literalValue = value.value;
|
|
4873
|
-
if (typeof literalValue !== "string" || literalValue === "") throw new Error(`'id' field in ${configPath} must be a non-empty string literal. To use this config for a separate app, delete it.`);
|
|
4874
|
-
if (!uuidRegex.test(literalValue)) throw new Error(`'id' field in ${configPath} must be a UUID. To use this config for a separate app, delete it.`);
|
|
4875
|
-
return {
|
|
4876
|
-
id: literalValue,
|
|
4877
|
-
injected: false
|
|
4878
|
-
};
|
|
4879
|
-
}
|
|
4880
|
-
const id = crypto.randomUUID();
|
|
4881
|
-
const newSource = insertIdProperty(source, configObj, id);
|
|
4882
|
-
await fs$1.promises.writeFile(configPath, newSource, "utf-8");
|
|
4883
|
-
logger.info(`Generated app id and wrote to ${configPath}: ${id}`);
|
|
4884
|
-
return {
|
|
4885
|
-
id,
|
|
4886
|
-
injected: true
|
|
4887
|
-
};
|
|
4888
|
-
}
|
|
4889
|
-
/**
|
|
4890
|
-
* Read the resolved `defineConfig({...})` `id` from a config file without
|
|
4891
|
-
* mutating it. Returns null when the file has no inline `defineConfig()` call
|
|
4892
|
-
* (wrapper/re-export config), and `{ id: null }` when the call exists but has
|
|
4893
|
-
* no usable `id` property.
|
|
4894
|
-
* @param configPath - Absolute path to the config file
|
|
4895
|
-
* @returns The existing id (or null when absent), or null for wrapper configs
|
|
4896
|
-
*/
|
|
4897
|
-
async function readConfigId(configPath) {
|
|
4898
|
-
const { program } = parseSync(configPath, await fs$1.promises.readFile(configPath, "utf-8"));
|
|
4899
|
-
const calls = [];
|
|
4900
|
-
findDefineConfigCalls(program, calls);
|
|
4901
|
-
if (calls.length === 0) return null;
|
|
4902
|
-
if (calls.length > 1) throw new Error(`Multiple defineConfig() calls found in ${configPath}. Only one is supported.`);
|
|
4903
|
-
const { configObj } = assertDefined(calls[0], "defineConfig call site missing");
|
|
4904
|
-
if (!configObj) throw new Error(`defineConfig() argument must be an inline object literal in ${configPath} so the SDK can manage the 'id' field.`);
|
|
4905
|
-
const idProp = findIdProperty(configObj);
|
|
4906
|
-
if (!idProp || idProp.value.type !== "Literal") return { id: null };
|
|
4907
|
-
const value = idProp.value.value;
|
|
4908
|
-
return { id: typeof value === "string" && value !== "" ? value : null };
|
|
4909
|
-
}
|
|
4910
|
-
/**
|
|
4911
|
-
* Read-only CI check: the config must already carry a valid app id.
|
|
4912
|
-
* Wrapper/re-export configs (no inline defineConfig call) are exempt,
|
|
4913
|
-
* mirroring the local behavior where {@link ensureConfigId} no-ops.
|
|
4914
|
-
* @param configPath - Absolute path to the config file
|
|
4915
|
-
*/
|
|
4916
|
-
async function assertConfigIdInCI(configPath) {
|
|
4917
|
-
const result = await readConfigId(configPath);
|
|
4918
|
-
if (result === null) return;
|
|
4919
|
-
if (!result.id) throw new Error("tailor.config.ts is missing an 'id'. CI does not auto-generate one (each run would be treated as a separate app and break resource ownership). Run 'tailor-sdk setup' or 'tailor-sdk apply' locally and commit the injected id.");
|
|
4920
|
-
if (!uuidRegex.test(result.id)) throw new Error(`'id' in ${configPath} must be a UUID. To use this config for a separate app, delete it.`);
|
|
4921
|
-
}
|
|
4922
|
-
/**
|
|
4923
|
-
* Ensure the config has an app id for a deploy run.
|
|
4924
|
-
*
|
|
4925
|
-
* Locally, the id is auto-injected when missing (via {@link ensureConfigId}).
|
|
4926
|
-
* In CI, the id is never auto-injected — a missing id is a hard error, because
|
|
4927
|
-
* generating one per run would create a fresh app each time and break resource
|
|
4928
|
-
* ownership. CI dry-runs (plan) perform the same check read-only, so a
|
|
4929
|
-
* forgotten id fails at PR time instead of at deploy. Ephemeral pipelines that
|
|
4930
|
-
* intentionally deploy a fresh app per run (such as e2e harnesses) can opt
|
|
4931
|
-
* back into injection with `TAILOR_PLATFORM_SDK_ALLOW_CI_ID_INJECTION=true`.
|
|
4932
|
-
* Local dry-run and build-only flows skip both injection and the check (no
|
|
4933
|
-
* on-disk side effects are expected, and build-only never talks to the
|
|
4934
|
-
* platform).
|
|
4935
|
-
* @param obj - Inputs
|
|
4936
|
-
* @param obj.configPath - Absolute path to the config file
|
|
4937
|
-
* @param obj.dryRun - Whether this is a dry-run
|
|
4938
|
-
* @param obj.buildOnly - Whether this is a build-only run
|
|
4939
|
-
*/
|
|
4940
|
-
async function ensureConfigIdForDeploy(obj) {
|
|
4941
|
-
const { configPath, dryRun, buildOnly } = obj;
|
|
4942
|
-
if (buildOnly) return;
|
|
4943
|
-
const allowCIInjection = parseBoolean(process.env.TAILOR_PLATFORM_SDK_ALLOW_CI_ID_INJECTION) === true;
|
|
4944
|
-
const strictCI = isCI && !allowCIInjection;
|
|
4945
|
-
if (dryRun) {
|
|
4946
|
-
if (strictCI) await assertConfigIdInCI(configPath);
|
|
4947
|
-
return;
|
|
4948
|
-
}
|
|
4949
|
-
if (strictCI) {
|
|
4950
|
-
await assertConfigIdInCI(configPath);
|
|
4951
|
-
return;
|
|
4952
|
-
}
|
|
4953
|
-
await ensureConfigId(configPath);
|
|
4954
|
-
}
|
|
4955
|
-
const idComment = "// SDK-managed app id — do not edit, except when copying this config to a separate app.";
|
|
4956
|
-
function insertIdProperty(source, configObj, id) {
|
|
4957
|
-
const idLiteral = `id: ${JSON.stringify(id)}`;
|
|
4958
|
-
if (configObj.properties.length > 0) {
|
|
4959
|
-
const firstProp = assertDefined(configObj.properties[0], "first property missing");
|
|
4960
|
-
const lineStart = source.lastIndexOf("\n", firstProp.start - 1) + 1;
|
|
4961
|
-
const indent = source.slice(lineStart, firstProp.start);
|
|
4962
|
-
if (!/^[\t ]*$/.test(indent)) return source.slice(0, firstProp.start) + `${idLiteral}, ` + source.slice(firstProp.start);
|
|
4963
|
-
const insertion = `${idComment}\n${indent}${idLiteral},\n${indent}`;
|
|
4964
|
-
return source.slice(0, firstProp.start) + insertion + source.slice(firstProp.start);
|
|
4965
|
-
}
|
|
4966
|
-
const openBracePos = configObj.start + 1;
|
|
4967
|
-
const braceLineStart = source.lastIndexOf("\n", configObj.start) + 1;
|
|
4968
|
-
const baseIndent = source.slice(braceLineStart).match(/^[\t ]*/)?.[0] ?? "";
|
|
4969
|
-
const innerIndent = `${baseIndent} `;
|
|
4970
|
-
const insertion = `\n${innerIndent}${idComment}\n${innerIndent}${idLiteral},\n${baseIndent}`;
|
|
4971
|
-
return source.slice(0, openBracePos) + insertion + source.slice(openBracePos);
|
|
4972
|
-
}
|
|
4973
|
-
|
|
4974
|
-
//#endregion
|
|
4975
|
-
//#region src/cli/commands/deploy/confirm.ts
|
|
4976
|
-
/**
|
|
4977
|
-
* Confirm reassignment of resources when owner conflicts are detected.
|
|
4978
|
-
* Splits into two scenarios: id regeneration (same sdk-name, different
|
|
4979
|
-
* sdk-app-id) and name mismatch (different sdk-name). Each gets its own
|
|
4980
|
-
* prompt because the user-facing meaning is different.
|
|
4981
|
-
* @param conflicts - Detected owner conflicts
|
|
4982
|
-
* @param appName - Target application name
|
|
4983
|
-
* @param yes - Whether to auto-confirm without prompting
|
|
4984
|
-
* @returns Promise that resolves when confirmation completes
|
|
4985
|
-
*/
|
|
4986
|
-
async function confirmOwnerConflict(conflicts, appName, yes) {
|
|
4987
|
-
if (conflicts.length === 0) return;
|
|
4988
|
-
const idRegenerated = conflicts.filter((c) => c.currentOwner === appName);
|
|
4989
|
-
const nameMismatches = conflicts.filter((c) => c.currentOwner !== appName);
|
|
4990
|
-
if (idRegenerated.length > 0) await confirmIdRegeneration(idRegenerated, appName, yes);
|
|
4991
|
-
if (nameMismatches.length > 0) await confirmNameMismatch(nameMismatches, appName, yes);
|
|
4992
|
-
}
|
|
4993
|
-
async function confirmIdRegeneration(conflicts, appName, yes) {
|
|
4994
|
-
logger.warn(`Application id was regenerated for "${appName}":`);
|
|
4995
|
-
logger.log(" These resources still carry the previous id.");
|
|
4996
|
-
logger.newline();
|
|
4997
|
-
logger.log(` ${styles.info("Resources")}:`);
|
|
4998
|
-
for (const c of conflicts) logger.log(` • ${styles.bold(c.resourceType)} ${styles.info(`"${c.resourceName}"`)}`);
|
|
4999
|
-
if (yes) {
|
|
5000
|
-
logger.success("Re-tagging resources with the new id (--yes flag specified)...", { mode: "plain" });
|
|
5001
|
-
return;
|
|
5002
|
-
}
|
|
5003
|
-
if (!await prompt.confirm({
|
|
5004
|
-
message: `Re-tag these resources with the new id for "${appName}"?\n${styles.dim("(The id in tailor.config.ts was removed since the previous deploy, so a new one was generated)")}`,
|
|
5005
|
-
default: false
|
|
5006
|
-
})) throw new Error(multiline`
|
|
5007
|
-
Apply cancelled. Resources remain tagged with the previous id.
|
|
5008
|
-
To override, run again and confirm, or use --yes flag.
|
|
5009
|
-
`);
|
|
5010
|
-
}
|
|
5011
|
-
async function confirmNameMismatch(conflicts, appName, yes) {
|
|
5012
|
-
const currentOwners = [...new Set(conflicts.map((c) => c.currentOwner))];
|
|
5013
|
-
logger.warn("Application name mismatch detected:");
|
|
5014
|
-
logger.log(` ${styles.warning("Current application(s)")}: ${currentOwners.map((o) => styles.bold(`"${o}"`)).join(", ")}`);
|
|
5015
|
-
logger.log(` ${styles.success("New application")}: ${styles.bold(`"${appName}"`)}`);
|
|
5016
|
-
logger.newline();
|
|
5017
|
-
logger.log(` ${styles.info("Resources")}:`);
|
|
5018
|
-
for (const c of conflicts) logger.log(` • ${styles.bold(c.resourceType)} ${styles.info(`"${c.resourceName}"`)}`);
|
|
5019
|
-
if (yes) {
|
|
5020
|
-
logger.success("Updating resources (--yes flag specified)...", { mode: "plain" });
|
|
5021
|
-
return;
|
|
5022
|
-
}
|
|
5023
|
-
const promptMessage = currentOwners.length === 1 ? `Update these resources to be managed by "${appName}"?\n${styles.dim("(Common when renaming your application)")}` : `Update these resources to be managed by "${appName}"?`;
|
|
5024
|
-
if (!await prompt.confirm({
|
|
5025
|
-
message: promptMessage,
|
|
5026
|
-
default: false
|
|
5027
|
-
})) throw new Error(multiline`
|
|
5028
|
-
Apply cancelled. Resources remain managed by their current applications.
|
|
5029
|
-
To override, run again and confirm, or use --yes flag.
|
|
5030
|
-
`);
|
|
5031
|
-
}
|
|
5032
|
-
/**
|
|
5033
|
-
* Confirm allowing tailor-sdk to manage previously unmanaged resources.
|
|
5034
|
-
* @param resources - Unmanaged resources
|
|
5035
|
-
* @param appName - Target application name
|
|
5036
|
-
* @param yes - Whether to auto-confirm without prompting
|
|
5037
|
-
* @returns Promise that resolves when confirmation completes
|
|
5038
|
-
*/
|
|
5039
|
-
async function confirmUnmanagedResources(resources, appName, yes) {
|
|
5040
|
-
if (resources.length === 0) return;
|
|
5041
|
-
logger.warn("Existing resources not tracked by tailor-sdk were found:");
|
|
5042
|
-
logger.log(` ${styles.info("Resources")}:`);
|
|
5043
|
-
for (const r of resources) logger.log(` • ${styles.bold(r.resourceType)} ${styles.info(`"${r.resourceName}"`)}`);
|
|
5044
|
-
logger.newline();
|
|
5045
|
-
logger.log(" These resources may have been created by older SDK versions, Terraform, or CUE.");
|
|
5046
|
-
logger.log(" To continue, confirm that tailor-sdk should manage them.");
|
|
5047
|
-
logger.log(" If they are managed by another tool (e.g., Terraform), cancel and manage them there instead.");
|
|
5048
|
-
if (yes) {
|
|
5049
|
-
logger.success(`Adding to "${appName}" (--yes flag specified)...`, { mode: "plain" });
|
|
5050
|
-
return;
|
|
5051
|
-
}
|
|
5052
|
-
if (!await prompt.confirm({
|
|
5053
|
-
message: `Allow tailor-sdk to manage these resources for "${appName}"?`,
|
|
5054
|
-
default: false
|
|
5055
|
-
})) throw new Error(multiline`
|
|
5056
|
-
Apply cancelled. Resources remain unmanaged.
|
|
5057
|
-
To override, run again and confirm, or use --yes flag.
|
|
5058
|
-
`);
|
|
5059
|
-
}
|
|
5060
|
-
/**
|
|
5061
|
-
* Confirm deletion of important resources.
|
|
5062
|
-
* @param resources - Resources scheduled for deletion
|
|
5063
|
-
* @param yes - Whether to auto-confirm without prompting
|
|
5064
|
-
* @returns Promise that resolves when confirmation completes
|
|
5065
|
-
*/
|
|
5066
|
-
async function confirmImportantResourceDeletion(resources, yes) {
|
|
5067
|
-
if (resources.length === 0) return;
|
|
5068
|
-
logger.warn("The following resources will be deleted:");
|
|
5069
|
-
logger.log(` ${styles.info("Resources")}:`);
|
|
5070
|
-
for (const r of resources) logger.log(` • ${styles.bold(r.resourceType)} ${styles.error(`"${r.resourceName}"`)}`);
|
|
5071
|
-
logger.newline();
|
|
5072
|
-
logger.log(styles.warning(" Deleting these resources will permanently remove all associated data."));
|
|
5073
|
-
if (yes) {
|
|
5074
|
-
logger.success("Deleting resources (--yes flag specified)...", { mode: "plain" });
|
|
5075
|
-
return;
|
|
5076
|
-
}
|
|
5077
|
-
if (!await prompt.confirm({
|
|
5078
|
-
message: "Are you sure you want to delete these resources?",
|
|
5079
|
-
default: false
|
|
5080
|
-
})) throw new Error(multiline`
|
|
5081
|
-
Apply cancelled. Resources will not be deleted.
|
|
5082
|
-
To override, run again and confirm, or use --yes flag.
|
|
5083
|
-
`);
|
|
5084
|
-
}
|
|
5085
|
-
|
|
5086
4985
|
//#endregion
|
|
5087
4986
|
//#region src/cli/commands/deploy/auth-invoker.ts
|
|
5088
4987
|
/**
|
|
@@ -5161,7 +5060,7 @@ async function planExecutor(context) {
|
|
|
5161
5060
|
appName: application.name,
|
|
5162
5061
|
appId: application.id
|
|
5163
5062
|
});
|
|
5164
|
-
const desiredExecutor = protoExecutor(
|
|
5063
|
+
const desiredExecutor = protoExecutor(context, executor);
|
|
5165
5064
|
if (existing) {
|
|
5166
5065
|
if (trackDesiredResourceOwnership({
|
|
5167
5066
|
labels: existing.allLabels,
|
|
@@ -5293,35 +5192,101 @@ function normalizeComparableExecutor(executor) {
|
|
|
5293
5192
|
function areExecutorsEqual(existing, desired) {
|
|
5294
5193
|
return areNormalizedEqual(normalizeComparableExecutor(existing), normalizeComparableExecutor(desired));
|
|
5295
5194
|
}
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5195
|
+
/**
|
|
5196
|
+
* Resolve a resource's namespace from same-run peer configs, throwing when the
|
|
5197
|
+
* name is claimed by more than one namespace in the deploy run.
|
|
5198
|
+
* @param sameRunNamespaces - Namespaces keyed by resource name across the deploy run
|
|
5199
|
+
* @param resourceName - Resource name to resolve
|
|
5200
|
+
* @param resourceLabel - Resource label used in error messages
|
|
5201
|
+
* @returns The owning namespace, or undefined when the name is unknown
|
|
5202
|
+
*/
|
|
5203
|
+
function resolveSameRunNamespace(sameRunNamespaces, resourceName, resourceLabel) {
|
|
5204
|
+
if (!sameRunNamespaces?.has(resourceName)) return;
|
|
5205
|
+
const namespace = sameRunNamespaces.get(resourceName);
|
|
5206
|
+
if (!namespace) throw new Error(`${resourceLabel} "${resourceName}" is defined in multiple namespaces in this deploy run. Move the trigger to the application that owns it or use unique names.`);
|
|
5207
|
+
return namespace;
|
|
5208
|
+
}
|
|
5209
|
+
/**
|
|
5210
|
+
* Find the local TailorDB namespace that declares the given type.
|
|
5211
|
+
* @param application - Loaded application
|
|
5212
|
+
* @param typeName - TailorDB type name to look up
|
|
5213
|
+
* @returns The declaring namespace, or undefined when no local service has it
|
|
5214
|
+
*/
|
|
5215
|
+
function findTailorDBNamespace(application, typeName) {
|
|
5216
|
+
return application.tailorDBServices.find((service) => Object.hasOwn(service.types, typeName))?.namespace;
|
|
5217
|
+
}
|
|
5218
|
+
/**
|
|
5219
|
+
* Find the local resolver namespace that declares the given resolver.
|
|
5220
|
+
* @param application - Loaded application
|
|
5221
|
+
* @param resolverName - Resolver name to look up
|
|
5222
|
+
* @returns The declaring namespace, or undefined when no local service has it
|
|
5223
|
+
*/
|
|
5224
|
+
function findResolverNamespace(application, resolverName) {
|
|
5225
|
+
return application.resolverServices.find((service) => Object.values(service.resolvers).some((resolver) => resolver.name === resolverName))?.namespace;
|
|
5226
|
+
}
|
|
5227
|
+
function resolveNamespace(params) {
|
|
5228
|
+
const { resourceLabel, resourceName, localNamespaces, findLocalNamespace, sameRunNamespaces } = params;
|
|
5229
|
+
const localNamespace = findLocalNamespace();
|
|
5230
|
+
if (localNamespace !== void 0) return localNamespace;
|
|
5231
|
+
const sameRunNamespace = resolveSameRunNamespace(sameRunNamespaces, resourceName, resourceLabel);
|
|
5232
|
+
if (sameRunNamespace !== void 0) return sameRunNamespace;
|
|
5233
|
+
const sameRunAvailableNamespaces = sameRunNamespaces ? [...new Set([...sameRunNamespaces.values()].filter((value) => value !== void 0))] : [];
|
|
5234
|
+
const availableNamespaces = [...localNamespaces, ...sameRunAvailableNamespaces];
|
|
5235
|
+
throw new Error(`${resourceLabel} "${resourceName}" not found in any namespace. Available namespaces: ${availableNamespaces.join(", ")}`);
|
|
5236
|
+
}
|
|
5237
|
+
function resolveTailorDBNamespace(application, typeName, sameRunNamespaces) {
|
|
5238
|
+
return resolveNamespace({
|
|
5239
|
+
resourceLabel: "TailorDB type",
|
|
5240
|
+
resourceName: typeName,
|
|
5241
|
+
localNamespaces: application.tailorDBServices.map((service) => service.namespace),
|
|
5242
|
+
findLocalNamespace: () => findTailorDBNamespace(application, typeName),
|
|
5243
|
+
sameRunNamespaces
|
|
5244
|
+
});
|
|
5299
5245
|
}
|
|
5300
|
-
function resolveResolverNamespace(application, resolverName) {
|
|
5301
|
-
|
|
5302
|
-
|
|
5246
|
+
function resolveResolverNamespace(application, resolverName, sameRunNamespaces) {
|
|
5247
|
+
return resolveNamespace({
|
|
5248
|
+
resourceLabel: "Resolver",
|
|
5249
|
+
resourceName: resolverName,
|
|
5250
|
+
localNamespaces: application.resolverServices.map((service) => service.namespace),
|
|
5251
|
+
findLocalNamespace: () => findResolverNamespace(application, resolverName),
|
|
5252
|
+
sameRunNamespaces
|
|
5253
|
+
});
|
|
5303
5254
|
}
|
|
5304
|
-
|
|
5305
|
-
|
|
5255
|
+
/**
|
|
5256
|
+
* Collect IdP names declared by the application, including external IdP
|
|
5257
|
+
* subgraphs.
|
|
5258
|
+
* @param application - Loaded application
|
|
5259
|
+
* @returns IdP names visible from the application config
|
|
5260
|
+
*/
|
|
5261
|
+
function collectApplicationIdpNames(application) {
|
|
5262
|
+
const names = new Set(application.idpServices.map((idp) => idp.name));
|
|
5263
|
+
for (const subgraph of application.subgraphs) if (subgraph.Type === "idp") names.add(subgraph.Name);
|
|
5264
|
+
return names;
|
|
5265
|
+
}
|
|
5266
|
+
function resolveIdpNamespace(application, executorName, idpName, sameRunIdpNames) {
|
|
5267
|
+
const localIdpNames = collectApplicationIdpNames(application);
|
|
5306
5268
|
if (idpName !== void 0) {
|
|
5307
|
-
const
|
|
5308
|
-
if (!
|
|
5309
|
-
const available =
|
|
5269
|
+
const candidateNames = sameRunIdpNames ?? localIdpNames;
|
|
5270
|
+
if (!candidateNames.has(idpName)) {
|
|
5271
|
+
const available = [...candidateNames].join(", ");
|
|
5310
5272
|
throw new Error(`Executor "${executorName}" specifies IdP "${idpName}" in its idpUser trigger, but no IdP with that name is configured. Available IdPs: ${available}`);
|
|
5311
5273
|
}
|
|
5312
|
-
return
|
|
5274
|
+
return idpName;
|
|
5313
5275
|
}
|
|
5314
|
-
if (
|
|
5315
|
-
|
|
5276
|
+
if (localIdpNames.size === 0) throw new Error(`Executor "${executorName}" uses an idpUser trigger but no IdP is configured.`);
|
|
5277
|
+
if (localIdpNames.size > 1) {
|
|
5278
|
+
const available = [...localIdpNames].join(", ");
|
|
5316
5279
|
throw new Error(`Executor "${executorName}" uses an idpUser trigger but the project defines multiple IdPs (${available}). Specify which IdP to subscribe to via the trigger's "idp" option.`);
|
|
5317
5280
|
}
|
|
5318
|
-
return assertDefined(
|
|
5281
|
+
return assertDefined([...localIdpNames][0], "idp service missing");
|
|
5319
5282
|
}
|
|
5320
5283
|
function resolveAuthNamespace(application) {
|
|
5321
|
-
|
|
5322
|
-
|
|
5284
|
+
const authNamespace = application.authService?.config.name ?? application.config.auth?.name;
|
|
5285
|
+
if (!authNamespace) throw new Error("No Auth service configured");
|
|
5286
|
+
return authNamespace;
|
|
5323
5287
|
}
|
|
5324
|
-
function protoExecutor(
|
|
5288
|
+
function protoExecutor(context, executor) {
|
|
5289
|
+
const { application } = context;
|
|
5325
5290
|
const appName = application.name;
|
|
5326
5291
|
const env = application.env;
|
|
5327
5292
|
const trigger = executor.trigger;
|
|
@@ -5351,7 +5316,7 @@ function protoExecutor(application, executor) {
|
|
|
5351
5316
|
case: "tailordb",
|
|
5352
5317
|
value: {
|
|
5353
5318
|
eventTypes: trigger.events,
|
|
5354
|
-
namespaceName: resolveTailorDBNamespace(application, trigger.typeName),
|
|
5319
|
+
namespaceName: resolveTailorDBNamespace(application, trigger.typeName, context.tailorDBTypeNamespaces),
|
|
5355
5320
|
typeName: trigger.typeName,
|
|
5356
5321
|
...trigger.condition ? { condition: { expr: `(${stringifyFunction(trigger.condition)})(${argsExpr})` } } : {}
|
|
5357
5322
|
}
|
|
@@ -5363,7 +5328,7 @@ function protoExecutor(application, executor) {
|
|
|
5363
5328
|
case: "pipeline",
|
|
5364
5329
|
value: {
|
|
5365
5330
|
eventTypes: ["pipeline.resolver.executed"],
|
|
5366
|
-
namespaceName: resolveResolverNamespace(application, trigger.resolverName),
|
|
5331
|
+
namespaceName: resolveResolverNamespace(application, trigger.resolverName, context.resolverNamespaces),
|
|
5367
5332
|
resolverName: trigger.resolverName,
|
|
5368
5333
|
...trigger.condition ? { condition: { expr: `(${stringifyFunction(trigger.condition)})(${argsExpr})` } } : {}
|
|
5369
5334
|
}
|
|
@@ -5385,7 +5350,7 @@ function protoExecutor(application, executor) {
|
|
|
5385
5350
|
case: "idp",
|
|
5386
5351
|
value: {
|
|
5387
5352
|
eventTypes: trigger.events,
|
|
5388
|
-
namespaceName: resolveIdpNamespace(application, executor.name, trigger.idp)
|
|
5353
|
+
namespaceName: resolveIdpNamespace(application, executor.name, trigger.idp, context.idpNames)
|
|
5389
5354
|
}
|
|
5390
5355
|
});
|
|
5391
5356
|
break;
|
|
@@ -5560,7 +5525,8 @@ async function planPipeline(context) {
|
|
|
5560
5525
|
}
|
|
5561
5526
|
const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
|
|
5562
5527
|
const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices$1(client, workspaceId, application.name, application.id, pipelines);
|
|
5563
|
-
const
|
|
5528
|
+
const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
|
|
5529
|
+
const { changeSet: resolverChangeSet } = await planResolvers(client, workspaceId, pipelines, executors, context.executorUsedResolvers ?? /* @__PURE__ */ new Set(), deletedServices, application.env, application.authService?.config.name, forceApplyAll);
|
|
5564
5530
|
return {
|
|
5565
5531
|
changeSet: {
|
|
5566
5532
|
service: serviceChangeSet,
|
|
@@ -5597,17 +5563,16 @@ async function planServices$1(client, workspaceId, appName, appId, pipelines) {
|
|
|
5597
5563
|
appId
|
|
5598
5564
|
});
|
|
5599
5565
|
if (existing) {
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
else conflicts.push({
|
|
5566
|
+
if (trackDesiredResourceOwnership({
|
|
5567
|
+
labels: existing.allLabels,
|
|
5568
|
+
ownerLabel: existing.label,
|
|
5569
|
+
appName,
|
|
5570
|
+
appId,
|
|
5606
5571
|
resourceType: "Pipeline service",
|
|
5607
5572
|
resourceName: pipeline.namespace,
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5573
|
+
conflicts,
|
|
5574
|
+
unmanaged
|
|
5575
|
+
}) && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) changeSet.unchanged.push({ name: pipeline.namespace });
|
|
5611
5576
|
else changeSet.updates.push({
|
|
5612
5577
|
name: pipeline.namespace,
|
|
5613
5578
|
request: {
|
|
@@ -5628,10 +5593,13 @@ async function planServices$1(client, workspaceId, appName, appId, pipelines) {
|
|
|
5628
5593
|
}
|
|
5629
5594
|
Object.entries(existingServices).forEach(([namespaceName]) => {
|
|
5630
5595
|
const entry = existingServices[namespaceName];
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5596
|
+
if (trackRemainingResourceOwner({
|
|
5597
|
+
labels: entry?.allLabels,
|
|
5598
|
+
ownerLabel: entry?.label,
|
|
5599
|
+
appName,
|
|
5600
|
+
appId,
|
|
5601
|
+
resourceOwners
|
|
5602
|
+
})) changeSet.deletes.push({
|
|
5635
5603
|
name: namespaceName,
|
|
5636
5604
|
request: {
|
|
5637
5605
|
workspaceId,
|
|
@@ -5646,25 +5614,20 @@ async function planServices$1(client, workspaceId, appName, appId, pipelines) {
|
|
|
5646
5614
|
resourceOwners
|
|
5647
5615
|
};
|
|
5648
5616
|
}
|
|
5649
|
-
async function planResolvers(client, workspaceId, pipelines, executors, deletedServices, env, authNamespace, forceApplyAll = false) {
|
|
5617
|
+
async function planResolvers(client, workspaceId, pipelines, executors, initialExecutorUsedResolvers, deletedServices, env, authNamespace, forceApplyAll = false) {
|
|
5650
5618
|
const changeSet = createChangeSet("Pipeline resolvers");
|
|
5651
5619
|
const fetchResolvers = (namespaceName) => {
|
|
5652
|
-
return
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
return [pipelineResolvers, nextPageToken];
|
|
5661
|
-
} catch (error) {
|
|
5662
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
5663
|
-
throw error;
|
|
5664
|
-
}
|
|
5620
|
+
return fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
5621
|
+
const { pipelineResolvers, nextPageToken } = await client.listPipelineResolvers({
|
|
5622
|
+
workspaceId,
|
|
5623
|
+
namespaceName,
|
|
5624
|
+
pageToken,
|
|
5625
|
+
pageSize: maxPageSize
|
|
5626
|
+
});
|
|
5627
|
+
return [pipelineResolvers, nextPageToken];
|
|
5665
5628
|
});
|
|
5666
5629
|
};
|
|
5667
|
-
const executorUsedResolvers =
|
|
5630
|
+
const executorUsedResolvers = new Set(initialExecutorUsedResolvers);
|
|
5668
5631
|
for (const executor of executors) if (executor.trigger.kind === "resolverExecuted") executorUsedResolvers.add(executor.trigger.resolverName);
|
|
5669
5632
|
for (const pipeline of pipelines) for (const resolver of Object.values(pipeline.resolvers)) if (executorUsedResolvers.has(resolver.name) && resolver.publishEvents === false) throw new Error(`Resolver "${resolver.name}" has publishEvents set to false, but it is used by an executor with a resolverExecuted trigger. Either remove the publishEvents: false setting or remove the executor trigger for this resolver.`);
|
|
5670
5633
|
for (const pipeline of pipelines) {
|
|
@@ -5844,453 +5807,249 @@ function protoFields(fields, baseName, isInput) {
|
|
|
5844
5807
|
description: field.metadata.description,
|
|
5845
5808
|
array: field.metadata.array ?? false,
|
|
5846
5809
|
required,
|
|
5847
|
-
type
|
|
5848
|
-
};
|
|
5849
|
-
});
|
|
5850
|
-
}
|
|
5851
|
-
|
|
5852
|
-
//#endregion
|
|
5853
|
-
//#region src/cli/commands/deploy/secret-manager.ts
|
|
5854
|
-
/**
|
|
5855
|
-
* Build the CreateSecretManagerVault request for a planned vault create.
|
|
5856
|
-
* @param create - Planned vault create
|
|
5857
|
-
* @returns Request init shape
|
|
5858
|
-
*/
|
|
5859
|
-
function vaultCreateRequest(create) {
|
|
5860
|
-
return {
|
|
5861
|
-
workspaceId: create.workspaceId,
|
|
5862
|
-
secretmanagerVaultName: create.name
|
|
5863
|
-
};
|
|
5864
|
-
}
|
|
5865
|
-
/**
|
|
5866
|
-
* Build the CreateSecretManagerSecret request for a planned secret create.
|
|
5867
|
-
* @param create - Planned secret create
|
|
5868
|
-
* @returns Request init shape
|
|
5869
|
-
*/
|
|
5870
|
-
function secretCreateRequest(create) {
|
|
5871
|
-
return {
|
|
5872
|
-
workspaceId: create.workspaceId,
|
|
5873
|
-
secretmanagerVaultName: create.vaultName,
|
|
5874
|
-
secretmanagerSecretName: create.secretName,
|
|
5875
|
-
secretmanagerSecretValue: create.value
|
|
5876
|
-
};
|
|
5877
|
-
}
|
|
5878
|
-
/**
|
|
5879
|
-
* Build the UpdateSecretManagerSecret request for a planned secret update.
|
|
5880
|
-
* @param update - Planned secret update
|
|
5881
|
-
* @returns Request init shape
|
|
5882
|
-
*/
|
|
5883
|
-
function secretUpdateRequest(update) {
|
|
5884
|
-
return {
|
|
5885
|
-
workspaceId: update.workspaceId,
|
|
5886
|
-
secretmanagerVaultName: update.vaultName,
|
|
5887
|
-
secretmanagerSecretName: update.secretName,
|
|
5888
|
-
secretmanagerSecretValue: update.value
|
|
5889
|
-
};
|
|
5890
|
-
}
|
|
5891
|
-
/**
|
|
5892
|
-
* Plan secret manager changes based on current and desired state.
|
|
5893
|
-
* @param context - Planning context
|
|
5894
|
-
* @returns Planned changes for vaults and secrets
|
|
5895
|
-
*/
|
|
5896
|
-
async function planSecretManager(context) {
|
|
5897
|
-
const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
|
|
5898
|
-
const secretVaults = forRemoval ? [] : application.secrets;
|
|
5899
|
-
const vaultChangeSet = createChangeSet("Secret Manager vaults");
|
|
5900
|
-
const secretChangeSet = createChangeSet("Secret Manager secrets");
|
|
5901
|
-
const conflicts = [];
|
|
5902
|
-
const unmanaged = [];
|
|
5903
|
-
const resourceOwners = /* @__PURE__ */ new Set();
|
|
5904
|
-
const existingVaults = await fetchExistingResourcesWithLabels({
|
|
5905
|
-
client,
|
|
5906
|
-
fetchPage: async (pageToken, maxPageSize) => {
|
|
5907
|
-
const { vaults, nextPageToken } = await client.listSecretManagerVaults({
|
|
5908
|
-
workspaceId,
|
|
5909
|
-
pageToken,
|
|
5910
|
-
pageSize: maxPageSize
|
|
5911
|
-
});
|
|
5912
|
-
return [vaults, nextPageToken];
|
|
5913
|
-
},
|
|
5914
|
-
getName: (resource) => resource.name,
|
|
5915
|
-
getTrn: (name) => resourceTrn(workspaceId, "vault", name)
|
|
5916
|
-
});
|
|
5917
|
-
const state = loadSecretsState();
|
|
5918
|
-
const skippedSecrets = [];
|
|
5919
|
-
await Promise.all(secretVaults.map(async (vault) => {
|
|
5920
|
-
const vaultName = vault.vaultName;
|
|
5921
|
-
const existing = existingVaults[vaultName];
|
|
5922
|
-
if (existing) {
|
|
5923
|
-
const metaRequest = await buildMetaRequest({
|
|
5924
|
-
trn: resourceTrn(workspaceId, "vault", vaultName),
|
|
5925
|
-
appName: application.name,
|
|
5926
|
-
appId: application.id
|
|
5927
|
-
});
|
|
5928
|
-
const owned = isOwnedByApp(existing.allLabels, application.name, application.id);
|
|
5929
|
-
if (!owned) if (!existing.label) unmanaged.push({
|
|
5930
|
-
resourceType: "Secret Manager vault",
|
|
5931
|
-
resourceName: vaultName
|
|
5932
|
-
});
|
|
5933
|
-
else conflicts.push({
|
|
5934
|
-
resourceType: "Secret Manager vault",
|
|
5935
|
-
resourceName: vaultName,
|
|
5936
|
-
currentOwner: existing.label
|
|
5937
|
-
});
|
|
5938
|
-
if (owned && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) vaultChangeSet.unchanged.push({ name: vaultName });
|
|
5939
|
-
else vaultChangeSet.updates.push({
|
|
5940
|
-
name: vaultName,
|
|
5941
|
-
workspaceId
|
|
5942
|
-
});
|
|
5943
|
-
delete existingVaults[vaultName];
|
|
5944
|
-
} else vaultChangeSet.creates.push({
|
|
5945
|
-
name: vaultName,
|
|
5946
|
-
workspaceId
|
|
5947
|
-
});
|
|
5948
|
-
let existingSecrets = [];
|
|
5949
|
-
if (existing) existingSecrets = (await fetchAll(async (pageToken, maxPageSize) => {
|
|
5950
|
-
try {
|
|
5951
|
-
const { secrets, nextPageToken } = await client.listSecretManagerSecrets({
|
|
5952
|
-
workspaceId,
|
|
5953
|
-
secretmanagerVaultName: vaultName,
|
|
5954
|
-
pageToken,
|
|
5955
|
-
pageSize: maxPageSize
|
|
5956
|
-
});
|
|
5957
|
-
return [secrets, nextPageToken];
|
|
5958
|
-
} catch (error) {
|
|
5959
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
5960
|
-
throw error;
|
|
5961
|
-
}
|
|
5962
|
-
})).map((s) => s.name);
|
|
5963
|
-
const existingSet = new Set(existingSecrets);
|
|
5964
|
-
for (const secret of vault.secrets) {
|
|
5965
|
-
if (secret.value == null) {
|
|
5966
|
-
existingSet.delete(secret.name);
|
|
5967
|
-
skippedSecrets.push(`${vaultName}/${secret.name}`);
|
|
5968
|
-
continue;
|
|
5969
|
-
}
|
|
5970
|
-
if (existingSet.has(secret.name)) {
|
|
5971
|
-
const currentHash = hashValue(secret.value);
|
|
5972
|
-
const storedHash = state.vaults[vaultName]?.[secret.name];
|
|
5973
|
-
if (forceApplyAll || currentHash !== storedHash) secretChangeSet.updates.push({
|
|
5974
|
-
name: `${vaultName}/${secret.name}`,
|
|
5975
|
-
secretName: secret.name,
|
|
5976
|
-
workspaceId,
|
|
5977
|
-
vaultName,
|
|
5978
|
-
value: secret.value
|
|
5979
|
-
});
|
|
5980
|
-
existingSet.delete(secret.name);
|
|
5981
|
-
} else secretChangeSet.creates.push({
|
|
5982
|
-
name: `${vaultName}/${secret.name}`,
|
|
5983
|
-
secretName: secret.name,
|
|
5984
|
-
workspaceId,
|
|
5985
|
-
vaultName,
|
|
5986
|
-
value: secret.value
|
|
5987
|
-
});
|
|
5988
|
-
}
|
|
5989
|
-
for (const orphanName of existingSet) secretChangeSet.deletes.push({
|
|
5990
|
-
name: `${vaultName}/${orphanName}`,
|
|
5991
|
-
secretName: orphanName,
|
|
5992
|
-
workspaceId,
|
|
5993
|
-
vaultName
|
|
5994
|
-
});
|
|
5995
|
-
}));
|
|
5996
|
-
for (const [name, entry] of Object.entries(existingVaults)) {
|
|
5997
|
-
if (!entry) continue;
|
|
5998
|
-
const label = entry.label;
|
|
5999
|
-
const owned = isOwnedByApp(entry.allLabels, application.name, application.id);
|
|
6000
|
-
if (label && !owned) resourceOwners.add(label);
|
|
6001
|
-
if (owned) {
|
|
6002
|
-
const secrets = await fetchAll(async (pageToken, maxPageSize) => {
|
|
6003
|
-
try {
|
|
6004
|
-
const { secrets, nextPageToken } = await client.listSecretManagerSecrets({
|
|
6005
|
-
workspaceId,
|
|
6006
|
-
secretmanagerVaultName: name,
|
|
6007
|
-
pageToken,
|
|
6008
|
-
pageSize: maxPageSize
|
|
6009
|
-
});
|
|
6010
|
-
return [secrets, nextPageToken];
|
|
6011
|
-
} catch (error) {
|
|
6012
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
6013
|
-
throw error;
|
|
6014
|
-
}
|
|
6015
|
-
});
|
|
6016
|
-
for (const secret of secrets) secretChangeSet.deletes.push({
|
|
6017
|
-
name: `${name}/${secret.name}`,
|
|
6018
|
-
secretName: secret.name,
|
|
6019
|
-
workspaceId,
|
|
6020
|
-
vaultName: name
|
|
6021
|
-
});
|
|
6022
|
-
vaultChangeSet.deletes.push({
|
|
6023
|
-
name,
|
|
6024
|
-
workspaceId
|
|
6025
|
-
});
|
|
6026
|
-
}
|
|
6027
|
-
}
|
|
5810
|
+
type
|
|
5811
|
+
};
|
|
5812
|
+
});
|
|
5813
|
+
}
|
|
5814
|
+
|
|
5815
|
+
//#endregion
|
|
5816
|
+
//#region src/cli/commands/deploy/secret-manager.ts
|
|
5817
|
+
/**
|
|
5818
|
+
* Build the CreateSecretManagerVault request for a planned vault create.
|
|
5819
|
+
* @param create - Planned vault create
|
|
5820
|
+
* @returns Request init shape
|
|
5821
|
+
*/
|
|
5822
|
+
function vaultCreateRequest(create) {
|
|
6028
5823
|
return {
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
skippedSecrets,
|
|
6032
|
-
conflicts,
|
|
6033
|
-
unmanaged,
|
|
6034
|
-
resourceOwners
|
|
5824
|
+
workspaceId: create.workspaceId,
|
|
5825
|
+
secretmanagerVaultName: create.name
|
|
6035
5826
|
};
|
|
6036
5827
|
}
|
|
6037
5828
|
/**
|
|
6038
|
-
*
|
|
6039
|
-
* @param
|
|
6040
|
-
* @
|
|
6041
|
-
* @param phase - Apply phase
|
|
6042
|
-
* @param application - Application to read secrets from for hash state persistence
|
|
6043
|
-
* @returns Promise that resolves when secret changes are applied
|
|
5829
|
+
* Build the CreateSecretManagerSecret request for a planned secret create.
|
|
5830
|
+
* @param create - Planned secret create
|
|
5831
|
+
* @returns Request init shape
|
|
6044
5832
|
*/
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
trn: resourceTrn(create.workspaceId, "vault", create.name),
|
|
6053
|
-
appName: application.name,
|
|
6054
|
-
appId: application.id
|
|
6055
|
-
});
|
|
6056
|
-
await client.setMetadata(metaRequest);
|
|
6057
|
-
}
|
|
6058
|
-
}));
|
|
6059
|
-
if (application) await Promise.all(vaultChangeSet.updates.map(async (update) => {
|
|
6060
|
-
const metaRequest = await buildMetaRequest({
|
|
6061
|
-
trn: resourceTrn(update.workspaceId, "vault", update.name),
|
|
6062
|
-
appName: application.name,
|
|
6063
|
-
appId: application.id
|
|
6064
|
-
});
|
|
6065
|
-
await client.setMetadata(metaRequest);
|
|
6066
|
-
}));
|
|
6067
|
-
await Promise.all(secretChangeSet.creates.map((create) => client.createSecretManagerSecret(secretCreateRequest(create))));
|
|
6068
|
-
await Promise.all(secretChangeSet.updates.map((update) => client.updateSecretManagerSecret(secretUpdateRequest(update))));
|
|
6069
|
-
if (application) {
|
|
6070
|
-
const state = loadSecretsState();
|
|
6071
|
-
for (const vault of application.secrets) {
|
|
6072
|
-
if (!Object.hasOwn(state.vaults, vault.vaultName)) state.vaults[vault.vaultName] = {};
|
|
6073
|
-
for (const secret of vault.secrets) if (secret.value != null) assertDefined(state.vaults[vault.vaultName], "vault state entry missing")[secret.name] = hashValue(secret.value);
|
|
6074
|
-
}
|
|
6075
|
-
saveSecretsState(state);
|
|
6076
|
-
}
|
|
6077
|
-
} else {
|
|
6078
|
-
await Promise.all(secretChangeSet.deletes.map((del) => client.deleteSecretManagerSecret({
|
|
6079
|
-
workspaceId: del.workspaceId,
|
|
6080
|
-
secretmanagerVaultName: del.vaultName,
|
|
6081
|
-
secretmanagerSecretName: del.secretName
|
|
6082
|
-
})));
|
|
6083
|
-
await Promise.all(vaultChangeSet.deletes.map((del) => client.deleteSecretManagerVault({
|
|
6084
|
-
workspaceId: del.workspaceId,
|
|
6085
|
-
secretmanagerVaultName: del.name
|
|
6086
|
-
})));
|
|
6087
|
-
if (secretChangeSet.deletes.length > 0 || vaultChangeSet.deletes.length > 0) {
|
|
6088
|
-
const state = loadSecretsState();
|
|
6089
|
-
for (const del of secretChangeSet.deletes) if (Object.hasOwn(state.vaults, del.vaultName)) {
|
|
6090
|
-
delete assertDefined(state.vaults[del.vaultName], "vault state entry missing")[del.secretName];
|
|
6091
|
-
if (Object.keys(assertDefined(state.vaults[del.vaultName], "vault state entry missing")).length === 0) delete state.vaults[del.vaultName];
|
|
6092
|
-
}
|
|
6093
|
-
for (const del of vaultChangeSet.deletes) delete state.vaults[del.name];
|
|
6094
|
-
saveSecretsState(state);
|
|
6095
|
-
}
|
|
6096
|
-
}
|
|
5833
|
+
function secretCreateRequest(create) {
|
|
5834
|
+
return {
|
|
5835
|
+
workspaceId: create.workspaceId,
|
|
5836
|
+
secretmanagerVaultName: create.vaultName,
|
|
5837
|
+
secretmanagerSecretName: create.secretName,
|
|
5838
|
+
secretmanagerSecretValue: create.value
|
|
5839
|
+
};
|
|
6097
5840
|
}
|
|
6098
|
-
|
|
6099
|
-
//#endregion
|
|
6100
|
-
//#region src/cli/commands/deploy/staticwebsite.ts
|
|
6101
5841
|
/**
|
|
6102
|
-
*
|
|
6103
|
-
* @param
|
|
6104
|
-
* @
|
|
6105
|
-
* @param phase - Apply phase
|
|
6106
|
-
* @returns Promise that resolves when static websites are applied
|
|
5842
|
+
* Build the UpdateSecretManagerSecret request for a planned secret update.
|
|
5843
|
+
* @param update - Planned secret update
|
|
5844
|
+
* @returns Request init shape
|
|
6107
5845
|
*/
|
|
6108
|
-
|
|
6109
|
-
const { changeSet, customDomainChangeSet } = result;
|
|
6110
|
-
if (phase === "create-update") {
|
|
6111
|
-
await Promise.all([...changeSet.creates.map(async (create) => {
|
|
6112
|
-
await client.createStaticWebsite(create.request);
|
|
6113
|
-
await client.setMetadata(create.metaRequest);
|
|
6114
|
-
}), ...changeSet.updates.map(async (update) => {
|
|
6115
|
-
await client.updateStaticWebsite(update.request);
|
|
6116
|
-
await client.setMetadata(update.metaRequest);
|
|
6117
|
-
})]);
|
|
6118
|
-
await Promise.all([...customDomainChangeSet.creates.map(async (add) => {
|
|
6119
|
-
await client.addCustomDomain(add.request);
|
|
6120
|
-
await client.setMetadata(add.metaRequest);
|
|
6121
|
-
}), ...customDomainChangeSet.deletes.map((del) => client.removeCustomDomain(del.request))]);
|
|
6122
|
-
} else await Promise.all(changeSet.deletes.map((del) => client.deleteStaticWebsite(del.request)));
|
|
6123
|
-
}
|
|
6124
|
-
function customDomainTrn(workspaceId, websiteName, domain) {
|
|
6125
|
-
return `trn:v1:workspace:${workspaceId}:staticwebsite:${websiteName}:custom_domain:${domain}`;
|
|
6126
|
-
}
|
|
6127
|
-
function normalizeComparableStaticWebsiteShape(input) {
|
|
5846
|
+
function secretUpdateRequest(update) {
|
|
6128
5847
|
return {
|
|
6129
|
-
|
|
6130
|
-
|
|
5848
|
+
workspaceId: update.workspaceId,
|
|
5849
|
+
secretmanagerVaultName: update.vaultName,
|
|
5850
|
+
secretmanagerSecretName: update.secretName,
|
|
5851
|
+
secretmanagerSecretValue: update.value
|
|
6131
5852
|
};
|
|
6132
5853
|
}
|
|
6133
|
-
function normalizeComparableStaticWebsite(input) {
|
|
6134
|
-
return normalizeComparableStaticWebsiteShape({
|
|
6135
|
-
description: input.description || "",
|
|
6136
|
-
allowedIpAddresses: [...input.allowedIpAddresses || []]
|
|
6137
|
-
});
|
|
6138
|
-
}
|
|
6139
|
-
function areStaticWebsitesEqual(existing, desired) {
|
|
6140
|
-
return areNormalizedEqual(normalizeComparableStaticWebsite(existing), normalizeComparableStaticWebsite(desired));
|
|
6141
|
-
}
|
|
6142
5854
|
/**
|
|
6143
|
-
* Plan
|
|
5855
|
+
* Plan secret manager changes based on current and desired state.
|
|
6144
5856
|
* @param context - Planning context
|
|
6145
|
-
* @returns Planned changes
|
|
5857
|
+
* @returns Planned changes for vaults and secrets
|
|
6146
5858
|
*/
|
|
6147
|
-
async function
|
|
6148
|
-
const { client, workspaceId, application, forRemoval } = context;
|
|
6149
|
-
const
|
|
6150
|
-
const
|
|
5859
|
+
async function planSecretManager(context) {
|
|
5860
|
+
const { client, workspaceId, application, forRemoval, forceApplyAll = false } = context;
|
|
5861
|
+
const secretVaults = forRemoval ? [] : application.secrets;
|
|
5862
|
+
const vaultChangeSet = createChangeSet("Secret Manager vaults");
|
|
5863
|
+
const secretChangeSet = createChangeSet("Secret Manager secrets");
|
|
6151
5864
|
const conflicts = [];
|
|
6152
5865
|
const unmanaged = [];
|
|
6153
5866
|
const resourceOwners = /* @__PURE__ */ new Set();
|
|
6154
|
-
const
|
|
5867
|
+
const existingVaults = await fetchExistingResourcesWithLabels({
|
|
6155
5868
|
client,
|
|
6156
|
-
fetchPage: async (pageToken,
|
|
6157
|
-
const {
|
|
5869
|
+
fetchPage: async (pageToken, maxPageSize) => {
|
|
5870
|
+
const { vaults, nextPageToken } = await client.listSecretManagerVaults({
|
|
6158
5871
|
workspaceId,
|
|
6159
5872
|
pageToken,
|
|
6160
|
-
pageSize
|
|
5873
|
+
pageSize: maxPageSize
|
|
6161
5874
|
});
|
|
6162
|
-
return [
|
|
5875
|
+
return [vaults, nextPageToken];
|
|
6163
5876
|
},
|
|
6164
5877
|
getName: (resource) => resource.name,
|
|
6165
|
-
getTrn: (name) => resourceTrn(workspaceId, "
|
|
5878
|
+
getTrn: (name) => resourceTrn(workspaceId, "vault", name)
|
|
6166
5879
|
});
|
|
6167
|
-
const
|
|
6168
|
-
const
|
|
6169
|
-
|
|
6170
|
-
const
|
|
6171
|
-
const
|
|
6172
|
-
const existing = existingWebsites[name];
|
|
6173
|
-
const metaRequest = await buildMetaRequest({
|
|
6174
|
-
trn: resourceTrn(workspaceId, "staticwebsite", name),
|
|
6175
|
-
appName: application.name,
|
|
6176
|
-
appId: application.id
|
|
6177
|
-
});
|
|
6178
|
-
const desired = normalizeComparableStaticWebsite(config);
|
|
6179
|
-
const request = {
|
|
6180
|
-
workspaceId,
|
|
6181
|
-
staticwebsite: {
|
|
6182
|
-
name,
|
|
6183
|
-
description: config.description || "",
|
|
6184
|
-
allowedIpAddresses: config.allowedIpAddresses || []
|
|
6185
|
-
}
|
|
6186
|
-
};
|
|
5880
|
+
const state = loadSecretsState();
|
|
5881
|
+
const skippedSecrets = [];
|
|
5882
|
+
await Promise.all(secretVaults.map(async (vault) => {
|
|
5883
|
+
const vaultName = vault.vaultName;
|
|
5884
|
+
const existing = existingVaults[vaultName];
|
|
6187
5885
|
if (existing) {
|
|
6188
|
-
const
|
|
5886
|
+
const metaRequest = await buildMetaRequest({
|
|
5887
|
+
trn: resourceTrn(workspaceId, "vault", vaultName),
|
|
5888
|
+
appName: application.name,
|
|
5889
|
+
appId: application.id
|
|
5890
|
+
});
|
|
5891
|
+
if (trackDesiredResourceOwnership({
|
|
6189
5892
|
labels: existing.allLabels,
|
|
6190
5893
|
ownerLabel: existing.label,
|
|
6191
5894
|
appName: application.name,
|
|
6192
5895
|
appId: application.id,
|
|
6193
|
-
resourceType: "
|
|
6194
|
-
resourceName:
|
|
5896
|
+
resourceType: "Secret Manager vault",
|
|
5897
|
+
resourceName: vaultName,
|
|
6195
5898
|
conflicts,
|
|
6196
5899
|
unmanaged
|
|
5900
|
+
}) && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels)) vaultChangeSet.unchanged.push({ name: vaultName });
|
|
5901
|
+
else vaultChangeSet.updates.push({
|
|
5902
|
+
name: vaultName,
|
|
5903
|
+
workspaceId
|
|
6197
5904
|
});
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
} else {
|
|
6207
|
-
changeSet.creates.push({
|
|
6208
|
-
name,
|
|
6209
|
-
request,
|
|
6210
|
-
metaRequest
|
|
6211
|
-
});
|
|
6212
|
-
ownedWebsiteNames.add(name);
|
|
6213
|
-
}
|
|
6214
|
-
}
|
|
6215
|
-
Object.entries(existingWebsites).forEach(([name]) => {
|
|
6216
|
-
const entry = existingWebsites[name];
|
|
6217
|
-
const label = entry?.label;
|
|
6218
|
-
if (trackRemainingResourceOwner({
|
|
6219
|
-
labels: entry?.allLabels,
|
|
6220
|
-
ownerLabel: label,
|
|
6221
|
-
appName: application.name,
|
|
6222
|
-
appId: application.id,
|
|
6223
|
-
resourceOwners
|
|
6224
|
-
})) changeSet.deletes.push({
|
|
6225
|
-
name,
|
|
6226
|
-
request: {
|
|
5905
|
+
delete existingVaults[vaultName];
|
|
5906
|
+
} else vaultChangeSet.creates.push({
|
|
5907
|
+
name: vaultName,
|
|
5908
|
+
workspaceId
|
|
5909
|
+
});
|
|
5910
|
+
let existingSecrets = [];
|
|
5911
|
+
if (existing) existingSecrets = (await fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
5912
|
+
const { secrets, nextPageToken } = await client.listSecretManagerSecrets({
|
|
6227
5913
|
workspaceId,
|
|
6228
|
-
|
|
5914
|
+
secretmanagerVaultName: vaultName,
|
|
5915
|
+
pageToken,
|
|
5916
|
+
pageSize: maxPageSize
|
|
5917
|
+
});
|
|
5918
|
+
return [secrets, nextPageToken];
|
|
5919
|
+
})).map((s) => s.name);
|
|
5920
|
+
const existingSet = new Set(existingSecrets);
|
|
5921
|
+
for (const secret of vault.secrets) {
|
|
5922
|
+
if (secret.value == null) {
|
|
5923
|
+
existingSet.delete(secret.name);
|
|
5924
|
+
skippedSecrets.push(`${vaultName}/${secret.name}`);
|
|
5925
|
+
continue;
|
|
6229
5926
|
}
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
5927
|
+
if (existingSet.has(secret.name)) {
|
|
5928
|
+
const currentHash = hashValue(secret.value);
|
|
5929
|
+
const storedHash = state.vaults[vaultName]?.[secret.name];
|
|
5930
|
+
if (forceApplyAll || currentHash !== storedHash) secretChangeSet.updates.push({
|
|
5931
|
+
name: `${vaultName}/${secret.name}`,
|
|
5932
|
+
secretName: secret.name,
|
|
5933
|
+
workspaceId,
|
|
5934
|
+
vaultName,
|
|
5935
|
+
value: secret.value
|
|
5936
|
+
});
|
|
5937
|
+
existingSet.delete(secret.name);
|
|
5938
|
+
} else secretChangeSet.creates.push({
|
|
5939
|
+
name: `${vaultName}/${secret.name}`,
|
|
5940
|
+
secretName: secret.name,
|
|
6239
5941
|
workspaceId,
|
|
6240
|
-
|
|
5942
|
+
vaultName,
|
|
5943
|
+
value: secret.value
|
|
6241
5944
|
});
|
|
6242
|
-
const domainsWithLabels = await Promise.all(customDomains.map(async (d) => {
|
|
6243
|
-
const { metadata } = await client.getMetadata({ trn: customDomainTrn(workspaceId, name, d.domain) });
|
|
6244
|
-
return {
|
|
6245
|
-
domain: d.domain,
|
|
6246
|
-
allLabels: metadata?.labels
|
|
6247
|
-
};
|
|
6248
|
-
}));
|
|
6249
|
-
existingDomainsByWebsite.set(name, domainsWithLabels);
|
|
6250
|
-
} catch (error) {
|
|
6251
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return;
|
|
6252
|
-
throw error;
|
|
6253
5945
|
}
|
|
5946
|
+
for (const orphanName of existingSet) secretChangeSet.deletes.push({
|
|
5947
|
+
name: `${vaultName}/${orphanName}`,
|
|
5948
|
+
secretName: orphanName,
|
|
5949
|
+
workspaceId,
|
|
5950
|
+
vaultName
|
|
5951
|
+
});
|
|
6254
5952
|
}));
|
|
6255
|
-
for (const name of
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
customDomainChangeSet.creates.push({
|
|
6267
|
-
name: domain,
|
|
6268
|
-
request: {
|
|
5953
|
+
for (const [name, entry] of Object.entries(existingVaults)) {
|
|
5954
|
+
if (!entry) continue;
|
|
5955
|
+
if (trackRemainingResourceOwner({
|
|
5956
|
+
labels: entry.allLabels,
|
|
5957
|
+
ownerLabel: entry.label,
|
|
5958
|
+
appName: application.name,
|
|
5959
|
+
appId: application.id,
|
|
5960
|
+
resourceOwners
|
|
5961
|
+
})) {
|
|
5962
|
+
const secrets = await fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
5963
|
+
const { secrets, nextPageToken } = await client.listSecretManagerSecrets({
|
|
6269
5964
|
workspaceId,
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
5965
|
+
secretmanagerVaultName: name,
|
|
5966
|
+
pageToken,
|
|
5967
|
+
pageSize: maxPageSize
|
|
5968
|
+
});
|
|
5969
|
+
return [secrets, nextPageToken];
|
|
6274
5970
|
});
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
5971
|
+
for (const secret of secrets) secretChangeSet.deletes.push({
|
|
5972
|
+
name: `${name}/${secret.name}`,
|
|
5973
|
+
secretName: secret.name,
|
|
5974
|
+
workspaceId,
|
|
5975
|
+
vaultName: name
|
|
5976
|
+
});
|
|
5977
|
+
vaultChangeSet.deletes.push({
|
|
5978
|
+
name,
|
|
5979
|
+
workspaceId
|
|
6283
5980
|
});
|
|
6284
5981
|
}
|
|
6285
5982
|
}
|
|
6286
5983
|
return {
|
|
6287
|
-
|
|
6288
|
-
|
|
5984
|
+
vaultChangeSet,
|
|
5985
|
+
secretChangeSet,
|
|
5986
|
+
skippedSecrets,
|
|
6289
5987
|
conflicts,
|
|
6290
5988
|
unmanaged,
|
|
6291
5989
|
resourceOwners
|
|
6292
5990
|
};
|
|
6293
5991
|
}
|
|
5992
|
+
/**
|
|
5993
|
+
* Apply secret manager changes for the given phase.
|
|
5994
|
+
* @param client - Operator client instance
|
|
5995
|
+
* @param result - Planned secret changes
|
|
5996
|
+
* @param phase - Apply phase
|
|
5997
|
+
* @param application - Application to read secrets from for hash state persistence
|
|
5998
|
+
* @returns Promise that resolves when secret changes are applied
|
|
5999
|
+
*/
|
|
6000
|
+
async function applySecretManager(client, result, phase = "create-update", application) {
|
|
6001
|
+
const { vaultChangeSet, secretChangeSet } = result;
|
|
6002
|
+
if (phase === "create-update") {
|
|
6003
|
+
await Promise.all(vaultChangeSet.creates.map(async (create) => {
|
|
6004
|
+
await client.createSecretManagerVault(vaultCreateRequest(create));
|
|
6005
|
+
if (application) {
|
|
6006
|
+
const metaRequest = await buildMetaRequest({
|
|
6007
|
+
trn: resourceTrn(create.workspaceId, "vault", create.name),
|
|
6008
|
+
appName: application.name,
|
|
6009
|
+
appId: application.id
|
|
6010
|
+
});
|
|
6011
|
+
await client.setMetadata(metaRequest);
|
|
6012
|
+
}
|
|
6013
|
+
}));
|
|
6014
|
+
if (application) await Promise.all(vaultChangeSet.updates.map(async (update) => {
|
|
6015
|
+
const metaRequest = await buildMetaRequest({
|
|
6016
|
+
trn: resourceTrn(update.workspaceId, "vault", update.name),
|
|
6017
|
+
appName: application.name,
|
|
6018
|
+
appId: application.id
|
|
6019
|
+
});
|
|
6020
|
+
await client.setMetadata(metaRequest);
|
|
6021
|
+
}));
|
|
6022
|
+
await Promise.all(secretChangeSet.creates.map((create) => client.createSecretManagerSecret(secretCreateRequest(create))));
|
|
6023
|
+
await Promise.all(secretChangeSet.updates.map((update) => client.updateSecretManagerSecret(secretUpdateRequest(update))));
|
|
6024
|
+
if (application) {
|
|
6025
|
+
const state = loadSecretsState();
|
|
6026
|
+
for (const vault of application.secrets) {
|
|
6027
|
+
if (!Object.hasOwn(state.vaults, vault.vaultName)) state.vaults[vault.vaultName] = {};
|
|
6028
|
+
for (const secret of vault.secrets) if (secret.value != null) assertDefined(state.vaults[vault.vaultName], "vault state entry missing")[secret.name] = hashValue(secret.value);
|
|
6029
|
+
}
|
|
6030
|
+
saveSecretsState(state);
|
|
6031
|
+
}
|
|
6032
|
+
} else {
|
|
6033
|
+
await Promise.all(secretChangeSet.deletes.map((del) => client.deleteSecretManagerSecret({
|
|
6034
|
+
workspaceId: del.workspaceId,
|
|
6035
|
+
secretmanagerVaultName: del.vaultName,
|
|
6036
|
+
secretmanagerSecretName: del.secretName
|
|
6037
|
+
})));
|
|
6038
|
+
await Promise.all(vaultChangeSet.deletes.map((del) => client.deleteSecretManagerVault({
|
|
6039
|
+
workspaceId: del.workspaceId,
|
|
6040
|
+
secretmanagerVaultName: del.name
|
|
6041
|
+
})));
|
|
6042
|
+
if (secretChangeSet.deletes.length > 0 || vaultChangeSet.deletes.length > 0) {
|
|
6043
|
+
const state = loadSecretsState();
|
|
6044
|
+
for (const del of secretChangeSet.deletes) if (Object.hasOwn(state.vaults, del.vaultName)) {
|
|
6045
|
+
delete assertDefined(state.vaults[del.vaultName], "vault state entry missing")[del.secretName];
|
|
6046
|
+
if (Object.keys(assertDefined(state.vaults[del.vaultName], "vault state entry missing")).length === 0) delete state.vaults[del.vaultName];
|
|
6047
|
+
}
|
|
6048
|
+
for (const del of vaultChangeSet.deletes) delete state.vaults[del.name];
|
|
6049
|
+
saveSecretsState(state);
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6294
6053
|
|
|
6295
6054
|
//#endregion
|
|
6296
6055
|
//#region src/cli/commands/tailordb/migrate/config.ts
|
|
@@ -6919,42 +6678,56 @@ function copySnapshotRecord(record) {
|
|
|
6919
6678
|
return copy;
|
|
6920
6679
|
}
|
|
6921
6680
|
/**
|
|
6922
|
-
* Normalize a snapshot field
|
|
6923
|
-
*
|
|
6924
|
-
* when omitted, which avoids false drift between local
|
|
6925
|
-
* may be omitted) and the platform (which always
|
|
6681
|
+
* Normalize a snapshot field into the canonical form for comparison, returning
|
|
6682
|
+
* a new object rather than mutating the input. Currently fills in the platform
|
|
6683
|
+
* default decimal scale when omitted, which avoids false drift between local
|
|
6684
|
+
* schemas (where scale may be omitted) and the platform (which always
|
|
6685
|
+
* materializes a scale).
|
|
6926
6686
|
* @param {SnapshotFieldConfig} field - Field configuration to normalize
|
|
6927
|
-
* @returns
|
|
6687
|
+
* @returns {SnapshotFieldConfig} A new, normalized field object
|
|
6928
6688
|
*/
|
|
6929
6689
|
function normalizeSnapshotField(field) {
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6690
|
+
const fields = field.fields ? Object.fromEntries(Object.entries(field.fields).map(([name, nested]) => [name, normalizeSnapshotField(nested)])) : void 0;
|
|
6691
|
+
return {
|
|
6692
|
+
...field,
|
|
6693
|
+
...field.type === "decimal" && field.scale === void 0 && { scale: 6 },
|
|
6694
|
+
...fields && { fields }
|
|
6695
|
+
};
|
|
6933
6696
|
}
|
|
6934
6697
|
/**
|
|
6935
|
-
* Normalize a snapshot type
|
|
6936
|
-
* Currently fills:
|
|
6698
|
+
* Normalize a snapshot type into the canonical comparison shape, returning a
|
|
6699
|
+
* new object rather than mutating the input. Currently fills:
|
|
6937
6700
|
* - `pluralForm` via inflection when missing (legacy snapshots written
|
|
6938
6701
|
* before `pluralForm` became required may omit it)
|
|
6939
6702
|
* - per-field `scale` defaults via {@link normalizeSnapshotField}
|
|
6940
6703
|
*
|
|
6941
6704
|
* Idempotent — safe to call multiple times on the same input.
|
|
6942
6705
|
* @param {TailorDBSnapshotType} type - Snapshot type to normalize
|
|
6943
|
-
* @returns
|
|
6706
|
+
* @returns {TailorDBSnapshotType} A new, normalized snapshot type object
|
|
6944
6707
|
*/
|
|
6945
6708
|
function normalizeSnapshotType(type) {
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6709
|
+
const pluralForm = type.pluralForm || inflection.pluralize(type.name);
|
|
6710
|
+
const fields = createSnapshotRecord();
|
|
6711
|
+
for (const [fieldName, field] of Object.entries(type.fields)) fields[fieldName] = normalizeSnapshotField(field);
|
|
6712
|
+
return {
|
|
6713
|
+
...type,
|
|
6714
|
+
pluralForm,
|
|
6715
|
+
fields
|
|
6716
|
+
};
|
|
6949
6717
|
}
|
|
6950
6718
|
/**
|
|
6951
|
-
* Normalize a schema snapshot
|
|
6719
|
+
* Normalize a schema snapshot into the canonical comparison shape, returning a
|
|
6720
|
+
* new object rather than mutating the input.
|
|
6952
6721
|
* @param {SchemaSnapshot} snapshot - Schema snapshot to normalize
|
|
6953
|
-
* @returns
|
|
6722
|
+
* @returns {NormalizedSchemaSnapshot} A new schema snapshot object branded as normalized
|
|
6954
6723
|
*/
|
|
6955
6724
|
function normalizeSchemaSnapshot(snapshot) {
|
|
6956
|
-
|
|
6957
|
-
|
|
6725
|
+
const types = createSnapshotRecord();
|
|
6726
|
+
for (const [typeName, type] of Object.entries(snapshot.types)) types[typeName] = normalizeSnapshotType(type);
|
|
6727
|
+
return {
|
|
6728
|
+
...snapshot,
|
|
6729
|
+
types
|
|
6730
|
+
};
|
|
6958
6731
|
}
|
|
6959
6732
|
/**
|
|
6960
6733
|
* Validate that a migration number follows the expected format (4-digit number)
|
|
@@ -6995,53 +6768,7 @@ function getMigrationFilePath(migrationsDir, num, type) {
|
|
|
6995
6768
|
return path.join(migrationDir, MIGRATION_FILE_NAMES[type]);
|
|
6996
6769
|
}
|
|
6997
6770
|
/**
|
|
6998
|
-
* Create a snapshot field config from
|
|
6999
|
-
* @param {ParsedField} field - Parsed field definition
|
|
7000
|
-
* @returns {SnapshotFieldConfig} Snapshot field configuration
|
|
7001
|
-
*/
|
|
7002
|
-
function createSnapshotFieldConfig(field) {
|
|
7003
|
-
const config = {
|
|
7004
|
-
type: field.config.type,
|
|
7005
|
-
required: field.config.required !== false
|
|
7006
|
-
};
|
|
7007
|
-
if (field.config.array) config.array = true;
|
|
7008
|
-
if (field.config.index) config.index = true;
|
|
7009
|
-
if (field.config.unique) config.unique = true;
|
|
7010
|
-
if (field.config.allowedValues && field.config.allowedValues.length > 0) config.allowedValues = field.config.allowedValues.map((v) => ({
|
|
7011
|
-
value: v.value,
|
|
7012
|
-
...v.description && { description: v.description }
|
|
7013
|
-
}));
|
|
7014
|
-
if (field.config.foreignKey) {
|
|
7015
|
-
config.foreignKey = true;
|
|
7016
|
-
if (field.config.foreignKeyType) config.foreignKeyType = field.config.foreignKeyType;
|
|
7017
|
-
if (field.config.foreignKeyField) config.foreignKeyField = field.config.foreignKeyField;
|
|
7018
|
-
}
|
|
7019
|
-
if (field.config.description) config.description = field.config.description;
|
|
7020
|
-
if (field.config.vector) config.vector = true;
|
|
7021
|
-
if (field.config.hooks) {
|
|
7022
|
-
config.hooks = {};
|
|
7023
|
-
if (field.config.hooks.create) config.hooks.create = { expr: field.config.hooks.create.expr };
|
|
7024
|
-
if (field.config.hooks.update) config.hooks.update = { expr: field.config.hooks.update.expr };
|
|
7025
|
-
}
|
|
7026
|
-
if (field.config.validate && field.config.validate.length > 0) config.validate = field.config.validate.map((v) => ({
|
|
7027
|
-
script: { expr: v.script.expr },
|
|
7028
|
-
errorMessage: v.errorMessage
|
|
7029
|
-
}));
|
|
7030
|
-
if (field.config.serial) config.serial = {
|
|
7031
|
-
start: field.config.serial.start,
|
|
7032
|
-
...field.config.serial.maxValue !== void 0 && { maxValue: field.config.serial.maxValue },
|
|
7033
|
-
...field.config.serial.format && { format: field.config.serial.format }
|
|
7034
|
-
};
|
|
7035
|
-
if (field.config.scale !== void 0) config.scale = field.config.scale;
|
|
7036
|
-
if (field.config.fields && Object.keys(field.config.fields).length > 0) {
|
|
7037
|
-
config.fields = {};
|
|
7038
|
-
for (const [nestedName, nestedConfig] of Object.entries(field.config.fields)) config.fields[nestedName] = createSnapshotFieldConfigFromOperatorConfig(nestedConfig);
|
|
7039
|
-
}
|
|
7040
|
-
normalizeSnapshotField(config);
|
|
7041
|
-
return config;
|
|
7042
|
-
}
|
|
7043
|
-
/**
|
|
7044
|
-
* Create a snapshot field config from an OperatorFieldConfig (for nested fields)
|
|
6771
|
+
* Create a snapshot field config from an OperatorFieldConfig.
|
|
7045
6772
|
* @param {import("#/parser/service/tailordb/types").OperatorFieldConfig} fieldConfig - Field configuration
|
|
7046
6773
|
* @returns {SnapshotFieldConfig} Snapshot field configuration
|
|
7047
6774
|
*/
|
|
@@ -7083,8 +6810,7 @@ function createSnapshotFieldConfigFromOperatorConfig(fieldConfig) {
|
|
|
7083
6810
|
config.fields = {};
|
|
7084
6811
|
for (const [nestedName, nestedConfig] of Object.entries(fieldConfig.fields)) config.fields[nestedName] = createSnapshotFieldConfigFromOperatorConfig(nestedConfig);
|
|
7085
6812
|
}
|
|
7086
|
-
normalizeSnapshotField(config);
|
|
7087
|
-
return config;
|
|
6813
|
+
return normalizeSnapshotField(config);
|
|
7088
6814
|
}
|
|
7089
6815
|
/**
|
|
7090
6816
|
* Create a snapshot type from a parsed type
|
|
@@ -7093,7 +6819,7 @@ function createSnapshotFieldConfigFromOperatorConfig(fieldConfig) {
|
|
|
7093
6819
|
*/
|
|
7094
6820
|
function createSnapshotType(type) {
|
|
7095
6821
|
const fields = createSnapshotRecord();
|
|
7096
|
-
for (const [fieldName, field] of Object.entries(type.fields)) fields[fieldName] =
|
|
6822
|
+
for (const [fieldName, field] of Object.entries(type.fields)) fields[fieldName] = createSnapshotFieldConfigFromOperatorConfig(field.config);
|
|
7097
6823
|
const snapshotType = {
|
|
7098
6824
|
name: type.name,
|
|
7099
6825
|
pluralForm: type.pluralForm || inflection.pluralize(type.name),
|
|
@@ -9640,35 +9366,25 @@ function groupMigrationsByNamespace(migrations) {
|
|
|
9640
9366
|
* @returns {Promise<ProtoTailorDBType[]>} Remote TailorDB types
|
|
9641
9367
|
*/
|
|
9642
9368
|
async function fetchRemoteTypes(client, workspaceId, namespace) {
|
|
9643
|
-
return
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
return [tailordbTypes, nextPageToken];
|
|
9652
|
-
} catch (error) {
|
|
9653
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
9654
|
-
throw error;
|
|
9655
|
-
}
|
|
9369
|
+
return fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
9370
|
+
const { tailordbTypes, nextPageToken } = await client.listTailorDBTypes({
|
|
9371
|
+
workspaceId,
|
|
9372
|
+
namespaceName: namespace,
|
|
9373
|
+
pageToken,
|
|
9374
|
+
pageSize: maxPageSize
|
|
9375
|
+
});
|
|
9376
|
+
return [tailordbTypes, nextPageToken];
|
|
9656
9377
|
});
|
|
9657
9378
|
}
|
|
9658
9379
|
async function fetchRemoteGqlPermissions(client, workspaceId, namespace) {
|
|
9659
|
-
return
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
return [permissions, nextPageToken];
|
|
9668
|
-
} catch (error) {
|
|
9669
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
9670
|
-
throw error;
|
|
9671
|
-
}
|
|
9380
|
+
return fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
9381
|
+
const { permissions, nextPageToken } = await client.listTailorDBGQLPermissions({
|
|
9382
|
+
workspaceId,
|
|
9383
|
+
namespaceName: namespace,
|
|
9384
|
+
pageToken,
|
|
9385
|
+
pageSize: maxPageSize
|
|
9386
|
+
});
|
|
9387
|
+
return [permissions, nextPageToken];
|
|
9672
9388
|
});
|
|
9673
9389
|
}
|
|
9674
9390
|
function definedWhenNotEmpty(value) {
|
|
@@ -10301,7 +10017,7 @@ async function planTailorDB(context) {
|
|
|
10301
10017
|
tailordbs.push(toTailorDBDeployInput(tailordb));
|
|
10302
10018
|
}
|
|
10303
10019
|
const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
|
|
10304
|
-
const executorUsedTypes =
|
|
10020
|
+
const executorUsedTypes = new Set(context.executorUsedTailorDBTypes ?? []);
|
|
10305
10021
|
for (const executor of executors) if (executor.trigger.kind === "tailordb") executorUsedTypes.add(executor.trigger.typeName);
|
|
10306
10022
|
const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices(client, workspaceId, application.name, application.id, tailordbs);
|
|
10307
10023
|
const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
|
|
@@ -10406,17 +10122,16 @@ async function planServices(client, workspaceId, appName, appId, tailordbs) {
|
|
|
10406
10122
|
existingLabels: existing?.allLabels
|
|
10407
10123
|
});
|
|
10408
10124
|
if (existing) {
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
else conflicts.push({
|
|
10125
|
+
if (trackDesiredResourceOwnership({
|
|
10126
|
+
labels: existing.allLabels,
|
|
10127
|
+
ownerLabel: existing.label,
|
|
10128
|
+
appName,
|
|
10129
|
+
appId,
|
|
10415
10130
|
resourceType: "TailorDB service",
|
|
10416
10131
|
resourceName: tailordb.namespace,
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
|
|
10132
|
+
conflicts,
|
|
10133
|
+
unmanaged
|
|
10134
|
+
}) && hasMatchingSdkVersion(existing.allLabels, metaRequest.labels) && areTailorDBServicesEqual(existing.resource, tailordb)) changeSet.unchanged.push({ name: tailordb.namespace });
|
|
10420
10135
|
else changeSet.updates.push({
|
|
10421
10136
|
name: tailordb.namespace,
|
|
10422
10137
|
metaRequest
|
|
@@ -10434,10 +10149,13 @@ async function planServices(client, workspaceId, appName, appId, tailordbs) {
|
|
|
10434
10149
|
}
|
|
10435
10150
|
Object.entries(existingServices).forEach(([namespaceName]) => {
|
|
10436
10151
|
const entry = existingServices[namespaceName];
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10152
|
+
if (trackRemainingResourceOwner({
|
|
10153
|
+
labels: entry?.allLabels,
|
|
10154
|
+
ownerLabel: entry?.label,
|
|
10155
|
+
appName,
|
|
10156
|
+
appId,
|
|
10157
|
+
resourceOwners
|
|
10158
|
+
})) changeSet.deletes.push({
|
|
10441
10159
|
name: namespaceName,
|
|
10442
10160
|
request: {
|
|
10443
10161
|
workspaceId,
|
|
@@ -10455,19 +10173,14 @@ async function planServices(client, workspaceId, appName, appId, tailordbs) {
|
|
|
10455
10173
|
async function planTypes(client, workspaceId, tailordbs, executorUsedTypes, deletedServices, filteredTypesByNamespace, forceApplyAll = false) {
|
|
10456
10174
|
const changeSet = createChangeSet("TailorDB types");
|
|
10457
10175
|
const fetchTypes = (namespaceName) => {
|
|
10458
|
-
return
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
return [tailordbTypes, nextPageToken];
|
|
10467
|
-
} catch (error) {
|
|
10468
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
10469
|
-
throw error;
|
|
10470
|
-
}
|
|
10176
|
+
return fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
10177
|
+
const { tailordbTypes, nextPageToken } = await client.listTailorDBTypes({
|
|
10178
|
+
workspaceId,
|
|
10179
|
+
namespaceName,
|
|
10180
|
+
pageToken,
|
|
10181
|
+
pageSize: maxPageSize
|
|
10182
|
+
});
|
|
10183
|
+
return [tailordbTypes, nextPageToken];
|
|
10471
10184
|
});
|
|
10472
10185
|
};
|
|
10473
10186
|
for (const tailordb of tailordbs) {
|
|
@@ -10597,24 +10310,19 @@ function matchesNumericStringPath(path) {
|
|
|
10597
10310
|
});
|
|
10598
10311
|
}
|
|
10599
10312
|
function isNumericLikeValue(value) {
|
|
10600
|
-
return typeof value === "number" || typeof value === "bigint" || /^-?\d+$/.test(value);
|
|
10601
|
-
}
|
|
10602
|
-
async function planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll = false) {
|
|
10603
|
-
const changeSet = createChangeSet("TailorDB gqlPermissions");
|
|
10604
|
-
const fetchGqlPermissions = (namespaceName) => {
|
|
10605
|
-
return
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
return [permissions, nextPageToken];
|
|
10614
|
-
} catch (error) {
|
|
10615
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
10616
|
-
throw error;
|
|
10617
|
-
}
|
|
10313
|
+
return typeof value === "number" || typeof value === "bigint" || /^-?\d+$/.test(value);
|
|
10314
|
+
}
|
|
10315
|
+
async function planGqlPermissions(client, workspaceId, tailordbs, deletedServices, forceApplyAll = false) {
|
|
10316
|
+
const changeSet = createChangeSet("TailorDB gqlPermissions");
|
|
10317
|
+
const fetchGqlPermissions = (namespaceName) => {
|
|
10318
|
+
return fetchAllTolerant(async (pageToken, maxPageSize) => {
|
|
10319
|
+
const { permissions, nextPageToken } = await client.listTailorDBGQLPermissions({
|
|
10320
|
+
workspaceId,
|
|
10321
|
+
namespaceName,
|
|
10322
|
+
pageToken,
|
|
10323
|
+
pageSize: maxPageSize
|
|
10324
|
+
});
|
|
10325
|
+
return [permissions, nextPageToken];
|
|
10618
10326
|
});
|
|
10619
10327
|
};
|
|
10620
10328
|
for (const tailordb of tailordbs) {
|
|
@@ -11105,6 +10813,327 @@ function normalizeRetryPolicyForCompare(policy) {
|
|
|
11105
10813
|
};
|
|
11106
10814
|
}
|
|
11107
10815
|
|
|
10816
|
+
//#endregion
|
|
10817
|
+
//#region src/cli/commands/deploy/apply-phases.ts
|
|
10818
|
+
/**
|
|
10819
|
+
* Apply planned deploy changes for one or more applications.
|
|
10820
|
+
* @param client - Operator client instance
|
|
10821
|
+
* @param workspaceId - Target workspace ID
|
|
10822
|
+
* @param deployments - Planned deployments to apply
|
|
10823
|
+
*/
|
|
10824
|
+
async function applyDeploymentPlans(client, workspaceId, deployments) {
|
|
10825
|
+
const forEachDeployment = async (apply) => {
|
|
10826
|
+
for (const deployment of deployments) await apply(deployment);
|
|
10827
|
+
};
|
|
10828
|
+
await withSpan("apply.createUpdateServices", async () => {
|
|
10829
|
+
await forEachDeployment((d) => applySecretManager(client, d.secretManager, "create-update", d.application));
|
|
10830
|
+
await forEachDeployment((d) => applyFunctionRegistry(client, workspaceId, d.functionRegistry, "create-update"));
|
|
10831
|
+
await forEachDeployment((d) => applyStaticWebsite(client, d.staticWebsite, "create-update"));
|
|
10832
|
+
await forEachDeployment((d) => applyAIGateway(client, d.aiGateway, "create-update"));
|
|
10833
|
+
await forEachDeployment((d) => applyIdP(client, d.idp, "create-update"));
|
|
10834
|
+
await forEachDeployment((d) => applyAuth(client, d.auth, "create-update-prerequisites"));
|
|
10835
|
+
await forEachDeployment((d) => applyTailorDB(client, d.tailorDB, "create-update"));
|
|
10836
|
+
await forEachDeployment((d) => applyAuth(client, d.auth, "create-update-dependents"));
|
|
10837
|
+
await forEachDeployment((d) => applyPipeline(client, d.pipeline, "create-update"));
|
|
10838
|
+
});
|
|
10839
|
+
await withSpan("apply.deleteSubgraphResources", async () => {
|
|
10840
|
+
await forEachDeployment((d) => applyPipeline(client, d.pipeline, "delete-resources"));
|
|
10841
|
+
await forEachDeployment((d) => applyAuth(client, d.auth, "delete-resources"));
|
|
10842
|
+
await forEachDeployment((d) => applyIdP(client, d.idp, "delete-resources"));
|
|
10843
|
+
});
|
|
10844
|
+
await withSpan("apply.createUpdateApplication", async () => {
|
|
10845
|
+
await forEachDeployment((d) => applyApplication(client, d.app, "create-update"));
|
|
10846
|
+
});
|
|
10847
|
+
await withSpan("apply.createUpdateDependentServices", async () => {
|
|
10848
|
+
await forEachDeployment((d) => applyExecutor(client, d.executor, "create-update"));
|
|
10849
|
+
await forEachDeployment((d) => applyWorkflow(client, d.workflow, "create-update"));
|
|
10850
|
+
});
|
|
10851
|
+
await withSpan("apply.deleteDependentServices", async () => {
|
|
10852
|
+
await forEachDeployment((d) => applyWorkflow(client, d.workflow, "delete"));
|
|
10853
|
+
await forEachDeployment((d) => applyExecutor(client, d.executor, "delete"));
|
|
10854
|
+
await forEachDeployment((d) => applyStaticWebsite(client, d.staticWebsite, "delete"));
|
|
10855
|
+
await forEachDeployment((d) => applyAIGateway(client, d.aiGateway, "delete"));
|
|
10856
|
+
await forEachDeployment((d) => applySecretManager(client, d.secretManager, "delete", d.application));
|
|
10857
|
+
});
|
|
10858
|
+
await withSpan("apply.deleteApplication", async () => {
|
|
10859
|
+
await forEachDeployment((d) => applyApplication(client, d.app, "delete"));
|
|
10860
|
+
});
|
|
10861
|
+
await withSpan("apply.deleteSubgraphServices", async () => {
|
|
10862
|
+
await forEachDeployment((d) => applyPipeline(client, d.pipeline, "delete-services"));
|
|
10863
|
+
await forEachDeployment((d) => applyAuth(client, d.auth, "delete-services"));
|
|
10864
|
+
await forEachDeployment((d) => applyIdP(client, d.idp, "delete-services"));
|
|
10865
|
+
await forEachDeployment((d) => applyTailorDB(client, d.tailorDB, "delete-services"));
|
|
10866
|
+
});
|
|
10867
|
+
await withSpan("apply.cleanup", async () => {
|
|
10868
|
+
await forEachDeployment((d) => applyFunctionRegistry(client, workspaceId, d.functionRegistry, "delete"));
|
|
10869
|
+
});
|
|
10870
|
+
}
|
|
10871
|
+
|
|
10872
|
+
//#endregion
|
|
10873
|
+
//#region src/cli/commands/deploy/config-id-injector.ts
|
|
10874
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10875
|
+
function findDefineConfigCalls(node, results) {
|
|
10876
|
+
if (!node || typeof node !== "object") return;
|
|
10877
|
+
const n = node;
|
|
10878
|
+
if (n.type === "CallExpression") {
|
|
10879
|
+
const ce = n;
|
|
10880
|
+
if (ce.callee.type === "Identifier" && ce.callee.name === "defineConfig") {
|
|
10881
|
+
const arg = ce.arguments[0];
|
|
10882
|
+
const configObj = arg && arg.type === "ObjectExpression" ? arg : null;
|
|
10883
|
+
results.push({
|
|
10884
|
+
callExpr: ce,
|
|
10885
|
+
configObj
|
|
10886
|
+
});
|
|
10887
|
+
}
|
|
10888
|
+
}
|
|
10889
|
+
for (const key of Object.keys(n)) {
|
|
10890
|
+
const child = n[key];
|
|
10891
|
+
if (Array.isArray(child)) for (const c of child) findDefineConfigCalls(c, results);
|
|
10892
|
+
else if (child && typeof child === "object") findDefineConfigCalls(child, results);
|
|
10893
|
+
}
|
|
10894
|
+
}
|
|
10895
|
+
function findIdProperty(obj) {
|
|
10896
|
+
for (const prop of obj.properties) {
|
|
10897
|
+
if (prop.type !== "Property") continue;
|
|
10898
|
+
if ((prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" ? prop.key.value : null) === "id") return prop;
|
|
10899
|
+
}
|
|
10900
|
+
return null;
|
|
10901
|
+
}
|
|
10902
|
+
/**
|
|
10903
|
+
* Ensure `tailor.config.ts` has an `id` property on the `defineConfig({...})`
|
|
10904
|
+
* argument. Generates a UUID when missing and writes it back to the file.
|
|
10905
|
+
* Returns null when the file does not contain a `defineConfig()` call (e.g.
|
|
10906
|
+
* a wrapper that re-exports another config).
|
|
10907
|
+
* @param configPath - Absolute path to the config file
|
|
10908
|
+
* @returns Resolved id and whether it was newly injected, or null if skipped
|
|
10909
|
+
*/
|
|
10910
|
+
async function ensureConfigId(configPath) {
|
|
10911
|
+
const source = await fs$1.promises.readFile(configPath, "utf-8");
|
|
10912
|
+
const { program } = parseSync(configPath, source);
|
|
10913
|
+
const calls = [];
|
|
10914
|
+
findDefineConfigCalls(program, calls);
|
|
10915
|
+
if (calls.length === 0) return null;
|
|
10916
|
+
if (calls.length > 1) throw new Error(`Multiple defineConfig() calls found in ${configPath}. Only one is supported.`);
|
|
10917
|
+
const { configObj } = assertDefined(calls[0], "defineConfig call site missing");
|
|
10918
|
+
if (!configObj) throw new Error(`defineConfig() argument must be an inline object literal in ${configPath} so the SDK can manage the 'id' field.`);
|
|
10919
|
+
const idProp = findIdProperty(configObj);
|
|
10920
|
+
if (idProp) {
|
|
10921
|
+
const value = idProp.value;
|
|
10922
|
+
if (value.type !== "Literal") throw new Error(`'id' field in ${configPath} must be a string literal. To use this config for a separate app, delete it.`);
|
|
10923
|
+
const literalValue = value.value;
|
|
10924
|
+
if (typeof literalValue !== "string" || literalValue === "") throw new Error(`'id' field in ${configPath} must be a non-empty string literal. To use this config for a separate app, delete it.`);
|
|
10925
|
+
if (!uuidRegex.test(literalValue)) throw new Error(`'id' field in ${configPath} must be a UUID. To use this config for a separate app, delete it.`);
|
|
10926
|
+
return {
|
|
10927
|
+
id: literalValue,
|
|
10928
|
+
injected: false
|
|
10929
|
+
};
|
|
10930
|
+
}
|
|
10931
|
+
const id = crypto.randomUUID();
|
|
10932
|
+
const newSource = insertIdProperty(source, configObj, id);
|
|
10933
|
+
await fs$1.promises.writeFile(configPath, newSource, "utf-8");
|
|
10934
|
+
logger.info(`Generated app id and wrote to ${configPath}: ${id}`);
|
|
10935
|
+
return {
|
|
10936
|
+
id,
|
|
10937
|
+
injected: true
|
|
10938
|
+
};
|
|
10939
|
+
}
|
|
10940
|
+
/**
|
|
10941
|
+
* Read the resolved `defineConfig({...})` `id` from a config file without
|
|
10942
|
+
* mutating it. Returns null when the file has no inline `defineConfig()` call
|
|
10943
|
+
* (wrapper/re-export config), and `{ id: null }` when the call exists but has
|
|
10944
|
+
* no usable `id` property.
|
|
10945
|
+
* @param configPath - Absolute path to the config file
|
|
10946
|
+
* @returns The existing id (or null when absent), or null for wrapper configs
|
|
10947
|
+
*/
|
|
10948
|
+
async function readConfigId(configPath) {
|
|
10949
|
+
const { program } = parseSync(configPath, await fs$1.promises.readFile(configPath, "utf-8"));
|
|
10950
|
+
const calls = [];
|
|
10951
|
+
findDefineConfigCalls(program, calls);
|
|
10952
|
+
if (calls.length === 0) return null;
|
|
10953
|
+
if (calls.length > 1) throw new Error(`Multiple defineConfig() calls found in ${configPath}. Only one is supported.`);
|
|
10954
|
+
const { configObj } = assertDefined(calls[0], "defineConfig call site missing");
|
|
10955
|
+
if (!configObj) throw new Error(`defineConfig() argument must be an inline object literal in ${configPath} so the SDK can manage the 'id' field.`);
|
|
10956
|
+
const idProp = findIdProperty(configObj);
|
|
10957
|
+
if (!idProp || idProp.value.type !== "Literal") return { id: null };
|
|
10958
|
+
const value = idProp.value.value;
|
|
10959
|
+
return { id: typeof value === "string" && value !== "" ? value : null };
|
|
10960
|
+
}
|
|
10961
|
+
/**
|
|
10962
|
+
* Read-only CI check: the config must already carry a valid app id.
|
|
10963
|
+
* Wrapper/re-export configs (no inline defineConfig call) are exempt,
|
|
10964
|
+
* mirroring the local behavior where {@link ensureConfigId} no-ops.
|
|
10965
|
+
* @param configPath - Absolute path to the config file
|
|
10966
|
+
*/
|
|
10967
|
+
async function assertConfigIdInCI(configPath) {
|
|
10968
|
+
const result = await readConfigId(configPath);
|
|
10969
|
+
if (result === null) return;
|
|
10970
|
+
if (!result.id) throw new Error("tailor.config.ts is missing an 'id'. CI does not auto-generate one (each run would be treated as a separate app and break resource ownership). Run 'tailor-sdk setup' or 'tailor-sdk apply' locally and commit the injected id.");
|
|
10971
|
+
if (!uuidRegex.test(result.id)) throw new Error(`'id' in ${configPath} must be a UUID. To use this config for a separate app, delete it.`);
|
|
10972
|
+
}
|
|
10973
|
+
/**
|
|
10974
|
+
* Ensure the config has an app id for a deploy run.
|
|
10975
|
+
*
|
|
10976
|
+
* Locally, the id is auto-injected when missing (via {@link ensureConfigId}).
|
|
10977
|
+
* In CI, the id is never auto-injected — a missing id is a hard error, because
|
|
10978
|
+
* generating one per run would create a fresh app each time and break resource
|
|
10979
|
+
* ownership. CI dry-runs (plan) perform the same check read-only, so a
|
|
10980
|
+
* forgotten id fails at PR time instead of at deploy. Ephemeral pipelines that
|
|
10981
|
+
* intentionally deploy a fresh app per run (such as e2e harnesses) can opt
|
|
10982
|
+
* back into injection with `TAILOR_PLATFORM_SDK_ALLOW_CI_ID_INJECTION=true`.
|
|
10983
|
+
* Local dry-run and build-only flows skip both injection and the check (no
|
|
10984
|
+
* on-disk side effects are expected, and build-only never talks to the
|
|
10985
|
+
* platform).
|
|
10986
|
+
* @param obj - Inputs
|
|
10987
|
+
* @param obj.configPath - Absolute path to the config file
|
|
10988
|
+
* @param obj.dryRun - Whether this is a dry-run
|
|
10989
|
+
* @param obj.buildOnly - Whether this is a build-only run
|
|
10990
|
+
*/
|
|
10991
|
+
async function ensureConfigIdForDeploy(obj) {
|
|
10992
|
+
const { configPath, dryRun, buildOnly } = obj;
|
|
10993
|
+
if (buildOnly) return;
|
|
10994
|
+
const allowCIInjection = parseBoolean(process.env.TAILOR_PLATFORM_SDK_ALLOW_CI_ID_INJECTION) === true;
|
|
10995
|
+
const strictCI = isCI && !allowCIInjection;
|
|
10996
|
+
if (dryRun) {
|
|
10997
|
+
if (strictCI) await assertConfigIdInCI(configPath);
|
|
10998
|
+
return;
|
|
10999
|
+
}
|
|
11000
|
+
if (strictCI) {
|
|
11001
|
+
await assertConfigIdInCI(configPath);
|
|
11002
|
+
return;
|
|
11003
|
+
}
|
|
11004
|
+
await ensureConfigId(configPath);
|
|
11005
|
+
}
|
|
11006
|
+
const idComment = "// SDK-managed app id — do not edit, except when copying this config to a separate app.";
|
|
11007
|
+
function insertIdProperty(source, configObj, id) {
|
|
11008
|
+
const idLiteral = `id: ${JSON.stringify(id)}`;
|
|
11009
|
+
if (configObj.properties.length > 0) {
|
|
11010
|
+
const firstProp = assertDefined(configObj.properties[0], "first property missing");
|
|
11011
|
+
const lineStart = source.lastIndexOf("\n", firstProp.start - 1) + 1;
|
|
11012
|
+
const indent = source.slice(lineStart, firstProp.start);
|
|
11013
|
+
if (!/^[\t ]*$/.test(indent)) return source.slice(0, firstProp.start) + `${idLiteral}, ` + source.slice(firstProp.start);
|
|
11014
|
+
const insertion = `${idComment}\n${indent}${idLiteral},\n${indent}`;
|
|
11015
|
+
return source.slice(0, firstProp.start) + insertion + source.slice(firstProp.start);
|
|
11016
|
+
}
|
|
11017
|
+
const openBracePos = configObj.start + 1;
|
|
11018
|
+
const braceLineStart = source.lastIndexOf("\n", configObj.start) + 1;
|
|
11019
|
+
const baseIndent = source.slice(braceLineStart).match(/^[\t ]*/)?.[0] ?? "";
|
|
11020
|
+
const innerIndent = `${baseIndent} `;
|
|
11021
|
+
const insertion = `\n${innerIndent}${idComment}\n${innerIndent}${idLiteral},\n${baseIndent}`;
|
|
11022
|
+
return source.slice(0, openBracePos) + insertion + source.slice(openBracePos);
|
|
11023
|
+
}
|
|
11024
|
+
|
|
11025
|
+
//#endregion
|
|
11026
|
+
//#region src/cli/commands/deploy/confirm.ts
|
|
11027
|
+
/**
|
|
11028
|
+
* Confirm reassignment of resources when owner conflicts are detected.
|
|
11029
|
+
* Splits into two scenarios: id regeneration (same sdk-name, different
|
|
11030
|
+
* sdk-app-id) and name mismatch (different sdk-name). Each gets its own
|
|
11031
|
+
* prompt because the user-facing meaning is different.
|
|
11032
|
+
* @param conflicts - Detected owner conflicts
|
|
11033
|
+
* @param appName - Target application name
|
|
11034
|
+
* @param yes - Whether to auto-confirm without prompting
|
|
11035
|
+
* @returns Promise that resolves when confirmation completes
|
|
11036
|
+
*/
|
|
11037
|
+
async function confirmOwnerConflict(conflicts, appName, yes) {
|
|
11038
|
+
if (conflicts.length === 0) return;
|
|
11039
|
+
const idRegenerated = conflicts.filter((c) => c.currentOwner === appName);
|
|
11040
|
+
const nameMismatches = conflicts.filter((c) => c.currentOwner !== appName);
|
|
11041
|
+
if (idRegenerated.length > 0) await confirmIdRegeneration(idRegenerated, appName, yes);
|
|
11042
|
+
if (nameMismatches.length > 0) await confirmNameMismatch(nameMismatches, appName, yes);
|
|
11043
|
+
}
|
|
11044
|
+
async function confirmIdRegeneration(conflicts, appName, yes) {
|
|
11045
|
+
logger.warn(`Application id was regenerated for "${appName}":`);
|
|
11046
|
+
logger.log(" These resources still carry the previous id.");
|
|
11047
|
+
logger.newline();
|
|
11048
|
+
logger.log(` ${styles.info("Resources")}:`);
|
|
11049
|
+
for (const c of conflicts) logger.log(` • ${styles.bold(c.resourceType)} ${styles.info(`"${c.resourceName}"`)}`);
|
|
11050
|
+
if (yes) {
|
|
11051
|
+
logger.success("Re-tagging resources with the new id (--yes flag specified)...", { mode: "plain" });
|
|
11052
|
+
return;
|
|
11053
|
+
}
|
|
11054
|
+
if (!await prompt.confirm({
|
|
11055
|
+
message: `Re-tag these resources with the new id for "${appName}"?\n${styles.dim("(The id in tailor.config.ts was removed since the previous deploy, so a new one was generated)")}`,
|
|
11056
|
+
default: false
|
|
11057
|
+
})) throw new Error(multiline`
|
|
11058
|
+
Apply cancelled. Resources remain tagged with the previous id.
|
|
11059
|
+
To override, run again and confirm, or use --yes flag.
|
|
11060
|
+
`);
|
|
11061
|
+
}
|
|
11062
|
+
async function confirmNameMismatch(conflicts, appName, yes) {
|
|
11063
|
+
const currentOwners = [...new Set(conflicts.map((c) => c.currentOwner))];
|
|
11064
|
+
logger.warn("Application name mismatch detected:");
|
|
11065
|
+
logger.log(` ${styles.warning("Current application(s)")}: ${currentOwners.map((o) => styles.bold(`"${o}"`)).join(", ")}`);
|
|
11066
|
+
logger.log(` ${styles.success("New application")}: ${styles.bold(`"${appName}"`)}`);
|
|
11067
|
+
logger.newline();
|
|
11068
|
+
logger.log(` ${styles.info("Resources")}:`);
|
|
11069
|
+
for (const c of conflicts) logger.log(` • ${styles.bold(c.resourceType)} ${styles.info(`"${c.resourceName}"`)}`);
|
|
11070
|
+
if (yes) {
|
|
11071
|
+
logger.success("Updating resources (--yes flag specified)...", { mode: "plain" });
|
|
11072
|
+
return;
|
|
11073
|
+
}
|
|
11074
|
+
const promptMessage = currentOwners.length === 1 ? `Update these resources to be managed by "${appName}"?\n${styles.dim("(Common when renaming your application)")}` : `Update these resources to be managed by "${appName}"?`;
|
|
11075
|
+
if (!await prompt.confirm({
|
|
11076
|
+
message: promptMessage,
|
|
11077
|
+
default: false
|
|
11078
|
+
})) throw new Error(multiline`
|
|
11079
|
+
Apply cancelled. Resources remain managed by their current applications.
|
|
11080
|
+
To override, run again and confirm, or use --yes flag.
|
|
11081
|
+
`);
|
|
11082
|
+
}
|
|
11083
|
+
/**
|
|
11084
|
+
* Confirm allowing tailor-sdk to manage previously unmanaged resources.
|
|
11085
|
+
* @param resources - Unmanaged resources
|
|
11086
|
+
* @param appName - Target application name
|
|
11087
|
+
* @param yes - Whether to auto-confirm without prompting
|
|
11088
|
+
* @returns Promise that resolves when confirmation completes
|
|
11089
|
+
*/
|
|
11090
|
+
async function confirmUnmanagedResources(resources, appName, yes) {
|
|
11091
|
+
if (resources.length === 0) return;
|
|
11092
|
+
logger.warn("Existing resources not tracked by tailor-sdk were found:");
|
|
11093
|
+
logger.log(` ${styles.info("Resources")}:`);
|
|
11094
|
+
for (const r of resources) logger.log(` • ${styles.bold(r.resourceType)} ${styles.info(`"${r.resourceName}"`)}`);
|
|
11095
|
+
logger.newline();
|
|
11096
|
+
logger.log(" These resources may have been created by older SDK versions, Terraform, or CUE.");
|
|
11097
|
+
logger.log(" To continue, confirm that tailor-sdk should manage them.");
|
|
11098
|
+
logger.log(" If they are managed by another tool (e.g., Terraform), cancel and manage them there instead.");
|
|
11099
|
+
if (yes) {
|
|
11100
|
+
logger.success(`Adding to "${appName}" (--yes flag specified)...`, { mode: "plain" });
|
|
11101
|
+
return;
|
|
11102
|
+
}
|
|
11103
|
+
if (!await prompt.confirm({
|
|
11104
|
+
message: `Allow tailor-sdk to manage these resources for "${appName}"?`,
|
|
11105
|
+
default: false
|
|
11106
|
+
})) throw new Error(multiline`
|
|
11107
|
+
Apply cancelled. Resources remain unmanaged.
|
|
11108
|
+
To override, run again and confirm, or use --yes flag.
|
|
11109
|
+
`);
|
|
11110
|
+
}
|
|
11111
|
+
/**
|
|
11112
|
+
* Confirm deletion of important resources.
|
|
11113
|
+
* @param resources - Resources scheduled for deletion
|
|
11114
|
+
* @param yes - Whether to auto-confirm without prompting
|
|
11115
|
+
* @returns Promise that resolves when confirmation completes
|
|
11116
|
+
*/
|
|
11117
|
+
async function confirmImportantResourceDeletion(resources, yes) {
|
|
11118
|
+
if (resources.length === 0) return;
|
|
11119
|
+
logger.warn("The following resources will be deleted:");
|
|
11120
|
+
logger.log(` ${styles.info("Resources")}:`);
|
|
11121
|
+
for (const r of resources) logger.log(` • ${styles.bold(r.resourceType)} ${styles.error(`"${r.resourceName}"`)}`);
|
|
11122
|
+
logger.newline();
|
|
11123
|
+
logger.log(styles.warning(" Deleting these resources will permanently remove all associated data."));
|
|
11124
|
+
if (yes) {
|
|
11125
|
+
logger.success("Deleting resources (--yes flag specified)...", { mode: "plain" });
|
|
11126
|
+
return;
|
|
11127
|
+
}
|
|
11128
|
+
if (!await prompt.confirm({
|
|
11129
|
+
message: "Are you sure you want to delete these resources?",
|
|
11130
|
+
default: false
|
|
11131
|
+
})) throw new Error(multiline`
|
|
11132
|
+
Apply cancelled. Resources will not be deleted.
|
|
11133
|
+
To override, run again and confirm, or use --yes flag.
|
|
11134
|
+
`);
|
|
11135
|
+
}
|
|
11136
|
+
|
|
11108
11137
|
//#endregion
|
|
11109
11138
|
//#region src/cli/commands/deploy/validate-plan.ts
|
|
11110
11139
|
function validateItems(params) {
|
|
@@ -11336,17 +11365,126 @@ async function validatePlan(input) {
|
|
|
11336
11365
|
*/
|
|
11337
11366
|
function collectIdpUserTriggerTargets(application) {
|
|
11338
11367
|
const targets = /* @__PURE__ */ new Set();
|
|
11339
|
-
const idps = application
|
|
11368
|
+
const idps = collectApplicationIdpNames(application);
|
|
11340
11369
|
for (const executor of Object.values(application.executorService?.executors ?? {})) {
|
|
11341
11370
|
if (executor.trigger.kind !== "idpUser") continue;
|
|
11342
11371
|
if (executor.trigger.idp != null) targets.add(executor.trigger.idp);
|
|
11343
|
-
else if (idps.
|
|
11372
|
+
else if (idps.size === 1) {
|
|
11344
11373
|
const [idp] = idps;
|
|
11345
|
-
if (idp) targets.add(idp
|
|
11374
|
+
if (idp) targets.add(idp);
|
|
11346
11375
|
}
|
|
11347
11376
|
}
|
|
11348
11377
|
return targets;
|
|
11349
11378
|
}
|
|
11379
|
+
function collectDeployIdpUserTriggerTargets(targets) {
|
|
11380
|
+
const triggerTargets = /* @__PURE__ */ new Set();
|
|
11381
|
+
for (const target of targets) for (const idpName of collectIdpUserTriggerTargets(target.application)) triggerTargets.add(idpName);
|
|
11382
|
+
return triggerTargets;
|
|
11383
|
+
}
|
|
11384
|
+
function collectExecutorUsedTailorDBTypes(owner, targets) {
|
|
11385
|
+
const usedTypes = /* @__PURE__ */ new Set();
|
|
11386
|
+
const ownerNamespaces = new Set(owner.application.tailorDBServices.map((service) => service.namespace));
|
|
11387
|
+
const applications = targets.map((target) => target.application);
|
|
11388
|
+
for (const target of targets) {
|
|
11389
|
+
const visibleNamespaces = collectVisibleTailorDBTypeNamespaces(target.application, applications);
|
|
11390
|
+
for (const executor of Object.values(target.application.executorService?.executors ?? {})) if (executor.trigger.kind === "tailordb") {
|
|
11391
|
+
const namespace = findTailorDBNamespace(target.application, executor.trigger.typeName) ?? resolveSameRunNamespace(visibleNamespaces, executor.trigger.typeName, "TailorDB type");
|
|
11392
|
+
if (namespace && ownerNamespaces.has(namespace)) usedTypes.add(executor.trigger.typeName);
|
|
11393
|
+
}
|
|
11394
|
+
}
|
|
11395
|
+
return usedTypes;
|
|
11396
|
+
}
|
|
11397
|
+
function collectExecutorUsedResolvers(owner, targets) {
|
|
11398
|
+
const usedResolvers = /* @__PURE__ */ new Set();
|
|
11399
|
+
const ownerNamespaces = new Set(owner.application.resolverServices.map((service) => service.namespace));
|
|
11400
|
+
const applications = targets.map((target) => target.application);
|
|
11401
|
+
for (const target of targets) {
|
|
11402
|
+
const visibleNamespaces = collectVisibleResolverNamespaces(target.application, applications);
|
|
11403
|
+
for (const executor of Object.values(target.application.executorService?.executors ?? {})) if (executor.trigger.kind === "resolverExecuted") {
|
|
11404
|
+
const namespace = findResolverNamespace(target.application, executor.trigger.resolverName) ?? resolveSameRunNamespace(visibleNamespaces, executor.trigger.resolverName, "Resolver");
|
|
11405
|
+
if (namespace && ownerNamespaces.has(namespace)) usedResolvers.add(executor.trigger.resolverName);
|
|
11406
|
+
}
|
|
11407
|
+
}
|
|
11408
|
+
return usedResolvers;
|
|
11409
|
+
}
|
|
11410
|
+
function collectExpectedLocalStaticWebsiteNames(targets) {
|
|
11411
|
+
const websiteNames = /* @__PURE__ */ new Set();
|
|
11412
|
+
for (const target of targets) for (const website of target.application.staticWebsiteServices) websiteNames.add(website.name);
|
|
11413
|
+
return websiteNames;
|
|
11414
|
+
}
|
|
11415
|
+
function addPossiblyAmbiguousNamespace(namespaces, key, namespace) {
|
|
11416
|
+
if (namespaces.has(key)) {
|
|
11417
|
+
if (namespaces.get(key) !== namespace) namespaces.set(key, void 0);
|
|
11418
|
+
return;
|
|
11419
|
+
}
|
|
11420
|
+
namespaces.set(key, namespace);
|
|
11421
|
+
}
|
|
11422
|
+
function collectVisibleResources(params) {
|
|
11423
|
+
const { application, applications, visibleKeysOf, resourcesOf, createResult, addResource } = params;
|
|
11424
|
+
const visibleKeys = visibleKeysOf(application);
|
|
11425
|
+
const result = createResult();
|
|
11426
|
+
for (const candidate of applications) for (const resource of resourcesOf(candidate)) {
|
|
11427
|
+
if (!visibleKeys.has(resource.visibilityKey)) continue;
|
|
11428
|
+
addResource(result, resource.resourceKey, resource.visibilityKey);
|
|
11429
|
+
}
|
|
11430
|
+
return result;
|
|
11431
|
+
}
|
|
11432
|
+
function collectApplicationTailorDBNamespaces(application) {
|
|
11433
|
+
return /* @__PURE__ */ new Set([...application.tailorDBServices.map((service) => service.namespace), ...application.externalTailorDBNamespaces]);
|
|
11434
|
+
}
|
|
11435
|
+
function* tailorDBTypeResources(application) {
|
|
11436
|
+
for (const service of application.tailorDBServices) for (const typeName of Object.keys(service.types)) yield {
|
|
11437
|
+
visibilityKey: service.namespace,
|
|
11438
|
+
resourceKey: typeName
|
|
11439
|
+
};
|
|
11440
|
+
}
|
|
11441
|
+
function collectVisibleTailorDBTypeNamespaces(application, applications) {
|
|
11442
|
+
return collectVisibleResources({
|
|
11443
|
+
application,
|
|
11444
|
+
applications,
|
|
11445
|
+
visibleKeysOf: collectApplicationTailorDBNamespaces,
|
|
11446
|
+
resourcesOf: tailorDBTypeResources,
|
|
11447
|
+
createResult: () => /* @__PURE__ */ new Map(),
|
|
11448
|
+
addResource: addPossiblyAmbiguousNamespace
|
|
11449
|
+
});
|
|
11450
|
+
}
|
|
11451
|
+
function collectApplicationResolverNamespaces(application) {
|
|
11452
|
+
return new Set(application.subgraphs.filter((subgraph) => subgraph.Type === "pipeline").map((subgraph) => subgraph.Name));
|
|
11453
|
+
}
|
|
11454
|
+
function* idpNameResources(application) {
|
|
11455
|
+
for (const name of collectApplicationIdpNames(application)) yield {
|
|
11456
|
+
visibilityKey: name,
|
|
11457
|
+
resourceKey: name
|
|
11458
|
+
};
|
|
11459
|
+
}
|
|
11460
|
+
function collectVisibleIdpNames(application, applications) {
|
|
11461
|
+
return collectVisibleResources({
|
|
11462
|
+
application,
|
|
11463
|
+
applications,
|
|
11464
|
+
visibleKeysOf: collectApplicationIdpNames,
|
|
11465
|
+
resourcesOf: idpNameResources,
|
|
11466
|
+
createResult: () => /* @__PURE__ */ new Set(),
|
|
11467
|
+
addResource: (names, name) => {
|
|
11468
|
+
names.add(name);
|
|
11469
|
+
}
|
|
11470
|
+
});
|
|
11471
|
+
}
|
|
11472
|
+
function* resolverResources(application) {
|
|
11473
|
+
for (const service of application.resolverServices) for (const resolver of Object.values(service.resolvers)) yield {
|
|
11474
|
+
visibilityKey: service.namespace,
|
|
11475
|
+
resourceKey: resolver.name
|
|
11476
|
+
};
|
|
11477
|
+
}
|
|
11478
|
+
function collectVisibleResolverNamespaces(application, applications) {
|
|
11479
|
+
return collectVisibleResources({
|
|
11480
|
+
application,
|
|
11481
|
+
applications,
|
|
11482
|
+
visibleKeysOf: collectApplicationResolverNamespaces,
|
|
11483
|
+
resourcesOf: resolverResources,
|
|
11484
|
+
createResult: () => /* @__PURE__ */ new Map(),
|
|
11485
|
+
addResource: addPossiblyAmbiguousNamespace
|
|
11486
|
+
});
|
|
11487
|
+
}
|
|
11350
11488
|
async function shouldForceApplyAll(client, workspaceId, application, functionEntries) {
|
|
11351
11489
|
const desiredLabels = (await buildMetaRequest({
|
|
11352
11490
|
trn: resourceTrn(workspaceId, "application", application.name),
|
|
@@ -11383,39 +11521,31 @@ async function shouldForceApplyAll(client, workspaceId, application, functionEnt
|
|
|
11383
11521
|
functionEntries.forEach((entry) => {
|
|
11384
11522
|
candidateTrns.add(resourceTrn(workspaceId, "function_registry", entry.name));
|
|
11385
11523
|
});
|
|
11386
|
-
for (const trn of candidateTrns)
|
|
11387
|
-
const
|
|
11524
|
+
for (const trn of candidateTrns) {
|
|
11525
|
+
const metadata = await getOrNull(async () => {
|
|
11526
|
+
const { metadata } = await client.getMetadata({ trn });
|
|
11527
|
+
return metadata;
|
|
11528
|
+
});
|
|
11388
11529
|
if (metadata?.labels["sdk-name"] !== application.name) continue;
|
|
11389
11530
|
if (!hasMatchingSdkVersion(metadata.labels, desiredLabels)) return true;
|
|
11390
|
-
} catch (error) {
|
|
11391
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) continue;
|
|
11392
|
-
throw error;
|
|
11393
11531
|
}
|
|
11394
11532
|
return false;
|
|
11395
11533
|
}
|
|
11396
11534
|
/**
|
|
11397
11535
|
* Decide which renamed-away applications should be deleted. Excludes the
|
|
11398
|
-
*
|
|
11399
|
-
* by name would destroy
|
|
11536
|
+
* deploy targets themselves: id regeneration alone keeps the name unchanged,
|
|
11537
|
+
* so deleting by name would destroy a live app.
|
|
11400
11538
|
* @param params - Inputs for the computation
|
|
11401
11539
|
* @param params.conflicts - Detected owner conflicts across all services
|
|
11402
11540
|
* @param params.resourceOwners - App names that still own resources we don't manage
|
|
11403
|
-
* @param params.
|
|
11541
|
+
* @param params.protectedAppNames - App names that must not be deleted
|
|
11404
11542
|
* @returns Names of empty old applications that should be deleted
|
|
11405
11543
|
*/
|
|
11406
11544
|
function computeRenamedAppDeletions(params) {
|
|
11407
|
-
const { conflicts, resourceOwners,
|
|
11408
|
-
return [...new Set(conflicts.map((c) => c.currentOwner))].filter((owner) => !resourceOwners.has(owner) && owner
|
|
11545
|
+
const { conflicts, resourceOwners, protectedAppNames } = params;
|
|
11546
|
+
return [...new Set(conflicts.map((c) => c.currentOwner))].filter((owner) => !resourceOwners.has(owner) && !protectedAppNames.has(owner));
|
|
11409
11547
|
}
|
|
11410
|
-
|
|
11411
|
-
* Format and output the plan results, then return a summary of change counts.
|
|
11412
|
-
* In JSON dry-run mode a JSON payload is written to stdout. In all other modes
|
|
11413
|
-
* the human-readable diff goes to stdout (dry-run) or stderr (apply).
|
|
11414
|
-
* @param results - Planned results across all services
|
|
11415
|
-
* @param opts - Output options (dry-run mode flag)
|
|
11416
|
-
* @returns Aggregated plan summary counts
|
|
11417
|
-
*/
|
|
11418
|
-
function printPlanResults(results, opts) {
|
|
11548
|
+
function buildPlanReport(results) {
|
|
11419
11549
|
const executorEntries = formatExecutorChangeEntries(results.executor.changeSet, buildPlannedExecutorsByName(results.executor.changeSet), results.functionRegistry.executorFunctionChanges);
|
|
11420
11550
|
const resolverEntries = formatResolverChangeEntries(results.pipeline.changeSet.resolver, results.functionRegistry.resolverFunctionChanges);
|
|
11421
11551
|
const workflowEntries = formatWorkflowChangeEntries(results.workflow.changeSet, results.functionRegistry.workflowJobChanges);
|
|
@@ -11482,132 +11612,808 @@ function printPlanResults(results, opts) {
|
|
|
11482
11612
|
...results.workflow.conflicts,
|
|
11483
11613
|
...results.secretManager.conflicts
|
|
11484
11614
|
];
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
11512
|
-
|
|
11513
|
-
|
|
11514
|
-
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
...buildGroupedDisplayLines(
|
|
11549
|
-
...
|
|
11550
|
-
...results.
|
|
11551
|
-
...results.
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11615
|
+
const changes = [
|
|
11616
|
+
...allDisplayEntries,
|
|
11617
|
+
...tailorDBServiceActions.map(({ action, name }) => ({
|
|
11618
|
+
action,
|
|
11619
|
+
name,
|
|
11620
|
+
labels: ["tailorDB"],
|
|
11621
|
+
namespace: void 0
|
|
11622
|
+
})),
|
|
11623
|
+
...pipelineServiceActions.map(({ action, name }) => ({
|
|
11624
|
+
action,
|
|
11625
|
+
name,
|
|
11626
|
+
labels: ["pipeline"],
|
|
11627
|
+
namespace: void 0
|
|
11628
|
+
})),
|
|
11629
|
+
...idpServiceActions.map(({ action, name }) => ({
|
|
11630
|
+
action,
|
|
11631
|
+
name,
|
|
11632
|
+
labels: ["idp"],
|
|
11633
|
+
namespace: void 0
|
|
11634
|
+
})),
|
|
11635
|
+
...authServiceActions.map(({ action, name }) => ({
|
|
11636
|
+
action,
|
|
11637
|
+
name,
|
|
11638
|
+
labels: ["auth"],
|
|
11639
|
+
namespace: void 0
|
|
11640
|
+
})),
|
|
11641
|
+
...formatChangeSetEntries(otherFunctionRegistryChanges),
|
|
11642
|
+
...formatChangeSetEntries(results.staticWebsite.changeSet, ["staticWebsite"]),
|
|
11643
|
+
...formatChangeSetEntries(results.staticWebsite.customDomainChangeSet, ["customDomain"]),
|
|
11644
|
+
...formatChangeSetEntries(results.aiGateway.changeSet, ["aiGateway"]),
|
|
11645
|
+
...formatChangeSetEntries(results.app, ["application"]),
|
|
11646
|
+
...formatChangeSetEntries(results.secretManager.vaultChangeSet, ["vault"]),
|
|
11647
|
+
...formatChangeSetEntries(results.secretManager.secretChangeSet, ["secret"])
|
|
11648
|
+
].map(({ action, name, labels, namespace }) => ({
|
|
11649
|
+
action,
|
|
11650
|
+
name,
|
|
11651
|
+
labels,
|
|
11652
|
+
namespace
|
|
11653
|
+
}));
|
|
11654
|
+
const warnings = [...allUnmanaged.map(({ resourceType, resourceName }) => ({
|
|
11655
|
+
type: "unmanaged",
|
|
11656
|
+
resourceType,
|
|
11657
|
+
name: resourceName
|
|
11658
|
+
})), ...results.secretManager.skippedSecrets.map((name) => ({
|
|
11659
|
+
type: "skippedSecret",
|
|
11660
|
+
resourceType: "secret",
|
|
11661
|
+
name
|
|
11662
|
+
}))];
|
|
11663
|
+
const conflicts = allConflicts.map(({ resourceType, resourceName, currentOwner }) => ({
|
|
11664
|
+
resourceType,
|
|
11665
|
+
name: resourceName,
|
|
11666
|
+
currentOwner
|
|
11667
|
+
}));
|
|
11668
|
+
const allLines = [
|
|
11669
|
+
...buildGroupedDisplayLines(results.functionRegistry.changeSet.title, formatChangeSetEntries(otherFunctionRegistryChanges)),
|
|
11670
|
+
...results.staticWebsite.changeSet.lines(),
|
|
11671
|
+
...results.staticWebsite.customDomainChangeSet.lines(),
|
|
11672
|
+
...results.aiGateway.changeSet.lines(),
|
|
11673
|
+
...results.app.lines(),
|
|
11674
|
+
...buildGroupedDisplayLines("TailorDB", tailorDBEntries, tailorDBServiceActions),
|
|
11675
|
+
...buildGroupedDisplayLines("Resolver", pipelineEntries, pipelineServiceActions),
|
|
11676
|
+
...buildGroupedDisplayLines("Executor", executorEntries),
|
|
11677
|
+
...buildGroupedDisplayLines("Workflow", workflowEntries),
|
|
11678
|
+
...buildGroupedDisplayLines("IdP", idpEntries, idpServiceActions),
|
|
11679
|
+
...buildGroupedDisplayLines("Auth", authEntries, authServiceActions),
|
|
11680
|
+
...results.secretManager.vaultChangeSet.lines(),
|
|
11681
|
+
...results.secretManager.secretChangeSet.lines()
|
|
11682
|
+
];
|
|
11683
|
+
if (allUnmanaged.length > 0) {
|
|
11684
|
+
allLines.push(styles.bold("Unmanaged resources (not in config):"));
|
|
11685
|
+
for (const { resourceType, resourceName } of allUnmanaged) allLines.push(` ${styles.warning("⚠")} ${styles.bold(resourceType)} "${resourceName}"`);
|
|
11686
|
+
}
|
|
11687
|
+
if (results.secretManager.skippedSecrets.length > 0) {
|
|
11688
|
+
allLines.push(styles.bold("Secret Manager secrets (skipped - no value provided):"));
|
|
11689
|
+
for (const name of results.secretManager.skippedSecrets) allLines.push(` ${styles.dim("○")} ${name}`);
|
|
11690
|
+
}
|
|
11691
|
+
if (allConflicts.length > 0) {
|
|
11692
|
+
allLines.push(styles.bold("Owner conflicts (will require confirmation on apply):"));
|
|
11693
|
+
for (const { resourceType, resourceName, currentOwner } of allConflicts) allLines.push(` ${styles.warning("!")} ${styles.bold(resourceType)} "${resourceName}" — owned by "${currentOwner}"`);
|
|
11694
|
+
}
|
|
11695
|
+
allLines.push(formatPlanSummary(summary));
|
|
11696
|
+
return {
|
|
11697
|
+
summary,
|
|
11698
|
+
json: {
|
|
11699
|
+
summary,
|
|
11700
|
+
changes,
|
|
11701
|
+
warnings,
|
|
11702
|
+
conflicts
|
|
11703
|
+
},
|
|
11704
|
+
lines: allLines
|
|
11705
|
+
};
|
|
11706
|
+
}
|
|
11707
|
+
/**
|
|
11708
|
+
* Format and output the plan results, then return a summary of change counts.
|
|
11709
|
+
* In JSON dry-run mode a JSON payload is written to stdout. In all other modes
|
|
11710
|
+
* the human-readable diff goes to stdout (dry-run) or stderr (apply).
|
|
11711
|
+
* @param results - Planned results across all services
|
|
11712
|
+
* @param opts - Output options (dry-run mode flag)
|
|
11713
|
+
* @returns Aggregated plan summary counts
|
|
11714
|
+
*/
|
|
11715
|
+
function printPlanResults(results, opts) {
|
|
11716
|
+
const report = buildPlanReport(results);
|
|
11717
|
+
if (logger.jsonMode && opts?.dryRun) {
|
|
11718
|
+
logger.out(report.json);
|
|
11719
|
+
return report.summary;
|
|
11720
|
+
}
|
|
11721
|
+
const output = report.lines.join("\n");
|
|
11722
|
+
if (opts?.dryRun) logger.out(output);
|
|
11723
|
+
else logger.log(output);
|
|
11724
|
+
return report.summary;
|
|
11725
|
+
}
|
|
11726
|
+
/**
|
|
11727
|
+
* Summarize plan counts from display entries, service actions, and non-grouped changesets.
|
|
11728
|
+
* @param results - Planned apply results
|
|
11729
|
+
* @param displayEntries - All grouped display entries across sections
|
|
11730
|
+
* @param serviceActions - All service-level namespace actions
|
|
11731
|
+
* @returns Aggregated plan summary
|
|
11732
|
+
*/
|
|
11733
|
+
function summarizePlanResults(results, displayEntries, serviceActions) {
|
|
11734
|
+
const summary = {
|
|
11735
|
+
create: 0,
|
|
11736
|
+
update: 0,
|
|
11737
|
+
delete: 0,
|
|
11738
|
+
replace: 0
|
|
11739
|
+
};
|
|
11740
|
+
for (const entry of displayEntries) summary[entry.action] += 1;
|
|
11741
|
+
for (const sa of serviceActions) summary[sa.action] += 1;
|
|
11742
|
+
const { otherChanges } = splitFunctionRegistryChanges(results.functionRegistry.changeSet);
|
|
11743
|
+
const nonGrouped = summarizeChangeSets([
|
|
11744
|
+
otherChanges,
|
|
11745
|
+
results.staticWebsite.changeSet,
|
|
11746
|
+
results.staticWebsite.customDomainChangeSet,
|
|
11747
|
+
results.aiGateway.changeSet,
|
|
11748
|
+
results.app,
|
|
11749
|
+
results.secretManager.vaultChangeSet,
|
|
11750
|
+
results.secretManager.secretChangeSet
|
|
11751
|
+
]);
|
|
11752
|
+
summary.create += nonGrouped.create;
|
|
11753
|
+
summary.update += nonGrouped.update;
|
|
11754
|
+
summary.delete += nonGrouped.delete;
|
|
11755
|
+
summary.replace += nonGrouped.replace;
|
|
11756
|
+
return summary;
|
|
11757
|
+
}
|
|
11758
|
+
/**
|
|
11759
|
+
* Parse the deploy config option into one or more config paths.
|
|
11760
|
+
* @param configPath - Raw `--config` option value
|
|
11761
|
+
* @returns Config paths, or one undefined entry to preserve default config lookup
|
|
11762
|
+
*/
|
|
11763
|
+
function parseDeployConfigPaths(configPath) {
|
|
11764
|
+
const rawConfigPath = configPath ?? process.env.TAILOR_PLATFORM_SDK_CONFIG_PATH;
|
|
11765
|
+
if (rawConfigPath === void 0) return [void 0];
|
|
11766
|
+
const configPaths = rawConfigPath.split(",").map((entry) => entry.trim());
|
|
11767
|
+
if (configPaths.some((entry) => entry.length === 0)) throw new Error("--config must contain one or more non-empty config paths.");
|
|
11768
|
+
return configPaths;
|
|
11769
|
+
}
|
|
11770
|
+
async function buildDeploymentTarget(params) {
|
|
11771
|
+
const { configPath, dryRun, buildOnly, noCache, packageVersion, cacheDir } = params;
|
|
11772
|
+
const { config, plugins } = await withSpan("build.loadConfig", async () => {
|
|
11773
|
+
const foundPath = loadConfigPath(configPath);
|
|
11774
|
+
if (foundPath) {
|
|
11775
|
+
const resolvedPath = path.resolve(process.cwd(), foundPath);
|
|
11776
|
+
if (fs$1.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
|
|
11777
|
+
configPath: resolvedPath,
|
|
11778
|
+
dryRun,
|
|
11779
|
+
buildOnly
|
|
11780
|
+
});
|
|
11781
|
+
}
|
|
11782
|
+
return loadConfig(configPath);
|
|
11783
|
+
});
|
|
11784
|
+
const configDir = path.dirname(config.path);
|
|
11785
|
+
const lockfilePath = findUpSync("pnpm-lock.yaml", { cwd: configDir }) ?? findUpSync("package-lock.json", { cwd: configDir }) ?? findUpSync("yarn.lock", { cwd: configDir }) ?? findUpSync("bun.lock", { cwd: configDir });
|
|
11786
|
+
const cacheManager = createCacheManager({
|
|
11787
|
+
enabled: !noCache,
|
|
11788
|
+
cacheDir,
|
|
11789
|
+
sdkVersion: packageVersion,
|
|
11790
|
+
lockfileHash: lockfilePath ? hashFile(lockfilePath) : void 0
|
|
11791
|
+
});
|
|
11792
|
+
let pluginManager;
|
|
11793
|
+
if (plugins.length > 0) pluginManager = new PluginManager(plugins);
|
|
11794
|
+
await withSpan("build.generateUserTypes", () => generateUserTypes({
|
|
11795
|
+
config,
|
|
11796
|
+
configPath: config.path
|
|
11797
|
+
}));
|
|
11798
|
+
let application;
|
|
11799
|
+
let workflowBuildResult;
|
|
11800
|
+
let httpAdapterBuildResult;
|
|
11801
|
+
let bundledScripts;
|
|
11802
|
+
try {
|
|
11803
|
+
const result = await withSpan("build.loadApplication", () => loadApplication({
|
|
11804
|
+
config,
|
|
11805
|
+
pluginManager,
|
|
11806
|
+
bundleCache: cacheManager.bundleCache
|
|
11807
|
+
}));
|
|
11808
|
+
application = result.application;
|
|
11809
|
+
workflowBuildResult = result.workflowBuildResult;
|
|
11810
|
+
httpAdapterBuildResult = result.httpAdapterBuildResult;
|
|
11811
|
+
bundledScripts = result.bundledScripts;
|
|
11812
|
+
} finally {
|
|
11813
|
+
cacheManager.finalize();
|
|
11814
|
+
}
|
|
11815
|
+
return {
|
|
11816
|
+
config,
|
|
11817
|
+
application,
|
|
11818
|
+
workflowBuildResult,
|
|
11819
|
+
httpAdapterBuildResult,
|
|
11820
|
+
bundledScripts
|
|
11821
|
+
};
|
|
11822
|
+
}
|
|
11823
|
+
async function buildDeploymentTargets(params) {
|
|
11824
|
+
const { configPaths, buildTarget = buildDeploymentTarget, ...targetParams } = params;
|
|
11825
|
+
return Promise.all(configPaths.map((configPath) => buildTarget({
|
|
11826
|
+
...targetParams,
|
|
11827
|
+
configPath
|
|
11828
|
+
})));
|
|
11829
|
+
}
|
|
11830
|
+
function setBundledScript(target, name, code, kind) {
|
|
11831
|
+
if (target.has(name)) throw new Error(`Duplicate ${kind} bundle name "${name}" across config files.`);
|
|
11832
|
+
target.set(name, code);
|
|
11833
|
+
}
|
|
11834
|
+
function addBundledScripts(target, source, kind) {
|
|
11835
|
+
for (const [name, code] of source) setBundledScript(target, name, code, kind);
|
|
11836
|
+
}
|
|
11837
|
+
function collectDuplicateBundleNames(targets, select) {
|
|
11838
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11839
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
11840
|
+
for (const target of targets) for (const name of select(target).keys()) {
|
|
11841
|
+
if (seen.has(name)) duplicates.add(name);
|
|
11842
|
+
seen.add(name);
|
|
11843
|
+
}
|
|
11844
|
+
return duplicates;
|
|
11845
|
+
}
|
|
11846
|
+
function addResolverBundledScripts(target, source, duplicateNames) {
|
|
11847
|
+
const consumedNames = /* @__PURE__ */ new Set();
|
|
11848
|
+
for (const service of source.application.resolverServices) for (const resolver of Object.values(service.resolvers)) {
|
|
11849
|
+
const code = source.bundledScripts.resolvers.get(resolver.name);
|
|
11850
|
+
if (code === void 0) continue;
|
|
11851
|
+
setBundledScript(target, duplicateNames.has(resolver.name) ? resolverFunctionName(service.namespace, resolver.name) : resolver.name, code, "resolver");
|
|
11852
|
+
consumedNames.add(resolver.name);
|
|
11853
|
+
}
|
|
11854
|
+
for (const [name, code] of source.bundledScripts.resolvers) if (!consumedNames.has(name)) setBundledScript(target, name, code, "resolver");
|
|
11855
|
+
}
|
|
11856
|
+
/**
|
|
11857
|
+
* Merge per-config bundled scripts into one build-only result.
|
|
11858
|
+
* @param targets - Built deployment targets to merge
|
|
11859
|
+
* @returns Combined bundled scripts across all targets
|
|
11860
|
+
*/
|
|
11861
|
+
function mergeBundledScripts(targets) {
|
|
11862
|
+
const bundledScripts = {
|
|
11863
|
+
resolvers: /* @__PURE__ */ new Map(),
|
|
11864
|
+
executors: /* @__PURE__ */ new Map(),
|
|
11865
|
+
workflowJobs: /* @__PURE__ */ new Map(),
|
|
11866
|
+
authHooks: /* @__PURE__ */ new Map()
|
|
11867
|
+
};
|
|
11868
|
+
const duplicateResolverNames = collectDuplicateBundleNames(targets, (target) => target.bundledScripts.resolvers);
|
|
11869
|
+
for (const target of targets) {
|
|
11870
|
+
addResolverBundledScripts(bundledScripts.resolvers, target, duplicateResolverNames);
|
|
11871
|
+
addBundledScripts(bundledScripts.executors, target.bundledScripts.executors, "executor");
|
|
11872
|
+
addBundledScripts(bundledScripts.workflowJobs, target.bundledScripts.workflowJobs, "workflow job");
|
|
11873
|
+
addBundledScripts(bundledScripts.authHooks, target.bundledScripts.authHooks, "auth hook");
|
|
11874
|
+
}
|
|
11875
|
+
return bundledScripts;
|
|
11876
|
+
}
|
|
11877
|
+
/**
|
|
11878
|
+
* Reject resource names that collide across configs. Each checked resource
|
|
11879
|
+
* type is workspace-global (its TRN is not qualified by namespace or app), so
|
|
11880
|
+
* a duplicate name would make two configs target the same platform resource:
|
|
11881
|
+
* one config's apply could overwrite the other's, or a fresh create could be
|
|
11882
|
+
* attempted twice. Resolver and auth-hook function names are namespace- or
|
|
11883
|
+
* app-qualified and are intentionally excluded.
|
|
11884
|
+
* @param targets - Built deployment targets to check
|
|
11885
|
+
*/
|
|
11886
|
+
function assertUniqueGlobalResourceNames(targets) {
|
|
11887
|
+
for (const check of DEPLOY_MANAGED_RESOURCE_DEFINITIONS) {
|
|
11888
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11889
|
+
for (const target of targets) for (const name of check.namesOf(target)) {
|
|
11890
|
+
if (seen.has(name)) throw new Error(`Duplicate ${check.resourceLabel} name "${name}" across config files. ${check.resourceLabel} names must be unique across all configs in a single deploy.`);
|
|
11891
|
+
seen.add(name);
|
|
11892
|
+
}
|
|
11893
|
+
}
|
|
11894
|
+
}
|
|
11895
|
+
function collectPlannedExternalTailorDBServices(target, targets) {
|
|
11896
|
+
const externalNamespaces = new Set(target.application.externalTailorDBNamespaces);
|
|
11897
|
+
if (externalNamespaces.size === 0) return [];
|
|
11898
|
+
return targets.flatMap((candidate) => candidate.application.tailorDBServices.filter((service) => externalNamespaces.has(service.namespace)));
|
|
11899
|
+
}
|
|
11900
|
+
function collectExternalAuthIdpConfigNames(targets) {
|
|
11901
|
+
const idpConfigNames = /* @__PURE__ */ new Map();
|
|
11902
|
+
for (const target of targets) {
|
|
11903
|
+
const authService = target.application.authService;
|
|
11904
|
+
if (!authService) continue;
|
|
11905
|
+
const { name } = authService.config;
|
|
11906
|
+
const idpConfigName = authService.config.idProvider?.name;
|
|
11907
|
+
if (idpConfigNames.has(name) && idpConfigNames.get(name) !== idpConfigName) throw new Error(`Auth namespace "${name}" is defined by multiple config files with different IdP configs. Auth namespace names must be unique across all configs in a single deploy.`);
|
|
11908
|
+
idpConfigNames.set(name, idpConfigName);
|
|
11909
|
+
}
|
|
11910
|
+
return idpConfigNames;
|
|
11911
|
+
}
|
|
11912
|
+
function collectDeployRunPlanInputs(targets) {
|
|
11913
|
+
return {
|
|
11914
|
+
idpUserTriggerTargets: collectDeployIdpUserTriggerTargets(targets),
|
|
11915
|
+
expectedLocalStaticWebsiteNames: collectExpectedLocalStaticWebsiteNames(targets),
|
|
11916
|
+
externalAuthIdpConfigNames: collectExternalAuthIdpConfigNames(targets)
|
|
11917
|
+
};
|
|
11918
|
+
}
|
|
11919
|
+
async function planDeploymentTarget(params) {
|
|
11920
|
+
const { target, targets, runInputs, client, workspaceId, noSchemaCheck } = params;
|
|
11921
|
+
const { config, application, workflowBuildResult, httpAdapterBuildResult, bundledScripts } = target;
|
|
11922
|
+
await withSpan("plan.validateTailorDBTypeNames", () => assertUniqueTailorDBTypeNamesWithExternal({
|
|
11923
|
+
client,
|
|
11924
|
+
workspaceId,
|
|
11925
|
+
tailorDBServices: application.tailorDBServices,
|
|
11926
|
+
externalTailorDBNamespaces: application.externalTailorDBNamespaces,
|
|
11927
|
+
plannedExternalTailorDBServices: collectPlannedExternalTailorDBServices(target, targets)
|
|
11928
|
+
}));
|
|
11929
|
+
const workflowService = application.workflowService;
|
|
11930
|
+
const functionEntries = collectFunctionEntries(application, filterBundledWorkflowJobs(workflowService?.jobs ?? [], workflowBuildResult?.usedJobNames ?? []), bundledScripts);
|
|
11931
|
+
const forceApplyAll = await withSpan("plan.detectSdkVersionChange", () => shouldForceApplyAll(client, workspaceId, application, functionEntries));
|
|
11932
|
+
return withSpan("plan", async () => {
|
|
11933
|
+
const applications = targets.map((target) => target.application);
|
|
11934
|
+
const tailorDBTypeNamespaces = collectVisibleTailorDBTypeNamespaces(application, applications);
|
|
11935
|
+
const resolverNamespaces = collectVisibleResolverNamespaces(application, applications);
|
|
11936
|
+
const idpNames = collectVisibleIdpNames(application, applications);
|
|
11937
|
+
const ctx = {
|
|
11938
|
+
client,
|
|
11939
|
+
workspaceId,
|
|
11940
|
+
application,
|
|
11941
|
+
forRemoval: false,
|
|
11942
|
+
config,
|
|
11943
|
+
noSchemaCheck,
|
|
11944
|
+
forceApplyAll,
|
|
11945
|
+
...runInputs,
|
|
11946
|
+
executorUsedTailorDBTypes: collectExecutorUsedTailorDBTypes(target, targets),
|
|
11947
|
+
executorUsedResolvers: collectExecutorUsedResolvers(target, targets),
|
|
11948
|
+
tailorDBTypeNamespaces,
|
|
11949
|
+
resolverNamespaces,
|
|
11950
|
+
idpNames
|
|
11951
|
+
};
|
|
11952
|
+
const functionRegistry = await withSpan("plan.functionRegistry", () => planFunctionRegistry(client, workspaceId, application.name, application.id, functionEntries));
|
|
11953
|
+
const unchangedWorkflowJobs = new Set(functionRegistry.changeSet.unchanged.filter((entry) => entry.name.startsWith(WORKFLOW_PREFIX)).map((entry) => entry.name.slice(WORKFLOW_PREFIX.length)));
|
|
11954
|
+
const [tailorDB, staticWebsite, aiGateway, idp, auth, pipeline, app, executor, workflow, secretManager] = await Promise.all([
|
|
11955
|
+
withSpan("plan.tailorDB", () => planTailorDB(ctx)),
|
|
11956
|
+
withSpan("plan.staticWebsite", () => planStaticWebsite(ctx)),
|
|
11957
|
+
withSpan("plan.aiGateway", () => planAIGateway(ctx)),
|
|
11958
|
+
withSpan("plan.idp", () => planIdP(ctx)),
|
|
11959
|
+
withSpan("plan.auth", () => planAuth(ctx)),
|
|
11960
|
+
withSpan("plan.pipeline", () => planPipeline(ctx)),
|
|
11961
|
+
withSpan("plan.application", () => planApplication(ctx, httpAdapterBuildResult)),
|
|
11962
|
+
withSpan("plan.executor", () => planExecutor(ctx)),
|
|
11963
|
+
withSpan("plan.workflow", () => planWorkflow(client, workspaceId, application.name, application.id, workflowService?.workflows ?? {}, workflowBuildResult?.mainJobDeps ?? {}, unchangedWorkflowJobs)),
|
|
11964
|
+
withSpan("plan.secretManager", () => planSecretManager(ctx))
|
|
11965
|
+
]);
|
|
11966
|
+
return {
|
|
11967
|
+
application,
|
|
11968
|
+
functionRegistry,
|
|
11969
|
+
tailorDB,
|
|
11970
|
+
staticWebsite,
|
|
11971
|
+
aiGateway,
|
|
11972
|
+
idp,
|
|
11973
|
+
auth,
|
|
11974
|
+
pipeline,
|
|
11975
|
+
app,
|
|
11976
|
+
executor,
|
|
11977
|
+
workflow,
|
|
11978
|
+
secretManager
|
|
11979
|
+
};
|
|
11980
|
+
});
|
|
11981
|
+
}
|
|
11982
|
+
async function planDeploymentTargets(params) {
|
|
11983
|
+
const { targets, planTarget = planDeploymentTarget, ...planParams } = params;
|
|
11984
|
+
return Promise.all(targets.map((target) => planTarget({
|
|
11985
|
+
...planParams,
|
|
11986
|
+
target,
|
|
11987
|
+
targets
|
|
11988
|
+
})));
|
|
11989
|
+
}
|
|
11990
|
+
function deploymentPlanResults(deployment) {
|
|
11991
|
+
const { application: _application, ...results } = deployment;
|
|
11992
|
+
return results;
|
|
11993
|
+
}
|
|
11994
|
+
function collectOwnerConflicts(results) {
|
|
11995
|
+
return [
|
|
11996
|
+
...results.functionRegistry.conflicts,
|
|
11997
|
+
...results.tailorDB.conflicts,
|
|
11998
|
+
...results.staticWebsite.conflicts,
|
|
11999
|
+
...results.aiGateway.conflicts,
|
|
12000
|
+
...results.idp.conflicts,
|
|
12001
|
+
...results.auth.conflicts,
|
|
12002
|
+
...results.pipeline.conflicts,
|
|
12003
|
+
...results.executor.conflicts,
|
|
12004
|
+
...results.workflow.conflicts,
|
|
12005
|
+
...results.secretManager.conflicts
|
|
12006
|
+
];
|
|
12007
|
+
}
|
|
12008
|
+
function collectUnmanagedResources(results) {
|
|
12009
|
+
return [
|
|
12010
|
+
...results.functionRegistry.unmanaged,
|
|
12011
|
+
...results.tailorDB.unmanaged,
|
|
12012
|
+
...results.staticWebsite.unmanaged,
|
|
12013
|
+
...results.aiGateway.unmanaged,
|
|
12014
|
+
...results.idp.unmanaged,
|
|
12015
|
+
...results.auth.unmanaged,
|
|
12016
|
+
...results.pipeline.unmanaged,
|
|
12017
|
+
...results.executor.unmanaged,
|
|
12018
|
+
...results.workflow.unmanaged,
|
|
12019
|
+
...results.secretManager.unmanaged
|
|
11561
12020
|
];
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
12021
|
+
}
|
|
12022
|
+
function collectResourceOwners(results) {
|
|
12023
|
+
return /* @__PURE__ */ new Set([
|
|
12024
|
+
...results.functionRegistry.resourceOwners,
|
|
12025
|
+
...results.tailorDB.resourceOwners,
|
|
12026
|
+
...results.staticWebsite.resourceOwners,
|
|
12027
|
+
...results.aiGateway.resourceOwners,
|
|
12028
|
+
...results.idp.resourceOwners,
|
|
12029
|
+
...results.auth.resourceOwners,
|
|
12030
|
+
...results.pipeline.resourceOwners,
|
|
12031
|
+
...results.executor.resourceOwners,
|
|
12032
|
+
...results.workflow.resourceOwners,
|
|
12033
|
+
...results.secretManager.resourceOwners
|
|
12034
|
+
]);
|
|
12035
|
+
}
|
|
12036
|
+
function collectImportantResourceDeletions(results) {
|
|
12037
|
+
const importantDeletions = [];
|
|
12038
|
+
for (const del of results.tailorDB.changeSet.type.deletes) importantDeletions.push({
|
|
12039
|
+
resourceType: "TailorDB type",
|
|
12040
|
+
resourceName: del.name
|
|
12041
|
+
});
|
|
12042
|
+
for (const del of results.staticWebsite.changeSet.deletes) importantDeletions.push({
|
|
12043
|
+
resourceType: "StaticWebsite",
|
|
12044
|
+
resourceName: del.name
|
|
12045
|
+
});
|
|
12046
|
+
for (const del of results.aiGateway.changeSet.deletes) importantDeletions.push({
|
|
12047
|
+
resourceType: "AIGateway",
|
|
12048
|
+
resourceName: del.name
|
|
12049
|
+
});
|
|
12050
|
+
for (const del of results.auth.changeSet.oauth2Client.deletes) importantDeletions.push({
|
|
12051
|
+
resourceType: "OAuth2 client",
|
|
12052
|
+
resourceName: del.name
|
|
12053
|
+
});
|
|
12054
|
+
for (const replace of results.auth.changeSet.oauth2Client.replaces) importantDeletions.push({
|
|
12055
|
+
resourceType: "OAuth2 client (client type change)",
|
|
12056
|
+
resourceName: replace.name
|
|
12057
|
+
});
|
|
12058
|
+
for (const del of results.auth.changeSet.connection.deletes) importantDeletions.push({
|
|
12059
|
+
resourceType: "Auth connection",
|
|
12060
|
+
resourceName: del.name
|
|
12061
|
+
});
|
|
12062
|
+
for (const del of results.secretManager.vaultChangeSet.deletes) importantDeletions.push({
|
|
12063
|
+
resourceType: "Secret Manager vault",
|
|
12064
|
+
resourceName: del.name
|
|
12065
|
+
});
|
|
12066
|
+
for (const del of results.secretManager.secretChangeSet.deletes) importantDeletions.push({
|
|
12067
|
+
resourceType: "Secret Manager secret",
|
|
12068
|
+
resourceName: del.name
|
|
12069
|
+
});
|
|
12070
|
+
return importantDeletions;
|
|
12071
|
+
}
|
|
12072
|
+
function readResourceField(item, field) {
|
|
12073
|
+
const itemRecord = item;
|
|
12074
|
+
for (const requestField of [
|
|
12075
|
+
"request",
|
|
12076
|
+
"deleteRequest",
|
|
12077
|
+
"createRequest"
|
|
12078
|
+
]) {
|
|
12079
|
+
const request = itemRecord[requestField];
|
|
12080
|
+
if (request && typeof request === "object" && field in request) {
|
|
12081
|
+
const value = request[field];
|
|
12082
|
+
return value == null ? void 0 : String(value);
|
|
12083
|
+
}
|
|
11565
12084
|
}
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
12085
|
+
const value = itemRecord[field];
|
|
12086
|
+
return value == null ? void 0 : String(value);
|
|
12087
|
+
}
|
|
12088
|
+
function managedResourceName(group, item) {
|
|
12089
|
+
if (group.getName) return group.getName(item);
|
|
12090
|
+
return item.name;
|
|
12091
|
+
}
|
|
12092
|
+
function managedResourceKey(group, item) {
|
|
12093
|
+
const namespace = group.namespaceFields?.map((field) => readResourceField(item, field)).find((value) => value !== void 0);
|
|
12094
|
+
const name = managedResourceName(group, item);
|
|
12095
|
+
return namespace !== void 0 ? `${group.resourceType}:${namespace}:${name}` : `${group.resourceType}:${name}`;
|
|
12096
|
+
}
|
|
12097
|
+
function managedNamespaceOwnerKey(group, item) {
|
|
12098
|
+
if (!group.namespaceOwnerResourceType) return;
|
|
12099
|
+
const namespace = group.namespaceFields?.map((field) => readResourceField(item, field)).find((value) => value !== void 0);
|
|
12100
|
+
return namespace !== void 0 ? `${group.namespaceOwnerResourceType}:${namespace}` : void 0;
|
|
12101
|
+
}
|
|
12102
|
+
function addManagedResourceClaims(claims, group, item) {
|
|
12103
|
+
claims.add(managedResourceKey(group, item));
|
|
12104
|
+
const namespaceOwnerKey = managedNamespaceOwnerKey(group, item);
|
|
12105
|
+
if (namespaceOwnerKey) claims.add(namespaceOwnerKey);
|
|
12106
|
+
}
|
|
12107
|
+
function isManagedResourceClaimed(claims, group, item) {
|
|
12108
|
+
if (claims.has(managedResourceKey(group, item))) return true;
|
|
12109
|
+
const namespaceOwnerKey = managedNamespaceOwnerKey(group, item);
|
|
12110
|
+
return namespaceOwnerKey ? claims.has(namespaceOwnerKey) : false;
|
|
12111
|
+
}
|
|
12112
|
+
function retainDeletesNotClaimed(group, otherClaims) {
|
|
12113
|
+
let writeIndex = 0;
|
|
12114
|
+
for (const item of group.changeSet.deletes) {
|
|
12115
|
+
if (isManagedResourceClaimed(otherClaims, group, item)) {
|
|
12116
|
+
logger.debug(`Skipping delete of ${managedResourceKey(group, item)}: still managed by another config in this deploy.`);
|
|
12117
|
+
continue;
|
|
12118
|
+
}
|
|
12119
|
+
group.changeSet.deletes[writeIndex] = item;
|
|
12120
|
+
writeIndex += 1;
|
|
11569
12121
|
}
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
12122
|
+
group.changeSet.deletes.length = writeIndex;
|
|
12123
|
+
}
|
|
12124
|
+
function workflowJobFunctionItems(items) {
|
|
12125
|
+
const jobNames = /* @__PURE__ */ new Set();
|
|
12126
|
+
for (const item of items) {
|
|
12127
|
+
const usedJobNames = item.usedJobNames;
|
|
12128
|
+
if (!Array.isArray(usedJobNames)) continue;
|
|
12129
|
+
for (const jobName of usedJobNames) if (typeof jobName === "string") jobNames.add(jobName);
|
|
11573
12130
|
}
|
|
11574
|
-
|
|
11575
|
-
const output = allLines.join("\n");
|
|
11576
|
-
if (opts?.dryRun) logger.out(output);
|
|
11577
|
-
else logger.log(output);
|
|
11578
|
-
return summary;
|
|
12131
|
+
return [...jobNames].map((name) => ({ name }));
|
|
11579
12132
|
}
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
12133
|
+
function workflowJobFunctionResourceGroup(results) {
|
|
12134
|
+
return {
|
|
12135
|
+
changeSet: {
|
|
12136
|
+
creates: workflowJobFunctionItems(results.workflow.changeSet.creates),
|
|
12137
|
+
updates: workflowJobFunctionItems(results.workflow.changeSet.updates),
|
|
12138
|
+
replaces: workflowJobFunctionItems(results.workflow.changeSet.replaces),
|
|
12139
|
+
unchanged: [...results.workflow.unchangedWorkflowJobNames].map((name) => ({ name })),
|
|
12140
|
+
deletes: results.workflow.jobFunctionDeletes
|
|
12141
|
+
},
|
|
12142
|
+
resourceType: "workflow_job_function",
|
|
12143
|
+
getName: (item) => "jobFunctionName" in item ? item.jobFunctionName : item.name
|
|
12144
|
+
};
|
|
12145
|
+
}
|
|
12146
|
+
const MANAGED_RESOURCE_NAMESPACE_FIELDS = [
|
|
12147
|
+
"namespaceName",
|
|
12148
|
+
"authNamespace",
|
|
12149
|
+
"vaultName",
|
|
12150
|
+
"staticWebsiteName"
|
|
12151
|
+
];
|
|
12152
|
+
const DEPLOY_MANAGED_RESOURCE_DEFINITIONS = [
|
|
12153
|
+
{
|
|
12154
|
+
resourceLabel: "Application",
|
|
12155
|
+
resourceType: "application",
|
|
12156
|
+
namesOf: (target) => [target.application.name],
|
|
12157
|
+
changeSetOf: (results) => results.app
|
|
12158
|
+
},
|
|
12159
|
+
{
|
|
12160
|
+
resourceLabel: "Executor",
|
|
12161
|
+
resourceType: "executor",
|
|
12162
|
+
namesOf: (target) => Object.values(target.application.executorService?.executors ?? {}).map((executor) => executor.name),
|
|
12163
|
+
changeSetOf: (results) => results.executor.changeSet
|
|
12164
|
+
},
|
|
12165
|
+
{
|
|
12166
|
+
resourceLabel: "Workflow job",
|
|
12167
|
+
resourceType: "workflow_job_function",
|
|
12168
|
+
namesOf: (target) => target.bundledScripts.workflowJobs.keys(),
|
|
12169
|
+
changeSetOf: (results) => workflowJobFunctionResourceGroup(results).changeSet,
|
|
12170
|
+
getName: (item) => "jobFunctionName" in item ? item.jobFunctionName : item.name
|
|
12171
|
+
},
|
|
12172
|
+
{
|
|
12173
|
+
resourceLabel: "Workflow",
|
|
12174
|
+
resourceType: "workflow",
|
|
12175
|
+
namesOf: (target) => Object.values(target.application.workflowService?.workflows ?? {}).map((workflow) => workflow.name),
|
|
12176
|
+
changeSetOf: (results) => results.workflow.changeSet
|
|
12177
|
+
},
|
|
12178
|
+
{
|
|
12179
|
+
resourceLabel: "Auth connection",
|
|
12180
|
+
resourceType: "auth.connection",
|
|
12181
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12182
|
+
namespaceOwnerResourceType: "auth.service",
|
|
12183
|
+
namesOf: (target) => Object.keys(target.application.authService?.connections ?? {}),
|
|
12184
|
+
changeSetOf: (results) => results.auth.changeSet.connection
|
|
12185
|
+
},
|
|
12186
|
+
{
|
|
12187
|
+
resourceLabel: "StaticWebsite",
|
|
12188
|
+
resourceType: "staticwebsite",
|
|
12189
|
+
namesOf: (target) => target.application.staticWebsiteServices.map((service) => service.name),
|
|
12190
|
+
changeSetOf: (results) => results.staticWebsite.changeSet
|
|
12191
|
+
},
|
|
12192
|
+
{
|
|
12193
|
+
resourceLabel: "TailorDB namespace",
|
|
12194
|
+
resourceType: "tailordb.service",
|
|
12195
|
+
namesOf: (target) => target.application.tailorDBServices.map((service) => service.namespace),
|
|
12196
|
+
changeSetOf: (results) => results.tailorDB.changeSet.service
|
|
12197
|
+
},
|
|
12198
|
+
{
|
|
12199
|
+
resourceLabel: "Auth namespace",
|
|
12200
|
+
resourceType: "auth.service",
|
|
12201
|
+
namesOf: (target) => {
|
|
12202
|
+
const name = target.application.authService?.config.name;
|
|
12203
|
+
return name === void 0 ? [] : [name];
|
|
12204
|
+
},
|
|
12205
|
+
changeSetOf: (results) => results.auth.changeSet.service
|
|
12206
|
+
},
|
|
12207
|
+
{
|
|
12208
|
+
resourceLabel: "IdP namespace",
|
|
12209
|
+
resourceType: "idp.service",
|
|
12210
|
+
namesOf: (target) => target.application.idpServices.map((idp) => idp.name),
|
|
12211
|
+
changeSetOf: (results) => results.idp.changeSet.service
|
|
12212
|
+
},
|
|
12213
|
+
{
|
|
12214
|
+
resourceLabel: "Resolver namespace",
|
|
12215
|
+
resourceType: "pipeline.service",
|
|
12216
|
+
namesOf: (target) => target.application.resolverServices.map((service) => service.namespace),
|
|
12217
|
+
changeSetOf: (results) => results.pipeline.changeSet.service
|
|
12218
|
+
},
|
|
12219
|
+
{
|
|
12220
|
+
resourceLabel: "AIGateway",
|
|
12221
|
+
resourceType: "aigateway",
|
|
12222
|
+
namesOf: (target) => target.application.aiGatewayServices.map((service) => service.name),
|
|
12223
|
+
changeSetOf: (results) => results.aiGateway.changeSet
|
|
12224
|
+
},
|
|
12225
|
+
{
|
|
12226
|
+
resourceLabel: "Secret Manager vault",
|
|
12227
|
+
resourceType: "secret.vault",
|
|
12228
|
+
namesOf: (target) => target.application.secrets.map((vault) => vault.vaultName),
|
|
12229
|
+
changeSetOf: (results) => results.secretManager.vaultChangeSet
|
|
12230
|
+
}
|
|
12231
|
+
];
|
|
12232
|
+
function managedResourceGroupFromDefinition(definition, results) {
|
|
12233
|
+
const { changeSetOf, namesOf: _namesOf, resourceLabel: _resourceLabel, ...group } = definition;
|
|
12234
|
+
return {
|
|
12235
|
+
...group,
|
|
12236
|
+
changeSet: changeSetOf(results)
|
|
12237
|
+
};
|
|
12238
|
+
}
|
|
12239
|
+
function managedResourceGroups(results) {
|
|
12240
|
+
return [
|
|
12241
|
+
{
|
|
12242
|
+
changeSet: results.functionRegistry.changeSet,
|
|
12243
|
+
resourceType: "function_registry"
|
|
12244
|
+
},
|
|
12245
|
+
...DEPLOY_MANAGED_RESOURCE_DEFINITIONS.map((definition) => managedResourceGroupFromDefinition(definition, results)),
|
|
12246
|
+
{
|
|
12247
|
+
changeSet: results.tailorDB.changeSet.type,
|
|
12248
|
+
resourceType: "tailordb.type",
|
|
12249
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12250
|
+
namespaceOwnerResourceType: "tailordb.service"
|
|
12251
|
+
},
|
|
12252
|
+
{
|
|
12253
|
+
changeSet: results.tailorDB.changeSet.gqlPermission,
|
|
12254
|
+
resourceType: "tailordb.gql_permission",
|
|
12255
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12256
|
+
namespaceOwnerResourceType: "tailordb.service"
|
|
12257
|
+
},
|
|
12258
|
+
{
|
|
12259
|
+
changeSet: results.staticWebsite.customDomainChangeSet,
|
|
12260
|
+
resourceType: "staticwebsite.custom_domain",
|
|
12261
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12262
|
+
namespaceOwnerResourceType: "staticwebsite"
|
|
12263
|
+
},
|
|
12264
|
+
{
|
|
12265
|
+
changeSet: results.idp.changeSet.client,
|
|
12266
|
+
resourceType: "idp.client",
|
|
12267
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12268
|
+
namespaceOwnerResourceType: "idp.service"
|
|
12269
|
+
},
|
|
12270
|
+
{
|
|
12271
|
+
changeSet: results.auth.changeSet.idpConfig,
|
|
12272
|
+
resourceType: "auth.idp_config",
|
|
12273
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12274
|
+
namespaceOwnerResourceType: "auth.service"
|
|
12275
|
+
},
|
|
12276
|
+
{
|
|
12277
|
+
changeSet: results.auth.changeSet.userProfileConfig,
|
|
12278
|
+
resourceType: "auth.user_profile_config",
|
|
12279
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12280
|
+
namespaceOwnerResourceType: "auth.service"
|
|
12281
|
+
},
|
|
12282
|
+
{
|
|
12283
|
+
changeSet: results.auth.changeSet.tenantConfig,
|
|
12284
|
+
resourceType: "auth.tenant_config",
|
|
12285
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12286
|
+
namespaceOwnerResourceType: "auth.service"
|
|
12287
|
+
},
|
|
12288
|
+
{
|
|
12289
|
+
changeSet: results.auth.changeSet.machineUser,
|
|
12290
|
+
resourceType: "auth.machine_user",
|
|
12291
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12292
|
+
namespaceOwnerResourceType: "auth.service"
|
|
12293
|
+
},
|
|
12294
|
+
{
|
|
12295
|
+
changeSet: results.auth.changeSet.oauth2Client,
|
|
12296
|
+
resourceType: "auth.oauth2_client",
|
|
12297
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12298
|
+
namespaceOwnerResourceType: "auth.service"
|
|
12299
|
+
},
|
|
12300
|
+
{
|
|
12301
|
+
changeSet: results.auth.changeSet.authHook,
|
|
12302
|
+
resourceType: "auth.hook",
|
|
12303
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12304
|
+
namespaceOwnerResourceType: "auth.service"
|
|
12305
|
+
},
|
|
12306
|
+
{
|
|
12307
|
+
changeSet: results.auth.changeSet.scim,
|
|
12308
|
+
resourceType: "auth.scim",
|
|
12309
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12310
|
+
namespaceOwnerResourceType: "auth.service"
|
|
12311
|
+
},
|
|
12312
|
+
{
|
|
12313
|
+
changeSet: results.auth.changeSet.scimResource,
|
|
12314
|
+
resourceType: "auth.scim_resource",
|
|
12315
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12316
|
+
namespaceOwnerResourceType: "auth.service"
|
|
12317
|
+
},
|
|
12318
|
+
{
|
|
12319
|
+
changeSet: results.pipeline.changeSet.resolver,
|
|
12320
|
+
resourceType: "pipeline.resolver",
|
|
12321
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12322
|
+
namespaceOwnerResourceType: "pipeline.service"
|
|
12323
|
+
},
|
|
12324
|
+
{
|
|
12325
|
+
changeSet: results.secretManager.secretChangeSet,
|
|
12326
|
+
resourceType: "secret.secret",
|
|
12327
|
+
namespaceFields: MANAGED_RESOURCE_NAMESPACE_FIELDS,
|
|
12328
|
+
namespaceOwnerResourceType: "secret.vault"
|
|
12329
|
+
}
|
|
12330
|
+
];
|
|
12331
|
+
}
|
|
12332
|
+
function dropCrossDeploymentManagedDeletes(deployments) {
|
|
12333
|
+
const groupsByDeployment = deployments.map((deployment) => managedResourceGroups(deploymentPlanResults(deployment)));
|
|
12334
|
+
const claimsByDeployment = groupsByDeployment.map((groups) => groups.reduce((claims, group) => {
|
|
12335
|
+
for (const item of [
|
|
12336
|
+
...group.changeSet.creates,
|
|
12337
|
+
...group.changeSet.updates,
|
|
12338
|
+
...group.changeSet.replaces,
|
|
12339
|
+
...group.changeSet.unchanged
|
|
12340
|
+
]) addManagedResourceClaims(claims, group, item);
|
|
12341
|
+
return claims;
|
|
12342
|
+
}, /* @__PURE__ */ new Set()));
|
|
12343
|
+
groupsByDeployment.forEach((groups, deploymentIndex) => {
|
|
12344
|
+
const otherClaims = /* @__PURE__ */ new Set();
|
|
12345
|
+
claimsByDeployment.forEach((claims, claimIndex) => {
|
|
12346
|
+
if (claimIndex === deploymentIndex) return;
|
|
12347
|
+
for (const claim of claims) otherClaims.add(claim);
|
|
12348
|
+
});
|
|
12349
|
+
for (const group of groups) retainDeletesNotClaimed(group, otherClaims);
|
|
12350
|
+
});
|
|
12351
|
+
}
|
|
12352
|
+
function collectDeploymentResourceOwners(deployments) {
|
|
12353
|
+
const owners = /* @__PURE__ */ new Set();
|
|
12354
|
+
for (const deployment of deployments) for (const owner of collectResourceOwners(deploymentPlanResults(deployment))) owners.add(owner);
|
|
12355
|
+
return owners;
|
|
12356
|
+
}
|
|
12357
|
+
async function confirmDeploymentPlans(params) {
|
|
12358
|
+
const { deployments, yes } = params;
|
|
12359
|
+
const targetAppNames = new Set(deployments.map((deployment) => deployment.application.name));
|
|
12360
|
+
const resourceOwners = collectDeploymentResourceOwners(deployments);
|
|
12361
|
+
const scheduledRenamedAppDeletes = /* @__PURE__ */ new Set();
|
|
12362
|
+
const importantDeletions = [];
|
|
12363
|
+
for (const deployment of deployments) {
|
|
12364
|
+
const results = deploymentPlanResults(deployment);
|
|
12365
|
+
const conflicts = collectOwnerConflicts(results);
|
|
12366
|
+
await confirmOwnerConflict(conflicts, deployment.application.name, yes);
|
|
12367
|
+
await confirmUnmanagedResources(collectUnmanagedResources(results), deployment.application.name, yes);
|
|
12368
|
+
importantDeletions.push(...collectImportantResourceDeletions(results));
|
|
12369
|
+
const emptyApps = computeRenamedAppDeletions({
|
|
12370
|
+
conflicts,
|
|
12371
|
+
resourceOwners,
|
|
12372
|
+
protectedAppNames: targetAppNames
|
|
12373
|
+
});
|
|
12374
|
+
for (const emptyApp of emptyApps) {
|
|
12375
|
+
if (scheduledRenamedAppDeletes.has(emptyApp)) continue;
|
|
12376
|
+
scheduledRenamedAppDeletes.add(emptyApp);
|
|
12377
|
+
deployment.app.deletes.push({
|
|
12378
|
+
name: emptyApp,
|
|
12379
|
+
request: {
|
|
12380
|
+
workspaceId: deployment.tailorDB.context.workspaceId,
|
|
12381
|
+
applicationName: emptyApp
|
|
12382
|
+
}
|
|
12383
|
+
});
|
|
12384
|
+
}
|
|
12385
|
+
}
|
|
12386
|
+
await confirmImportantResourceDeletion(importantDeletions, yes);
|
|
12387
|
+
}
|
|
12388
|
+
function sumPlanSummaries(summaries) {
|
|
12389
|
+
return summaries.reduce((acc, summary) => ({
|
|
12390
|
+
create: acc.create + summary.create,
|
|
12391
|
+
update: acc.update + summary.update,
|
|
12392
|
+
delete: acc.delete + summary.delete,
|
|
12393
|
+
replace: acc.replace + summary.replace
|
|
12394
|
+
}), {
|
|
11589
12395
|
create: 0,
|
|
11590
12396
|
update: 0,
|
|
11591
12397
|
delete: 0,
|
|
11592
12398
|
replace: 0
|
|
11593
|
-
};
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
12399
|
+
});
|
|
12400
|
+
}
|
|
12401
|
+
function printDeploymentPlans(deployments, opts) {
|
|
12402
|
+
if (logger.jsonMode && opts?.dryRun) {
|
|
12403
|
+
const reports = deployments.map((deployment) => buildPlanReport(deploymentPlanResults(deployment)));
|
|
12404
|
+
const summary = sumPlanSummaries(reports.map((report) => report.summary));
|
|
12405
|
+
logger.out({
|
|
12406
|
+
summary,
|
|
12407
|
+
changes: reports.flatMap((report) => report.json.changes),
|
|
12408
|
+
warnings: reports.flatMap((report) => report.json.warnings),
|
|
12409
|
+
conflicts: reports.flatMap((report) => report.json.conflicts)
|
|
12410
|
+
});
|
|
12411
|
+
return summary;
|
|
12412
|
+
}
|
|
12413
|
+
return sumPlanSummaries(deployments.map((deployment) => printPlanResults(deploymentPlanResults(deployment), opts)));
|
|
12414
|
+
}
|
|
12415
|
+
async function validateDeploymentPlans(deployments) {
|
|
12416
|
+
for (const deployment of deployments) await validatePlan(deploymentPlanResults(deployment));
|
|
11611
12417
|
}
|
|
11612
12418
|
/**
|
|
11613
12419
|
* Deploy the configured application to the Tailor platform.
|
|
@@ -11617,21 +12423,10 @@ function summarizePlanResults(results, displayEntries, serviceActions) {
|
|
|
11617
12423
|
async function deploy(options) {
|
|
11618
12424
|
return withSpan("deploy", async (rootSpan) => {
|
|
11619
12425
|
rootSpan.setAttribute("deploy.dry_run", options?.dryRun ?? false);
|
|
11620
|
-
const
|
|
12426
|
+
const configPaths = parseDeployConfigPaths(options?.configPath);
|
|
12427
|
+
const { targets, buildOnly } = await withSpan("build", async () => {
|
|
11621
12428
|
const dryRun = options?.dryRun ?? false;
|
|
11622
12429
|
const buildOnly = options?.buildOnly ?? parseBoolean(process.env.TAILOR_PLATFORM_SDK_BUILD_ONLY) === true;
|
|
11623
|
-
const { config, plugins } = await withSpan("build.loadConfig", async () => {
|
|
11624
|
-
const foundPath = loadConfigPath(options?.configPath);
|
|
11625
|
-
if (foundPath) {
|
|
11626
|
-
const resolvedPath = path.resolve(process.cwd(), foundPath);
|
|
11627
|
-
if (fs$1.existsSync(resolvedPath)) await ensureConfigIdForDeploy({
|
|
11628
|
-
configPath: resolvedPath,
|
|
11629
|
-
dryRun,
|
|
11630
|
-
buildOnly
|
|
11631
|
-
});
|
|
11632
|
-
}
|
|
11633
|
-
return loadConfig(options?.configPath);
|
|
11634
|
-
});
|
|
11635
12430
|
const noCache = options?.noCache ?? false;
|
|
11636
12431
|
const packageJson = await readPackageJson();
|
|
11637
12432
|
const cacheDir = path.resolve(getDistDir(), "cache");
|
|
@@ -11642,256 +12437,51 @@ async function deploy(options) {
|
|
|
11642
12437
|
});
|
|
11643
12438
|
logger.info("Bundle cache cleaned");
|
|
11644
12439
|
}
|
|
11645
|
-
const configDir = path.dirname(config.path);
|
|
11646
|
-
const lockfilePath = findUpSync("pnpm-lock.yaml", { cwd: configDir }) ?? findUpSync("package-lock.json", { cwd: configDir }) ?? findUpSync("yarn.lock", { cwd: configDir }) ?? findUpSync("bun.lock", { cwd: configDir });
|
|
11647
|
-
const cacheManager = createCacheManager({
|
|
11648
|
-
enabled: !noCache,
|
|
11649
|
-
cacheDir,
|
|
11650
|
-
sdkVersion: packageJson.version ?? "unknown",
|
|
11651
|
-
lockfileHash: lockfilePath ? hashFile(lockfilePath) : void 0
|
|
11652
|
-
});
|
|
11653
|
-
let pluginManager;
|
|
11654
|
-
if (plugins.length > 0) pluginManager = new PluginManager(plugins);
|
|
11655
|
-
await withSpan("build.generateUserTypes", () => generateUserTypes({
|
|
11656
|
-
config,
|
|
11657
|
-
configPath: config.path
|
|
11658
|
-
}));
|
|
11659
|
-
let application;
|
|
11660
|
-
let workflowBuildResult;
|
|
11661
|
-
let httpAdapterBuildResult;
|
|
11662
|
-
let bundledScripts;
|
|
11663
|
-
try {
|
|
11664
|
-
const result = await withSpan("build.loadApplication", () => loadApplication({
|
|
11665
|
-
config,
|
|
11666
|
-
pluginManager,
|
|
11667
|
-
bundleCache: cacheManager.bundleCache
|
|
11668
|
-
}));
|
|
11669
|
-
application = result.application;
|
|
11670
|
-
workflowBuildResult = result.workflowBuildResult;
|
|
11671
|
-
httpAdapterBuildResult = result.httpAdapterBuildResult;
|
|
11672
|
-
bundledScripts = result.bundledScripts;
|
|
11673
|
-
} finally {
|
|
11674
|
-
cacheManager.finalize();
|
|
11675
|
-
}
|
|
11676
12440
|
return {
|
|
11677
|
-
|
|
11678
|
-
|
|
11679
|
-
|
|
11680
|
-
|
|
11681
|
-
|
|
11682
|
-
|
|
11683
|
-
|
|
12441
|
+
targets: await buildDeploymentTargets({
|
|
12442
|
+
configPaths,
|
|
12443
|
+
dryRun,
|
|
12444
|
+
buildOnly,
|
|
12445
|
+
noCache,
|
|
12446
|
+
packageVersion: packageJson.version ?? "unknown",
|
|
12447
|
+
cacheDir
|
|
12448
|
+
}),
|
|
11684
12449
|
buildOnly
|
|
11685
12450
|
};
|
|
11686
12451
|
});
|
|
11687
|
-
if (buildOnly) return { bundledScripts };
|
|
12452
|
+
if (buildOnly) return { bundledScripts: mergeBundledScripts(targets) };
|
|
12453
|
+
assertUniqueGlobalResourceNames(targets);
|
|
11688
12454
|
const client = await initOperatorClient(await loadAccessToken({ profile: options?.profile }));
|
|
11689
12455
|
const workspaceId = await loadWorkspaceId({
|
|
11690
12456
|
workspaceId: options?.workspaceId,
|
|
11691
12457
|
profile: options?.profile
|
|
11692
12458
|
});
|
|
11693
|
-
rootSpan.setAttribute("app.name", application.name);
|
|
12459
|
+
rootSpan.setAttribute("app.name", targets.map((target) => target.application.name).join(","));
|
|
11694
12460
|
rootSpan.setAttribute("workspace.id", workspaceId);
|
|
11695
|
-
|
|
12461
|
+
const deployments = await planDeploymentTargets({
|
|
12462
|
+
targets,
|
|
12463
|
+
runInputs: collectDeployRunPlanInputs(targets),
|
|
11696
12464
|
client,
|
|
11697
12465
|
workspaceId,
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
}));
|
|
11701
|
-
const workflowService = application.workflowService;
|
|
11702
|
-
const functionEntries = collectFunctionEntries(application, filterBundledWorkflowJobs(workflowService?.jobs ?? [], workflowBuildResult?.usedJobNames ?? []), bundledScripts);
|
|
12466
|
+
noSchemaCheck: options?.noSchemaCheck
|
|
12467
|
+
});
|
|
11703
12468
|
const dryRun = options?.dryRun ?? false;
|
|
11704
12469
|
const yes = options?.yes ?? false;
|
|
11705
|
-
|
|
11706
|
-
const { functionRegistry, tailorDB, staticWebsite, aiGateway, idp, auth, pipeline, app, executor, workflow, secretManager } = await withSpan("plan", async () => {
|
|
11707
|
-
const idpUserTriggerTargets = collectIdpUserTriggerTargets(application);
|
|
11708
|
-
const ctx = {
|
|
11709
|
-
client,
|
|
11710
|
-
workspaceId,
|
|
11711
|
-
application,
|
|
11712
|
-
forRemoval: false,
|
|
11713
|
-
config,
|
|
11714
|
-
noSchemaCheck: options?.noSchemaCheck,
|
|
11715
|
-
forceApplyAll,
|
|
11716
|
-
idpUserTriggerTargets
|
|
11717
|
-
};
|
|
11718
|
-
const functionRegistry = await withSpan("plan.functionRegistry", () => planFunctionRegistry(client, workspaceId, application.name, application.id, functionEntries));
|
|
11719
|
-
const unchangedWorkflowJobs = new Set(functionRegistry.changeSet.unchanged.filter((entry) => entry.name.startsWith(WORKFLOW_PREFIX)).map((entry) => entry.name.slice(WORKFLOW_PREFIX.length)));
|
|
11720
|
-
const [tailorDB, staticWebsite, aiGateway, idp, auth, pipeline, app, executor, workflow, secretManager] = await Promise.all([
|
|
11721
|
-
withSpan("plan.tailorDB", () => planTailorDB(ctx)),
|
|
11722
|
-
withSpan("plan.staticWebsite", () => planStaticWebsite(ctx)),
|
|
11723
|
-
withSpan("plan.aiGateway", () => planAIGateway(ctx)),
|
|
11724
|
-
withSpan("plan.idp", () => planIdP(ctx)),
|
|
11725
|
-
withSpan("plan.auth", () => planAuth(ctx)),
|
|
11726
|
-
withSpan("plan.pipeline", () => planPipeline(ctx)),
|
|
11727
|
-
withSpan("plan.application", () => planApplication(ctx, httpAdapterBuildResult)),
|
|
11728
|
-
withSpan("plan.executor", () => planExecutor(ctx)),
|
|
11729
|
-
withSpan("plan.workflow", () => planWorkflow(client, workspaceId, application.name, application.id, workflowService?.workflows ?? {}, workflowBuildResult?.mainJobDeps ?? {}, unchangedWorkflowJobs)),
|
|
11730
|
-
withSpan("plan.secretManager", () => planSecretManager(ctx))
|
|
11731
|
-
]);
|
|
11732
|
-
return {
|
|
11733
|
-
functionRegistry,
|
|
11734
|
-
tailorDB,
|
|
11735
|
-
staticWebsite,
|
|
11736
|
-
aiGateway,
|
|
11737
|
-
idp,
|
|
11738
|
-
auth,
|
|
11739
|
-
pipeline,
|
|
11740
|
-
app,
|
|
11741
|
-
executor,
|
|
11742
|
-
workflow,
|
|
11743
|
-
secretManager
|
|
11744
|
-
};
|
|
11745
|
-
});
|
|
12470
|
+
dropCrossDeploymentManagedDeletes(deployments);
|
|
11746
12471
|
await withSpan("confirm", async () => {
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
...staticWebsite.conflicts,
|
|
11751
|
-
...aiGateway.conflicts,
|
|
11752
|
-
...idp.conflicts,
|
|
11753
|
-
...auth.conflicts,
|
|
11754
|
-
...pipeline.conflicts,
|
|
11755
|
-
...executor.conflicts,
|
|
11756
|
-
...workflow.conflicts,
|
|
11757
|
-
...secretManager.conflicts
|
|
11758
|
-
];
|
|
11759
|
-
await confirmOwnerConflict(allConflicts, application.name, yes);
|
|
11760
|
-
await confirmUnmanagedResources([
|
|
11761
|
-
...functionRegistry.unmanaged,
|
|
11762
|
-
...tailorDB.unmanaged,
|
|
11763
|
-
...staticWebsite.unmanaged,
|
|
11764
|
-
...aiGateway.unmanaged,
|
|
11765
|
-
...idp.unmanaged,
|
|
11766
|
-
...auth.unmanaged,
|
|
11767
|
-
...pipeline.unmanaged,
|
|
11768
|
-
...executor.unmanaged,
|
|
11769
|
-
...workflow.unmanaged,
|
|
11770
|
-
...secretManager.unmanaged
|
|
11771
|
-
], application.name, yes);
|
|
11772
|
-
const importantDeletions = [];
|
|
11773
|
-
for (const del of tailorDB.changeSet.type.deletes) importantDeletions.push({
|
|
11774
|
-
resourceType: "TailorDB type",
|
|
11775
|
-
resourceName: del.name
|
|
11776
|
-
});
|
|
11777
|
-
for (const del of staticWebsite.changeSet.deletes) importantDeletions.push({
|
|
11778
|
-
resourceType: "StaticWebsite",
|
|
11779
|
-
resourceName: del.name
|
|
11780
|
-
});
|
|
11781
|
-
for (const del of aiGateway.changeSet.deletes) importantDeletions.push({
|
|
11782
|
-
resourceType: "AIGateway",
|
|
11783
|
-
resourceName: del.name
|
|
11784
|
-
});
|
|
11785
|
-
for (const del of auth.changeSet.oauth2Client.deletes) importantDeletions.push({
|
|
11786
|
-
resourceType: "OAuth2 client",
|
|
11787
|
-
resourceName: del.name
|
|
11788
|
-
});
|
|
11789
|
-
for (const replace of auth.changeSet.oauth2Client.replaces) importantDeletions.push({
|
|
11790
|
-
resourceType: "OAuth2 client (client type change)",
|
|
11791
|
-
resourceName: replace.name
|
|
11792
|
-
});
|
|
11793
|
-
for (const del of auth.changeSet.connection.deletes) importantDeletions.push({
|
|
11794
|
-
resourceType: "Auth connection",
|
|
11795
|
-
resourceName: del.name
|
|
11796
|
-
});
|
|
11797
|
-
for (const del of secretManager.vaultChangeSet.deletes) importantDeletions.push({
|
|
11798
|
-
resourceType: "Secret Manager vault",
|
|
11799
|
-
resourceName: del.name
|
|
11800
|
-
});
|
|
11801
|
-
for (const del of secretManager.secretChangeSet.deletes) importantDeletions.push({
|
|
11802
|
-
resourceType: "Secret Manager secret",
|
|
11803
|
-
resourceName: del.name
|
|
11804
|
-
});
|
|
11805
|
-
await confirmImportantResourceDeletion(importantDeletions, yes);
|
|
11806
|
-
const emptyApps = computeRenamedAppDeletions({
|
|
11807
|
-
conflicts: allConflicts,
|
|
11808
|
-
resourceOwners: /* @__PURE__ */ new Set([
|
|
11809
|
-
...functionRegistry.resourceOwners,
|
|
11810
|
-
...tailorDB.resourceOwners,
|
|
11811
|
-
...staticWebsite.resourceOwners,
|
|
11812
|
-
...aiGateway.resourceOwners,
|
|
11813
|
-
...idp.resourceOwners,
|
|
11814
|
-
...auth.resourceOwners,
|
|
11815
|
-
...pipeline.resourceOwners,
|
|
11816
|
-
...executor.resourceOwners,
|
|
11817
|
-
...workflow.resourceOwners,
|
|
11818
|
-
...secretManager.resourceOwners
|
|
11819
|
-
]),
|
|
11820
|
-
targetAppName: application.name
|
|
11821
|
-
});
|
|
11822
|
-
for (const emptyApp of emptyApps) app.deletes.push({
|
|
11823
|
-
name: emptyApp,
|
|
11824
|
-
request: {
|
|
11825
|
-
workspaceId,
|
|
11826
|
-
applicationName: emptyApp
|
|
11827
|
-
}
|
|
12472
|
+
await confirmDeploymentPlans({
|
|
12473
|
+
deployments,
|
|
12474
|
+
yes
|
|
11828
12475
|
});
|
|
11829
12476
|
});
|
|
11830
|
-
const planSummary =
|
|
11831
|
-
functionRegistry,
|
|
11832
|
-
tailorDB,
|
|
11833
|
-
staticWebsite,
|
|
11834
|
-
aiGateway,
|
|
11835
|
-
idp,
|
|
11836
|
-
auth,
|
|
11837
|
-
pipeline,
|
|
11838
|
-
app,
|
|
11839
|
-
executor,
|
|
11840
|
-
workflow,
|
|
11841
|
-
secretManager
|
|
11842
|
-
}, { dryRun: options?.dryRun });
|
|
12477
|
+
const planSummary = printDeploymentPlans(deployments, { dryRun: options?.dryRun });
|
|
11843
12478
|
if (options?.noValidate) logger.warn("Client-side validation skipped (--no-validate).");
|
|
11844
|
-
else await
|
|
11845
|
-
functionRegistry,
|
|
11846
|
-
tailorDB,
|
|
11847
|
-
staticWebsite,
|
|
11848
|
-
idp,
|
|
11849
|
-
auth,
|
|
11850
|
-
pipeline,
|
|
11851
|
-
app,
|
|
11852
|
-
executor,
|
|
11853
|
-
workflow,
|
|
11854
|
-
secretManager
|
|
11855
|
-
});
|
|
12479
|
+
else await validateDeploymentPlans(deployments);
|
|
11856
12480
|
if (dryRun) {
|
|
11857
12481
|
logger.info("Dry run enabled. No changes applied.");
|
|
11858
12482
|
return;
|
|
11859
12483
|
}
|
|
11860
|
-
await
|
|
11861
|
-
await applySecretManager(client, secretManager, "create-update", application);
|
|
11862
|
-
await applyFunctionRegistry(client, workspaceId, functionRegistry, "create-update");
|
|
11863
|
-
await applyStaticWebsite(client, staticWebsite, "create-update");
|
|
11864
|
-
await applyAIGateway(client, aiGateway, "create-update");
|
|
11865
|
-
await applyIdP(client, idp, "create-update");
|
|
11866
|
-
await applyAuth(client, auth, "create-update");
|
|
11867
|
-
await applyTailorDB(client, tailorDB, "create-update");
|
|
11868
|
-
await applyPipeline(client, pipeline, "create-update");
|
|
11869
|
-
});
|
|
11870
|
-
await withSpan("apply.deleteSubgraphResources", async () => {
|
|
11871
|
-
await applyPipeline(client, pipeline, "delete-resources");
|
|
11872
|
-
await applyAuth(client, auth, "delete-resources");
|
|
11873
|
-
await applyIdP(client, idp, "delete-resources");
|
|
11874
|
-
});
|
|
11875
|
-
await withSpan("apply.createUpdateApplication", () => applyApplication(client, app, "create-update"));
|
|
11876
|
-
await withSpan("apply.createUpdateDependentServices", async () => {
|
|
11877
|
-
await applyExecutor(client, executor, "create-update");
|
|
11878
|
-
await applyWorkflow(client, workflow, "create-update");
|
|
11879
|
-
});
|
|
11880
|
-
await withSpan("apply.deleteDependentServices", async () => {
|
|
11881
|
-
await applyWorkflow(client, workflow, "delete");
|
|
11882
|
-
await applyExecutor(client, executor, "delete");
|
|
11883
|
-
await applyStaticWebsite(client, staticWebsite, "delete");
|
|
11884
|
-
await applyAIGateway(client, aiGateway, "delete");
|
|
11885
|
-
await applySecretManager(client, secretManager, "delete");
|
|
11886
|
-
});
|
|
11887
|
-
await withSpan("apply.deleteApplication", () => applyApplication(client, app, "delete"));
|
|
11888
|
-
await withSpan("apply.deleteSubgraphServices", async () => {
|
|
11889
|
-
await applyPipeline(client, pipeline, "delete-services");
|
|
11890
|
-
await applyAuth(client, auth, "delete-services");
|
|
11891
|
-
await applyIdP(client, idp, "delete-services");
|
|
11892
|
-
await applyTailorDB(client, tailorDB, "delete-services");
|
|
11893
|
-
});
|
|
11894
|
-
await withSpan("apply.cleanup", () => applyFunctionRegistry(client, workspaceId, functionRegistry, "delete"));
|
|
12484
|
+
await applyDeploymentPlans(client, workspaceId, deployments);
|
|
11895
12485
|
if (logger.jsonMode) logger.out({
|
|
11896
12486
|
summary: planSummary,
|
|
11897
12487
|
status: "applied"
|
|
@@ -16031,10 +16621,12 @@ function createWorkspaceFolderNameResolver(client) {
|
|
|
16031
16621
|
};
|
|
16032
16622
|
}
|
|
16033
16623
|
async function workspaceInfoWithFolderName(client, workspace) {
|
|
16034
|
-
|
|
16624
|
+
const folderName = await resolveWorkspaceFolderName(client, workspace);
|
|
16625
|
+
return workspaceInfo(workspace, folderName);
|
|
16035
16626
|
}
|
|
16036
16627
|
async function workspaceDetailsWithFolderName(client, workspace) {
|
|
16037
|
-
|
|
16628
|
+
const folderName = await resolveWorkspaceFolderName(client, workspace);
|
|
16629
|
+
return workspaceDetails(workspace, folderName);
|
|
16038
16630
|
}
|
|
16039
16631
|
async function workspaceInfosWithFolderNames(client, workspaces) {
|
|
16040
16632
|
const resolveFolderName = createWorkspaceFolderNameResolver(client);
|
|
@@ -16705,7 +17297,7 @@ async function generate(options) {
|
|
|
16705
17297
|
if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
|
|
16706
17298
|
let pluginManager;
|
|
16707
17299
|
if (plugins.length > 0) pluginManager = new PluginManager(plugins);
|
|
16708
|
-
const { defineApplication } = await import("./application-
|
|
17300
|
+
const { defineApplication } = await import("./application-CSUhZMb5.mjs");
|
|
16709
17301
|
const application = defineApplication({
|
|
16710
17302
|
config,
|
|
16711
17303
|
pluginManager
|
|
@@ -18635,7 +19227,7 @@ async function runRepl(options) {
|
|
|
18635
19227
|
const execute = await prepareQueryExecutor(options);
|
|
18636
19228
|
const historyPath = getReplHistoryPath(options.engine, options.profile, options.workspaceId);
|
|
18637
19229
|
const validate = createReplValidator(options.engine);
|
|
18638
|
-
const { highlightSqlLine, highlightGraphqlLine, replTransform } = await import("./repl-editor-
|
|
19230
|
+
const { highlightSqlLine, highlightGraphqlLine, replTransform } = await import("./repl-editor-BCozyiNq.mjs");
|
|
18639
19231
|
const highlight = options.engine === "sql" ? highlightSqlLine : highlightGraphqlLine;
|
|
18640
19232
|
const prompt = createPrompt({
|
|
18641
19233
|
prefix: "",
|
|
@@ -18969,5 +19561,5 @@ function isDeno() {
|
|
|
18969
19561
|
}
|
|
18970
19562
|
|
|
18971
19563
|
//#endregion
|
|
18972
|
-
export { getOrganization as $,
|
|
18973
|
-
//# sourceMappingURL=runtime-
|
|
19564
|
+
export { getOrganization as $, compareSnapshotWithRemote as $t, listCommand$3 as A, apiCommand as An, getExecutorWaitFailureMessage as At, show as B, pagedLogArgs as Bn, listWorkflowExecutions as Bt, listCommand$2 as C, hasChanges as Cn, listWebhookExecutors as Ct, waitWorkflowExecution as D, PluginManager as Dn, listCommand$9 as Dt, waitCommand as E, sdkNameLabelKey as En, triggerExecutor as Et, generateCommand as F, configArg as Fn, startWorkflow as Ft, removeCommand$1 as G, deploy as Gt, extractOwnedNamespaces as H, toPageDirection as Hn, formatKeyValueTable as Ht, generateMigrationScript as I, confirmationArgs as In, getCommand$5 as It, organizationTree as J, waitForExecution as Jt, updateCommand$1 as K, ensureConfigId as Kt, writeDbTypesFile as L, deploymentArgs as Ln, getWorkflow as Lt, truncate as M, assertWritable as Mn, listExecutorJobs as Mt, truncateCommand as N, defineAppCommand as Nn, watchExecutorJob as Nt, resumeCommand as O, generateUserTypes as On, listExecutors as Ot, generate as P, commonArgs as Pn, startCommand as Pt, getCommand$1 as Q, parseMigrationLabelNumber as Qt, getConfiguredEditorCommand as R, isVerbose as Rn, executionsCommand as Rt, listApps as S, formatMigrationDiff as Sn, getFunctionRegistry as St, healthCommand as T, resourceTrn as Tn, triggerCommand as Tt, logBetaWarning as U, workspaceArgs as Un, getCommand$6 as Ut, showCommand as V, paginationArgs as Vn, functionExecutionStatusToString as Vt, remove as W, getExecutor as Wt, listCommand$4 as X, MIGRATION_LABEL_KEY as Xt, treeCommand as Y, bundleMigrationScript as Yt, listOrganizations as Z, handleOptionalToRequiredError as Zt, getWorkspace as _, loadDiff as _n, listMachineUsers as _t, updateUser as a, MIGRATE_FILE_NAME as an, getFolder as at, createCommand as b, parseMigrationNumberArg as bn, listFunctionRegistries as bt, listCommand as c, compareLocalTypesWithSnapshot as cn, createCommand$1 as ct, inviteUser as d, getLatestMigrationNumber as dn, listOAuth2Clients as dt, generateAllTypeManifestsFromSnapshot as en, updateCommand$2 as et, restoreCommand as f, getMigrationDirPath as fn, getCommand$3 as ft, getCommand as g, isValidMigrationNumber as gn, listCommand$7 as gt, listWorkspaces as h, getNextMigrationNumber as hn, tokenCommand as ht, updateCommand as i, INITIAL_SCHEMA_NUMBER as in, getCommand$2 as it, listWorkflows as j, apiCall as jn, jobsCommand as jt, resumeWorkflow as k, prompt as kn, getExecutorJob as kt, listUsers as l, compareSnapshots as ln, createFolder as lt, listCommand$1 as m, getMigrationFiles as mn, getMachineUserToken as mt, query as n, DB_TYPES_FILE_NAME as nn, listCommand$5 as nt, removeCommand as o, SCHEMA_FILE_NAME as on, deleteCommand$1 as ot, restoreWorkspace as p, getMigrationFilePath as pn, getOAuth2Client as pt, updateOrganization as q, executeScript as qt, queryCommand as r, DIFF_FILE_NAME as rn, listFolders as rt, removeUser as s, assertValidMigrationFiles as sn, deleteFolder as st, isNativeTypeScriptRuntime as t, protoGqlPermission as tn, updateFolder as tt, inviteCommand as u, createSnapshotFromLocalTypes as un, listCommand$6 as ut, deleteCommand as v, reconstructSnapshotFromMigrations as vn, generate$1 as vt, getAppHealth as w, getNamespacesWithMigrations as wn, webhookCommand as wt, createWorkspace as x, formatDiffSummary as xn, getCommand$4 as xt, deleteWorkspace as y, formatMigrationNumber as yn, listCommand$8 as yt, openInConfiguredEditor as z, multiConfigArg as zn, getWorkflowExecution as zt };
|
|
19565
|
+
//# sourceMappingURL=runtime-DV1EnfMs.mjs.map
|