44eca4d166
Adds an opt-in way for operators to route deployed runs' API traffic through a different origin than the public one, per organization. Set `INTERNAL_API_ORIGIN` on the webapp and enable the `internalApiOriginEnabled` feature flag (globally or per org, with the org override winning in both directions): deployed runs for enabled orgs then get `TRIGGER_API_URL` set to the internal origin instead of `API_ORIGIN`. Useful for gradually moving run traffic onto a private network path. ## Design The origin is resolved when an attempt starts, so flag changes take effect on the next attempt and roll back the same way, with no task redeploys. The org override is read fresh per attempt; the global default comes from the cached flags registry (a cold read fails safe to the public origin). When `INTERNAL_API_ORIGIN` is unset the flag is a no-op and no extra queries run, so existing deployments are unaffected. Dev runs always use the public origin, and `TRIGGER_STREAM_URL` remains unchanged.
1443 lines
42 KiB
TypeScript
1443 lines
42 KiB
TypeScript
import type { AuthenticatedEnvironment } from "@trigger.dev/core/v3/auth/environment";
|
|
import { Prisma, type PrismaClient, type RuntimeEnvironmentType } from "@trigger.dev/database";
|
|
import { z } from "zod";
|
|
import { environmentFullTitle } from "~/components/environments/EnvironmentLabel";
|
|
import { $replica, $transaction, prisma, type PrismaReplicaClient } from "~/db.server";
|
|
import { env } from "~/env.server";
|
|
import { getSecretStore } from "~/services/secrets/secretStore.server";
|
|
import { deduplicateVariableArray } from "../deduplicateVariableArray.server";
|
|
import { removeBlacklistedVariables } from "../environmentVariableRules.server";
|
|
import { FEATURE_FLAG, resolveInternalApiOriginEnabled } from "../featureFlags";
|
|
import { globalFlagsRegistry } from "../globalFlagsRegistry.server";
|
|
import { generateFriendlyId } from "../friendlyIdentifiers";
|
|
import {
|
|
type CreateEnvironmentVariables,
|
|
type CreateResult,
|
|
type DeleteEnvironmentVariable,
|
|
type DeleteEnvironmentVariableValue,
|
|
type EditEnvironmentVariable,
|
|
type EditEnvironmentVariableValue,
|
|
type EnvironmentVariable,
|
|
type EnvironmentVariableWithSecret,
|
|
type ProjectEnvironmentVariable,
|
|
type Repository,
|
|
type Result,
|
|
} from "./repository";
|
|
|
|
function secretKeyProjectPrefix(projectId: string) {
|
|
return `environmentvariable:${projectId}:`;
|
|
}
|
|
|
|
function secretKeyEnvironmentPrefix(projectId: string, environmentId: string) {
|
|
return `${secretKeyProjectPrefix(projectId)}${environmentId}:`;
|
|
}
|
|
|
|
function secretKey(projectId: string, environmentId: string, key: string) {
|
|
return `${secretKeyEnvironmentPrefix(projectId, environmentId)}${key}`;
|
|
}
|
|
|
|
function parseSecretKey(key: string) {
|
|
const parts = key.split(":");
|
|
return {
|
|
projectId: parts[1],
|
|
environmentId: parts[2],
|
|
key: parts[3],
|
|
};
|
|
}
|
|
|
|
const SecretValue = z.object({ secret: z.string() });
|
|
|
|
export class EnvironmentVariablesRepository implements Repository {
|
|
constructor(
|
|
private prismaClient: PrismaClient = prisma,
|
|
private replicaClient: PrismaReplicaClient = $replica
|
|
) {}
|
|
|
|
async create(projectId: string, options: CreateEnvironmentVariables): Promise<CreateResult> {
|
|
const project = await this.prismaClient.project.findFirst({
|
|
where: {
|
|
id: projectId,
|
|
deletedAt: null,
|
|
},
|
|
select: {
|
|
environments: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
environmentVariables: {
|
|
select: {
|
|
key: true,
|
|
values: {
|
|
select: {
|
|
environment: {
|
|
select: { id: true, type: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!project) {
|
|
return { success: false as const, error: "Project not found" };
|
|
}
|
|
|
|
// Reject if ANY supplied environmentId is outside the caller's project.
|
|
// `.some` (not `.every`) so one in-project id can't let a mixed array
|
|
// through.
|
|
if (options.environmentIds.some((v) => !project.environments.some((e) => e.id === v))) {
|
|
return { success: false as const, error: `Environment not found` };
|
|
}
|
|
|
|
// Remove `TRIGGER_SECRET_KEY` or `TRIGGER_API_URL`
|
|
let values = removeBlacklistedVariables(options.variables);
|
|
const removedBlacklisted = values.length !== options.variables.length;
|
|
|
|
//get rid of empty variables
|
|
values = values.filter((v) => v.key.trim() !== "" && v.value.trim() !== "");
|
|
if (values.length === 0) {
|
|
return {
|
|
success: false as const,
|
|
error: `You must set at least one valid variable.${
|
|
removedBlacklisted ? " All the variables submitted are not allowed." : ""
|
|
}`,
|
|
};
|
|
}
|
|
|
|
//check if any of them exist in an environment we're setting
|
|
if (!options.override) {
|
|
const existingVariableKeys: { key: string; environments: RuntimeEnvironmentType[] }[] = [];
|
|
for (const variable of values) {
|
|
const existingVariable = project.environmentVariables.find((v) => v.key === variable.key);
|
|
if (
|
|
existingVariable &&
|
|
existingVariable.values.some((v) => options.environmentIds.includes(v.environment.id))
|
|
) {
|
|
existingVariableKeys.push({
|
|
key: variable.key,
|
|
environments: existingVariable.values
|
|
.filter((v) => options.environmentIds.includes(v.environment.id))
|
|
.map((v) => v.environment.type),
|
|
});
|
|
}
|
|
}
|
|
|
|
if (existingVariableKeys.length > 0) {
|
|
return {
|
|
success: false as const,
|
|
error: `Some of the variables are already set for these environments. Set override to true to override them.`,
|
|
variableErrors: existingVariableKeys.map((val) => ({
|
|
key: val.key,
|
|
error: `Variable already set in ${val.environments
|
|
.map((e) => environmentFullTitle({ type: e }))
|
|
.join(", ")}.`,
|
|
})),
|
|
};
|
|
}
|
|
}
|
|
|
|
try {
|
|
for (const variable of values) {
|
|
const _result = await $transaction(this.prismaClient, "create env var", async (tx) => {
|
|
const environmentVariable = await tx.environmentVariable.upsert({
|
|
where: {
|
|
projectId_key: {
|
|
key: variable.key,
|
|
projectId,
|
|
},
|
|
},
|
|
create: {
|
|
key: variable.key,
|
|
friendlyId: generateFriendlyId("envvar"),
|
|
project: {
|
|
connect: {
|
|
id: projectId,
|
|
},
|
|
},
|
|
},
|
|
update: {},
|
|
});
|
|
|
|
const secretStore = getSecretStore("DATABASE", {
|
|
prismaClient: tx,
|
|
});
|
|
|
|
// If parentEnvironmentId is provided and isSecret is not explicitly set,
|
|
// look up if the parent has this variable marked as secret
|
|
let inheritedIsSecret: boolean | undefined = undefined;
|
|
if (options.isSecret === undefined && options.parentEnvironmentId) {
|
|
const parentVariableValue = await tx.environmentVariableValue.findFirst({
|
|
where: {
|
|
variableId: environmentVariable.id,
|
|
environmentId: options.parentEnvironmentId,
|
|
},
|
|
select: {
|
|
isSecret: true,
|
|
},
|
|
});
|
|
if (parentVariableValue?.isSecret) {
|
|
inheritedIsSecret = true;
|
|
}
|
|
}
|
|
|
|
const effectiveIsSecret = options.isSecret ?? inheritedIsSecret;
|
|
|
|
//set the secret values and references
|
|
for (const environmentId of options.environmentIds) {
|
|
const key = secretKey(projectId, environmentId, variable.key);
|
|
|
|
const existingValueRecord = await tx.environmentVariableValue.findFirst({
|
|
where: {
|
|
variableId: environmentVariable.id,
|
|
environmentId,
|
|
},
|
|
});
|
|
|
|
// Check if value already exists and is the same, and no metadata change (e.g. isSecret toggle)
|
|
const existingSecret = await secretStore.getSecret(SecretValue, key);
|
|
const canSkip =
|
|
existingSecret &&
|
|
existingSecret.secret === variable.value &&
|
|
existingValueRecord &&
|
|
(options.isSecret === undefined || existingValueRecord.isSecret === options.isSecret);
|
|
if (canSkip) {
|
|
continue;
|
|
}
|
|
|
|
//create the secret reference
|
|
const secretReference = await tx.secretReference.upsert({
|
|
where: {
|
|
key,
|
|
},
|
|
create: {
|
|
key,
|
|
provider: "DATABASE",
|
|
},
|
|
update: {},
|
|
});
|
|
|
|
if (existingValueRecord) {
|
|
await tx.environmentVariableValue.update({
|
|
where: {
|
|
id: existingValueRecord.id,
|
|
},
|
|
data: {
|
|
version: {
|
|
increment: 1,
|
|
},
|
|
...(options.lastUpdatedBy ? { lastUpdatedBy: options.lastUpdatedBy } : {}),
|
|
valueReferenceId: secretReference.id,
|
|
...(options.isSecret !== undefined
|
|
? {
|
|
isSecret: options.isSecret,
|
|
}
|
|
: {}),
|
|
},
|
|
});
|
|
} else {
|
|
await tx.environmentVariableValue.create({
|
|
data: {
|
|
variableId: environmentVariable.id,
|
|
environmentId: environmentId,
|
|
valueReferenceId: secretReference.id,
|
|
isSecret: effectiveIsSecret,
|
|
version: 1,
|
|
lastUpdatedBy: options.lastUpdatedBy ? options.lastUpdatedBy : Prisma.JsonNull,
|
|
},
|
|
});
|
|
}
|
|
|
|
await secretStore.setSecret<{ secret: string }>(key, {
|
|
secret: variable.value,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
return {
|
|
success: true as const,
|
|
};
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
// The error code for unique constraint violation in Prisma is P2002
|
|
if (error.code === "P2002") {
|
|
return {
|
|
success: false as const,
|
|
error: `There was already an existing field`,
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: false as const,
|
|
error: error instanceof Error ? error.message : "Something went wrong",
|
|
};
|
|
}
|
|
}
|
|
|
|
async edit(projectId: string, options: EditEnvironmentVariable): Promise<Result> {
|
|
const project = await this.prismaClient.project.findFirst({
|
|
where: {
|
|
id: projectId,
|
|
deletedAt: null,
|
|
},
|
|
select: {
|
|
environments: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!project) {
|
|
return { success: false as const, error: "Project not found" };
|
|
}
|
|
|
|
// Same guard as `create()`: reject if ANY supplied environmentId is
|
|
// outside the caller's project (`.some`, not `.every`).
|
|
if (options.values.some((v) => !project.environments.some((e) => e.id === v.environmentId))) {
|
|
return { success: false as const, error: `Environment not found` };
|
|
}
|
|
|
|
//get rid of empty strings
|
|
let values = options.values.filter((v) => v.value.trim() !== "");
|
|
|
|
//add in empty values for environments that don't have a value
|
|
const environmentIds = project.environments.map((e) => e.id);
|
|
|
|
if (!options.keepEmptyValues) {
|
|
for (const environmentId of environmentIds) {
|
|
if (!values.some((v) => v.environmentId === environmentId)) {
|
|
values.push({
|
|
environmentId,
|
|
value: "",
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
const environmentVariable = await this.prismaClient.environmentVariable.findFirst({
|
|
select: {
|
|
id: true,
|
|
key: true,
|
|
},
|
|
where: {
|
|
id: options.id,
|
|
},
|
|
});
|
|
if (!environmentVariable) {
|
|
return { success: false as const, error: "Environment variable not found" };
|
|
}
|
|
|
|
try {
|
|
await $transaction(this.prismaClient, "edit env var", async (tx) => {
|
|
const secretStore = getSecretStore("DATABASE", {
|
|
prismaClient: tx,
|
|
});
|
|
|
|
//create the secret values and references
|
|
for (const value of values) {
|
|
const key = secretKey(projectId, value.environmentId, environmentVariable.key);
|
|
const existingValue = await tx.environmentVariableValue.findFirst({
|
|
where: {
|
|
variableId: environmentVariable.id,
|
|
environmentId: value.environmentId,
|
|
},
|
|
});
|
|
|
|
if (existingValue && existingValue.valueReferenceId) {
|
|
if (value.value === "") {
|
|
//delete the value
|
|
await secretStore.deleteSecret(key);
|
|
await tx.secretReference.delete({
|
|
where: {
|
|
id: existingValue.valueReferenceId,
|
|
},
|
|
});
|
|
await tx.environmentVariableValue.delete({
|
|
where: {
|
|
variableId_environmentId: {
|
|
variableId: environmentVariable.id,
|
|
environmentId: value.environmentId,
|
|
},
|
|
},
|
|
});
|
|
} else {
|
|
await secretStore.setSecret<{ secret: string }>(key, {
|
|
secret: value.value,
|
|
});
|
|
await tx.environmentVariableValue.update({
|
|
where: {
|
|
variableId_environmentId: {
|
|
variableId: environmentVariable.id,
|
|
environmentId: value.environmentId,
|
|
},
|
|
},
|
|
data: {
|
|
version: {
|
|
increment: 1,
|
|
},
|
|
lastUpdatedBy: options.lastUpdatedBy ? options.lastUpdatedBy : undefined,
|
|
},
|
|
});
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//create the secret reference
|
|
const secretReference = await tx.secretReference.create({
|
|
data: {
|
|
key,
|
|
provider: "DATABASE",
|
|
},
|
|
});
|
|
|
|
const _variableValue = await tx.environmentVariableValue.create({
|
|
data: {
|
|
variableId: environmentVariable.id,
|
|
environmentId: value.environmentId,
|
|
valueReferenceId: secretReference.id,
|
|
version: 1,
|
|
lastUpdatedBy: options.lastUpdatedBy ? options.lastUpdatedBy : Prisma.JsonNull,
|
|
},
|
|
});
|
|
|
|
await secretStore.setSecret<{ secret: string }>(key, {
|
|
secret: value.value,
|
|
});
|
|
}
|
|
});
|
|
|
|
return {
|
|
success: true as const,
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false as const,
|
|
error: error instanceof Error ? error.message : "Something went wrong",
|
|
};
|
|
}
|
|
}
|
|
|
|
async editValue(projectId: string, options: EditEnvironmentVariableValue): Promise<Result> {
|
|
const project = await this.prismaClient.project.findFirst({
|
|
where: {
|
|
id: projectId,
|
|
deletedAt: null,
|
|
},
|
|
select: {
|
|
environments: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!project) {
|
|
return { success: false as const, error: "Project not found" };
|
|
}
|
|
|
|
if (!project.environments.some((e) => e.id === options.environmentId)) {
|
|
return { success: false as const, error: "Environment not found" };
|
|
}
|
|
|
|
const environmentVariable = await this.prismaClient.environmentVariable.findFirst({
|
|
select: {
|
|
id: true,
|
|
key: true,
|
|
values: {
|
|
where: {
|
|
environmentId: options.environmentId,
|
|
},
|
|
select: {
|
|
valueReferenceId: true,
|
|
},
|
|
},
|
|
},
|
|
where: {
|
|
id: options.id,
|
|
},
|
|
});
|
|
|
|
if (!environmentVariable) {
|
|
return { success: false as const, error: "Environment variable not found" };
|
|
}
|
|
|
|
if (environmentVariable.values.length === 0) {
|
|
return { success: false as const, error: "Environment variable value not found" };
|
|
}
|
|
|
|
try {
|
|
await $transaction(this.prismaClient, "edit env var value", async (tx) => {
|
|
const secretStore = getSecretStore("DATABASE", {
|
|
prismaClient: tx,
|
|
});
|
|
|
|
const key = secretKey(projectId, options.environmentId, environmentVariable.key);
|
|
await secretStore.setSecret<{ secret: string }>(key, {
|
|
secret: options.value,
|
|
});
|
|
|
|
await tx.environmentVariableValue.update({
|
|
where: {
|
|
variableId_environmentId: {
|
|
variableId: environmentVariable.id,
|
|
environmentId: options.environmentId,
|
|
},
|
|
},
|
|
data: {
|
|
version: {
|
|
increment: 1,
|
|
},
|
|
lastUpdatedBy: options.lastUpdatedBy ? options.lastUpdatedBy : undefined,
|
|
},
|
|
});
|
|
});
|
|
|
|
return {
|
|
success: true as const,
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false as const,
|
|
error: error instanceof Error ? error.message : "Something went wrong",
|
|
};
|
|
}
|
|
}
|
|
|
|
async getProject(projectId: string): Promise<ProjectEnvironmentVariable[]> {
|
|
const project = await this.prismaClient.project.findFirst({
|
|
where: {
|
|
id: projectId,
|
|
deletedAt: null,
|
|
},
|
|
select: {
|
|
environments: {
|
|
select: {
|
|
id: true,
|
|
type: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!project) {
|
|
return [];
|
|
}
|
|
|
|
const secretStore = getSecretStore("DATABASE", {
|
|
prismaClient: this.prismaClient,
|
|
});
|
|
|
|
const secrets = await secretStore.getSecrets(SecretValue, secretKeyProjectPrefix(projectId));
|
|
|
|
const values = secrets.map((secret) => {
|
|
const { projectId, environmentId, key } = parseSecretKey(secret.key);
|
|
return {
|
|
projectId,
|
|
environmentId,
|
|
key,
|
|
value: secret.value.secret,
|
|
};
|
|
});
|
|
|
|
//now group the values together by key and environment ID into ProjectEnvironmentVariable[]
|
|
//and add the type of environment to the result
|
|
const results: ProjectEnvironmentVariable[] = [];
|
|
for (const value of values) {
|
|
const environment = project.environments.find((e) => e.id === value.environmentId);
|
|
if (!environment) {
|
|
throw new Error("Environment not found");
|
|
}
|
|
|
|
const existing = results.find((r) => r.key === value.key);
|
|
if (existing) {
|
|
existing.values.push({
|
|
value: value.value,
|
|
environment: {
|
|
id: value.environmentId,
|
|
type: environment.type,
|
|
},
|
|
});
|
|
} else {
|
|
results.push({
|
|
key: value.key,
|
|
values: [
|
|
{
|
|
value: value.value,
|
|
environment: {
|
|
id: value.environmentId,
|
|
type: environment.type,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
async getVariableValuesForKeys(
|
|
projectId: string,
|
|
items: Array<{ environmentId: string; key: string }>
|
|
): Promise<Map<string, string>> {
|
|
if (items.length === 0) {
|
|
return new Map();
|
|
}
|
|
|
|
const uniqueItems = new Map<string, { environmentId: string; key: string }>();
|
|
for (const item of items) {
|
|
uniqueItems.set(`${item.environmentId}:${item.key}`, item);
|
|
}
|
|
|
|
const secretStore = getSecretStore("DATABASE", {
|
|
prismaClient: this.replicaClient,
|
|
});
|
|
|
|
const storeKeys = Array.from(uniqueItems.values()).map((item) =>
|
|
secretKey(projectId, item.environmentId, item.key)
|
|
);
|
|
|
|
const secrets = await secretStore.getSecretsByKeys(SecretValue, storeKeys);
|
|
const secretsByStoreKey = new Map(secrets.map((secret) => [secret.key, secret.value.secret]));
|
|
|
|
const values = new Map<string, string>();
|
|
for (const item of uniqueItems.values()) {
|
|
const storeKey = secretKey(projectId, item.environmentId, item.key);
|
|
const value = secretsByStoreKey.get(storeKey);
|
|
if (value !== undefined) {
|
|
values.set(`${item.environmentId}:${item.key}`, value);
|
|
}
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
async getEnvironmentWithRedactedSecrets(
|
|
projectId: string,
|
|
environmentId: string,
|
|
parentEnvironmentId?: string
|
|
): Promise<EnvironmentVariableWithSecret[]> {
|
|
const variables = await this.getEnvironment(projectId, environmentId, parentEnvironmentId);
|
|
|
|
// Get the keys of all secret variables
|
|
const secretValues = await this.replicaClient.environmentVariableValue.findMany({
|
|
where: {
|
|
environmentId: parentEnvironmentId
|
|
? { in: [environmentId, parentEnvironmentId] }
|
|
: environmentId,
|
|
isSecret: true,
|
|
},
|
|
select: {
|
|
variable: {
|
|
select: {
|
|
key: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
const secretVarKeys = secretValues.map((r) => r.variable.key);
|
|
|
|
// Filter out secret variables
|
|
return variables.map((v) => {
|
|
if (secretVarKeys.includes(v.key)) {
|
|
return {
|
|
key: v.key,
|
|
value: "<redacted>",
|
|
isSecret: true,
|
|
};
|
|
}
|
|
|
|
return { key: v.key, value: v.value, isSecret: false };
|
|
});
|
|
}
|
|
|
|
async getEnvironment(
|
|
projectId: string,
|
|
environmentId: string,
|
|
parentEnvironmentId?: string
|
|
): Promise<EnvironmentVariable[]> {
|
|
const project = await this.prismaClient.project.findFirst({
|
|
where: {
|
|
id: projectId,
|
|
deletedAt: null,
|
|
},
|
|
select: {
|
|
environments: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
where: {
|
|
id: environmentId,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!project || project.environments.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
return this.getEnvironmentVariables(projectId, environmentId, parentEnvironmentId);
|
|
}
|
|
|
|
async #getSecretEnvironmentVariables(
|
|
projectId: string,
|
|
environmentId: string,
|
|
parentEnvironmentId?: string
|
|
): Promise<EnvironmentVariable[]> {
|
|
const secretStore = getSecretStore("DATABASE", {
|
|
prismaClient: this.prismaClient,
|
|
});
|
|
|
|
const parentSecrets = parentEnvironmentId
|
|
? await secretStore.getSecrets(
|
|
SecretValue,
|
|
secretKeyEnvironmentPrefix(projectId, parentEnvironmentId)
|
|
)
|
|
: [];
|
|
|
|
const childSecrets = await secretStore.getSecrets(
|
|
SecretValue,
|
|
secretKeyEnvironmentPrefix(projectId, environmentId)
|
|
);
|
|
|
|
// Merge the secrets, we want child ones to override parent ones
|
|
const mergedSecrets = new Map<string, string>();
|
|
for (const secret of parentSecrets) {
|
|
const { key: parsedKey } = parseSecretKey(secret.key);
|
|
mergedSecrets.set(parsedKey, secret.value.secret);
|
|
}
|
|
for (const secret of childSecrets) {
|
|
const { key: parsedKey } = parseSecretKey(secret.key);
|
|
mergedSecrets.set(parsedKey, secret.value.secret);
|
|
}
|
|
|
|
const merged = Array.from(mergedSecrets.entries()).map(([key, value]) => {
|
|
return {
|
|
key,
|
|
value,
|
|
};
|
|
});
|
|
|
|
return removeBlacklistedVariables(merged);
|
|
}
|
|
|
|
async getEnvironmentVariables(
|
|
projectId: string,
|
|
environmentId: string,
|
|
parentEnvironmentId?: string
|
|
): Promise<EnvironmentVariable[]> {
|
|
return this.#getSecretEnvironmentVariables(projectId, environmentId, parentEnvironmentId);
|
|
}
|
|
|
|
async delete(projectId: string, options: DeleteEnvironmentVariable): Promise<Result> {
|
|
const project = await this.prismaClient.project.findFirst({
|
|
where: {
|
|
id: projectId,
|
|
deletedAt: null,
|
|
},
|
|
select: {
|
|
environments: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!project) {
|
|
return { success: false as const, error: "Project not found" };
|
|
}
|
|
|
|
const environmentVariable = await this.prismaClient.environmentVariable.findFirst({
|
|
select: {
|
|
id: true,
|
|
key: true,
|
|
values: {
|
|
select: {
|
|
id: true,
|
|
environmentId: true,
|
|
valueReference: {
|
|
select: {
|
|
key: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
where: {
|
|
id: options.id,
|
|
},
|
|
});
|
|
if (!environmentVariable) {
|
|
return { success: false as const, error: "Environment variable not found" };
|
|
}
|
|
|
|
try {
|
|
await $transaction(this.prismaClient, "delete env var", async (tx) => {
|
|
await tx.environmentVariable.delete({
|
|
where: {
|
|
id: options.id,
|
|
},
|
|
});
|
|
|
|
const secretStore = getSecretStore("DATABASE", {
|
|
prismaClient: tx,
|
|
});
|
|
|
|
//delete the secret values and references
|
|
for (const value of environmentVariable.values) {
|
|
const key = secretKey(projectId, value.environmentId, environmentVariable.key);
|
|
await secretStore.deleteSecret(key);
|
|
|
|
if (value.valueReference) {
|
|
await tx.secretReference.delete({
|
|
where: {
|
|
key: value.valueReference.key,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
success: true as const,
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false as const,
|
|
error: error instanceof Error ? error.message : "Something went wrong",
|
|
};
|
|
}
|
|
}
|
|
|
|
async deleteValue(projectId: string, options: DeleteEnvironmentVariableValue): Promise<Result> {
|
|
const project = await this.prismaClient.project.findFirst({
|
|
where: {
|
|
id: projectId,
|
|
deletedAt: null,
|
|
},
|
|
select: {
|
|
environments: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!project) {
|
|
return { success: false as const, error: "Project not found" };
|
|
}
|
|
|
|
const environmentVariable = await this.prismaClient.environmentVariable.findFirst({
|
|
select: {
|
|
id: true,
|
|
key: true,
|
|
values: {
|
|
select: {
|
|
id: true,
|
|
environmentId: true,
|
|
valueReference: {
|
|
select: {
|
|
key: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
where: {
|
|
id: options.id,
|
|
},
|
|
});
|
|
|
|
if (!environmentVariable) {
|
|
return { success: false as const, error: "Environment variable not found" };
|
|
}
|
|
|
|
const value = environmentVariable.values.find((v) => v.environmentId === options.environmentId);
|
|
|
|
if (!value) {
|
|
return { success: false as const, error: "Environment variable value not found" };
|
|
}
|
|
|
|
// If this is the last value, delete the whole variable
|
|
if (environmentVariable.values.length === 1) {
|
|
return this.delete(projectId, { id: options.id });
|
|
}
|
|
|
|
try {
|
|
await $transaction(this.prismaClient, "delete env var value", async (tx) => {
|
|
const secretStore = getSecretStore("DATABASE", {
|
|
prismaClient: tx,
|
|
});
|
|
|
|
const key = secretKey(projectId, options.environmentId, environmentVariable.key);
|
|
await secretStore.deleteSecret(key);
|
|
|
|
if (value.valueReference) {
|
|
await tx.secretReference.delete({
|
|
where: {
|
|
key: value.valueReference.key,
|
|
},
|
|
});
|
|
}
|
|
|
|
await tx.environmentVariableValue.delete({
|
|
where: {
|
|
id: value.id,
|
|
},
|
|
});
|
|
});
|
|
|
|
return {
|
|
success: true as const,
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false as const,
|
|
error: error instanceof Error ? error.message : "Something went wrong",
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
export const RuntimeEnvironmentForEnvRepoPayload = {
|
|
select: {
|
|
id: true,
|
|
slug: true,
|
|
type: true,
|
|
projectId: true,
|
|
apiKey: true,
|
|
organizationId: true,
|
|
branchName: true,
|
|
builtInEnvironmentVariableOverrides: true,
|
|
},
|
|
} as const;
|
|
|
|
// Derived from the slim AuthenticatedEnvironment so a full AE satisfies
|
|
// this type — the legacy Prisma payload had `builtInEnvironmentVariableOverrides`
|
|
// as Prisma's JsonValue, which is a subtype of `unknown` in the slim
|
|
// shape, causing assignability errors in the JWT/queue paths that pass
|
|
// AE values straight through. Using Pick<AE, ...> aligns them.
|
|
export type RuntimeEnvironmentForEnvRepo = Pick<
|
|
AuthenticatedEnvironment,
|
|
| "id"
|
|
| "slug"
|
|
| "type"
|
|
| "projectId"
|
|
| "apiKey"
|
|
| "organizationId"
|
|
| "branchName"
|
|
| "builtInEnvironmentVariableOverrides"
|
|
> & { organization?: { featureFlags: unknown } | null };
|
|
|
|
export const environmentVariablesRepository = new EnvironmentVariablesRepository();
|
|
|
|
export async function resolveVariablesForEnvironment(
|
|
runtimeEnvironment: RuntimeEnvironmentForEnvRepo,
|
|
parentEnvironment?: RuntimeEnvironmentForEnvRepo
|
|
) {
|
|
let projectSecrets = await environmentVariablesRepository.getEnvironmentVariables(
|
|
runtimeEnvironment.projectId,
|
|
runtimeEnvironment.id,
|
|
parentEnvironment?.id
|
|
);
|
|
|
|
projectSecrets = renameVariables(projectSecrets, {
|
|
OTEL_RESOURCE_ATTRIBUTES: "CUSTOM_OTEL_RESOURCE_ATTRIBUTES",
|
|
});
|
|
|
|
const overridableTriggerVariables = await resolveOverridableTriggerVariables(runtimeEnvironment);
|
|
|
|
const builtInVariables =
|
|
runtimeEnvironment.type === "DEVELOPMENT"
|
|
? await resolveBuiltInDevVariables(runtimeEnvironment)
|
|
: await resolveBuiltInProdVariables(runtimeEnvironment, parentEnvironment);
|
|
|
|
const overridableOtelVariables =
|
|
runtimeEnvironment.type === "DEVELOPMENT"
|
|
? await resolveOverridableOtelDevVariables(runtimeEnvironment)
|
|
: [];
|
|
|
|
const result = deduplicateVariableArray([
|
|
...overridableTriggerVariables,
|
|
...overridableOtelVariables,
|
|
...projectSecrets,
|
|
...builtInVariables,
|
|
]);
|
|
|
|
return result;
|
|
}
|
|
|
|
function renameVariables(variables: EnvironmentVariable[], renameMap: Record<string, string>) {
|
|
return variables.map((variable) => {
|
|
return {
|
|
...variable,
|
|
key: renameMap[variable.key] ?? variable.key,
|
|
};
|
|
});
|
|
}
|
|
|
|
async function resolveOverridableTriggerVariables(
|
|
runtimeEnvironment: RuntimeEnvironmentForEnvRepo
|
|
) {
|
|
let result: Array<EnvironmentVariable> = [
|
|
{
|
|
key: "TRIGGER_REALTIME_STREAM_VERSION",
|
|
value: env.REALTIME_STREAM_VERSION,
|
|
},
|
|
];
|
|
|
|
return result;
|
|
}
|
|
|
|
async function resolveBuiltInDevVariables(runtimeEnvironment: RuntimeEnvironmentForEnvRepo) {
|
|
let result: Array<EnvironmentVariable> = [
|
|
{
|
|
key: "TRIGGER_OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
value: env.DEV_OTEL_EXPORTER_OTLP_ENDPOINT ?? `${env.APP_ORIGIN.replace(/\/$/, "")}/otel`,
|
|
},
|
|
{
|
|
key: "TRIGGER_API_URL",
|
|
value: env.API_ORIGIN ?? env.APP_ORIGIN,
|
|
},
|
|
{
|
|
key: "TRIGGER_STREAM_URL",
|
|
value: env.STREAM_ORIGIN ?? env.API_ORIGIN ?? env.APP_ORIGIN,
|
|
},
|
|
];
|
|
|
|
if (env.DEV_OTEL_METRICS_ENDPOINT) {
|
|
result.push({
|
|
key: "TRIGGER_OTEL_METRICS_ENDPOINT",
|
|
value: env.DEV_OTEL_METRICS_ENDPOINT,
|
|
});
|
|
}
|
|
|
|
if (env.DEV_OTEL_METRICS_EXPORT_INTERVAL_MILLIS) {
|
|
result.push({
|
|
key: "TRIGGER_OTEL_METRICS_EXPORT_INTERVAL_MILLIS",
|
|
value: env.DEV_OTEL_METRICS_EXPORT_INTERVAL_MILLIS,
|
|
});
|
|
}
|
|
|
|
if (env.DEV_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS) {
|
|
result.push({
|
|
key: "TRIGGER_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS",
|
|
value: env.DEV_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS,
|
|
});
|
|
}
|
|
|
|
if (env.DEV_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS) {
|
|
result.push({
|
|
key: "TRIGGER_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS",
|
|
value: env.DEV_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS,
|
|
});
|
|
}
|
|
|
|
if (env.DEV_OTEL_BATCH_PROCESSING_ENABLED === "1") {
|
|
result = result.concat([
|
|
{
|
|
key: "TRIGGER_OTEL_BATCH_PROCESSING_ENABLED",
|
|
value: "1",
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE",
|
|
value: env.DEV_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_SPAN_SCHEDULED_DELAY_MILLIS",
|
|
value: env.DEV_OTEL_SPAN_SCHEDULED_DELAY_MILLIS,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS",
|
|
value: env.DEV_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_SPAN_MAX_QUEUE_SIZE",
|
|
value: env.DEV_OTEL_SPAN_MAX_QUEUE_SIZE,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_LOG_MAX_EXPORT_BATCH_SIZE",
|
|
value: env.DEV_OTEL_LOG_MAX_EXPORT_BATCH_SIZE,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_LOG_SCHEDULED_DELAY_MILLIS",
|
|
value: env.DEV_OTEL_LOG_SCHEDULED_DELAY_MILLIS,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_LOG_EXPORT_TIMEOUT_MILLIS",
|
|
value: env.DEV_OTEL_LOG_EXPORT_TIMEOUT_MILLIS,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_LOG_MAX_QUEUE_SIZE",
|
|
value: env.DEV_OTEL_LOG_MAX_QUEUE_SIZE,
|
|
},
|
|
{
|
|
key: "OTEL_BATCH_PROCESSING_ENABLED",
|
|
value: "1",
|
|
},
|
|
{
|
|
key: "OTEL_SPAN_MAX_EXPORT_BATCH_SIZE",
|
|
value: env.DEV_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE,
|
|
},
|
|
{
|
|
key: "OTEL_SPAN_SCHEDULED_DELAY_MILLIS",
|
|
value: env.DEV_OTEL_SPAN_SCHEDULED_DELAY_MILLIS,
|
|
},
|
|
{
|
|
key: "OTEL_SPAN_EXPORT_TIMEOUT_MILLIS",
|
|
value: env.DEV_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS,
|
|
},
|
|
{
|
|
key: "OTEL_SPAN_MAX_QUEUE_SIZE",
|
|
value: env.DEV_OTEL_SPAN_MAX_QUEUE_SIZE,
|
|
},
|
|
{
|
|
key: "OTEL_LOG_MAX_EXPORT_BATCH_SIZE",
|
|
value: env.DEV_OTEL_LOG_MAX_EXPORT_BATCH_SIZE,
|
|
},
|
|
{
|
|
key: "OTEL_LOG_SCHEDULED_DELAY_MILLIS",
|
|
value: env.DEV_OTEL_LOG_SCHEDULED_DELAY_MILLIS,
|
|
},
|
|
{
|
|
key: "OTEL_LOG_EXPORT_TIMEOUT_MILLIS",
|
|
value: env.DEV_OTEL_LOG_EXPORT_TIMEOUT_MILLIS,
|
|
},
|
|
{
|
|
key: "OTEL_LOG_MAX_QUEUE_SIZE",
|
|
value: env.DEV_OTEL_LOG_MAX_QUEUE_SIZE,
|
|
},
|
|
]);
|
|
}
|
|
|
|
// Dev branches set branchName too, so carry it to the task via the same
|
|
// TRIGGER_PREVIEW_BRANCH var the prod path uses.
|
|
if (runtimeEnvironment.branchName) {
|
|
result = result.concat([
|
|
{
|
|
key: "TRIGGER_PREVIEW_BRANCH",
|
|
value: runtimeEnvironment.branchName,
|
|
},
|
|
]);
|
|
}
|
|
|
|
const commonVariables = await resolveCommonBuiltInVariables(runtimeEnvironment);
|
|
|
|
return [...result, ...commonVariables];
|
|
}
|
|
|
|
async function resolveOverridableOtelDevVariables(
|
|
runtimeEnvironment: RuntimeEnvironmentForEnvRepo
|
|
) {
|
|
let result: Array<EnvironmentVariable> = [
|
|
{
|
|
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
value: env.DEV_OTEL_EXPORTER_OTLP_ENDPOINT ?? `${env.APP_ORIGIN.replace(/\/$/, "")}/otel`,
|
|
},
|
|
];
|
|
|
|
return result;
|
|
}
|
|
|
|
// Deployed runs normally get the public API origin. When INTERNAL_API_ORIGIN is
|
|
// set and the org's internalApiOriginEnabled flag resolves on (org override wins
|
|
// in both directions; INTERNAL_API_ORIGIN_ENABLED is the global default applied
|
|
// only when the org has not set it), they get the internal origin instead. The
|
|
// global default is the cached DB flag with INTERNAL_API_ORIGIN_ENABLED as the
|
|
// fallback; org flags are read in-memory, so a flip applies on the next attempt.
|
|
function resolveProdApiOrigin(runtimeEnvironment: RuntimeEnvironmentForEnvRepo): string {
|
|
const publicOrigin = env.API_ORIGIN ?? env.APP_ORIGIN;
|
|
|
|
if (!env.INTERNAL_API_ORIGIN) {
|
|
return publicOrigin;
|
|
}
|
|
|
|
const enabled = resolveInternalApiOriginEnabled({
|
|
orgFeatureFlags: runtimeEnvironment.organization?.featureFlags,
|
|
globalDefault:
|
|
globalFlagsRegistry.current()?.[FEATURE_FLAG.internalApiOriginEnabled] ??
|
|
env.INTERNAL_API_ORIGIN_ENABLED === "1",
|
|
});
|
|
|
|
return enabled ? env.INTERNAL_API_ORIGIN : publicOrigin;
|
|
}
|
|
|
|
async function resolveBuiltInProdVariables(
|
|
runtimeEnvironment: RuntimeEnvironmentForEnvRepo,
|
|
parentEnvironment?: RuntimeEnvironmentForEnvRepo
|
|
) {
|
|
const apiOrigin = resolveProdApiOrigin(runtimeEnvironment);
|
|
|
|
let result: Array<EnvironmentVariable> = [
|
|
{
|
|
key: "TRIGGER_SECRET_KEY",
|
|
value: parentEnvironment?.apiKey ?? runtimeEnvironment.apiKey,
|
|
},
|
|
{
|
|
key: "TRIGGER_API_URL",
|
|
value: apiOrigin,
|
|
},
|
|
{
|
|
// Deliberately not switched by internalApiOriginEnabled: streams are
|
|
// long-lived connections served on their own path.
|
|
key: "TRIGGER_STREAM_URL",
|
|
value: env.STREAM_ORIGIN ?? env.API_ORIGIN ?? env.APP_ORIGIN,
|
|
},
|
|
{
|
|
key: "TRIGGER_RUNTIME_WAIT_THRESHOLD_IN_MS",
|
|
value: String(env.CHECKPOINT_THRESHOLD_IN_MS),
|
|
},
|
|
{
|
|
key: "TRIGGER_ORG_ID",
|
|
value: runtimeEnvironment.organizationId,
|
|
},
|
|
];
|
|
|
|
if (runtimeEnvironment.branchName) {
|
|
result = result.concat([
|
|
{
|
|
key: "TRIGGER_PREVIEW_BRANCH",
|
|
value: runtimeEnvironment.branchName,
|
|
},
|
|
]);
|
|
}
|
|
|
|
if (env.PROD_OTEL_METRICS_EXPORT_INTERVAL_MILLIS) {
|
|
result.push({
|
|
key: "TRIGGER_OTEL_METRICS_EXPORT_INTERVAL_MILLIS",
|
|
value: env.PROD_OTEL_METRICS_EXPORT_INTERVAL_MILLIS,
|
|
});
|
|
}
|
|
|
|
if (env.PROD_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS) {
|
|
result.push({
|
|
key: "TRIGGER_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS",
|
|
value: env.PROD_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS,
|
|
});
|
|
}
|
|
|
|
if (env.PROD_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS) {
|
|
result.push({
|
|
key: "TRIGGER_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS",
|
|
value: env.PROD_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS,
|
|
});
|
|
}
|
|
|
|
if (env.PROD_OTEL_BATCH_PROCESSING_ENABLED === "1") {
|
|
result = result.concat([
|
|
{
|
|
key: "TRIGGER_OTEL_BATCH_PROCESSING_ENABLED",
|
|
value: "1",
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE",
|
|
value: env.PROD_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_SPAN_SCHEDULED_DELAY_MILLIS",
|
|
value: env.PROD_OTEL_SPAN_SCHEDULED_DELAY_MILLIS,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS",
|
|
value: env.PROD_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_SPAN_MAX_QUEUE_SIZE",
|
|
value: env.PROD_OTEL_SPAN_MAX_QUEUE_SIZE,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_LOG_MAX_EXPORT_BATCH_SIZE",
|
|
value: env.PROD_OTEL_LOG_MAX_EXPORT_BATCH_SIZE,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_LOG_SCHEDULED_DELAY_MILLIS",
|
|
value: env.PROD_OTEL_LOG_SCHEDULED_DELAY_MILLIS,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_LOG_EXPORT_TIMEOUT_MILLIS",
|
|
value: env.PROD_OTEL_LOG_EXPORT_TIMEOUT_MILLIS,
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_LOG_MAX_QUEUE_SIZE",
|
|
value: env.PROD_OTEL_LOG_MAX_QUEUE_SIZE,
|
|
},
|
|
{
|
|
key: "OTEL_BATCH_PROCESSING_ENABLED",
|
|
value: "1",
|
|
},
|
|
{
|
|
key: "OTEL_SPAN_MAX_EXPORT_BATCH_SIZE",
|
|
value: env.PROD_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE,
|
|
},
|
|
{
|
|
key: "OTEL_SPAN_SCHEDULED_DELAY_MILLIS",
|
|
value: env.PROD_OTEL_SPAN_SCHEDULED_DELAY_MILLIS,
|
|
},
|
|
{
|
|
key: "OTEL_SPAN_EXPORT_TIMEOUT_MILLIS",
|
|
value: env.PROD_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS,
|
|
},
|
|
{
|
|
key: "OTEL_SPAN_MAX_QUEUE_SIZE",
|
|
value: env.PROD_OTEL_SPAN_MAX_QUEUE_SIZE,
|
|
},
|
|
{
|
|
key: "OTEL_LOG_MAX_EXPORT_BATCH_SIZE",
|
|
value: env.PROD_OTEL_LOG_MAX_EXPORT_BATCH_SIZE,
|
|
},
|
|
{
|
|
key: "OTEL_LOG_SCHEDULED_DELAY_MILLIS",
|
|
value: env.PROD_OTEL_LOG_SCHEDULED_DELAY_MILLIS,
|
|
},
|
|
{
|
|
key: "OTEL_LOG_EXPORT_TIMEOUT_MILLIS",
|
|
value: env.PROD_OTEL_LOG_EXPORT_TIMEOUT_MILLIS,
|
|
},
|
|
{
|
|
key: "OTEL_LOG_MAX_QUEUE_SIZE",
|
|
value: env.PROD_OTEL_LOG_MAX_QUEUE_SIZE,
|
|
},
|
|
]);
|
|
}
|
|
|
|
if (env.PROD_USAGE_HEARTBEAT_INTERVAL_MS && env.USAGE_EVENT_URL) {
|
|
result = result.concat([
|
|
{
|
|
key: "USAGE_HEARTBEAT_INTERVAL_MS",
|
|
value: String(env.PROD_USAGE_HEARTBEAT_INTERVAL_MS),
|
|
},
|
|
{
|
|
key: "USAGE_EVENT_URL",
|
|
value: env.USAGE_EVENT_URL,
|
|
},
|
|
]);
|
|
}
|
|
|
|
if (env.PROD_TASK_HEARTBEAT_INTERVAL_MS) {
|
|
result = result.concat([
|
|
{
|
|
key: "HEARTBEAT_INTERVAL_MS",
|
|
value: String(env.PROD_TASK_HEARTBEAT_INTERVAL_MS),
|
|
},
|
|
]);
|
|
}
|
|
|
|
const commonVariables = await resolveCommonBuiltInVariables(runtimeEnvironment);
|
|
|
|
return [...result, ...commonVariables];
|
|
}
|
|
|
|
async function resolveCommonBuiltInVariables(
|
|
runtimeEnvironment: RuntimeEnvironmentForEnvRepo
|
|
): Promise<Array<EnvironmentVariable>> {
|
|
return [
|
|
{
|
|
key: "TRIGGER_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT",
|
|
value: resolveBuiltInEnvironmentVariableOverrides(
|
|
"TRIGGER_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT",
|
|
runtimeEnvironment,
|
|
String(env.TRIGGER_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT)
|
|
),
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT",
|
|
value: resolveBuiltInEnvironmentVariableOverrides(
|
|
"TRIGGER_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT",
|
|
runtimeEnvironment,
|
|
String(env.TRIGGER_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT)
|
|
),
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT",
|
|
value: resolveBuiltInEnvironmentVariableOverrides(
|
|
"TRIGGER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT",
|
|
runtimeEnvironment,
|
|
String(env.TRIGGER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT)
|
|
),
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_LOG_ATTRIBUTE_VALUE_LENGTH_LIMIT",
|
|
value: resolveBuiltInEnvironmentVariableOverrides(
|
|
"TRIGGER_OTEL_LOG_ATTRIBUTE_VALUE_LENGTH_LIMIT",
|
|
runtimeEnvironment,
|
|
String(env.TRIGGER_OTEL_LOG_ATTRIBUTE_VALUE_LENGTH_LIMIT)
|
|
),
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_SPAN_EVENT_COUNT_LIMIT",
|
|
value: resolveBuiltInEnvironmentVariableOverrides(
|
|
"TRIGGER_OTEL_SPAN_EVENT_COUNT_LIMIT",
|
|
runtimeEnvironment,
|
|
String(env.TRIGGER_OTEL_SPAN_EVENT_COUNT_LIMIT)
|
|
),
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_LINK_COUNT_LIMIT",
|
|
value: resolveBuiltInEnvironmentVariableOverrides(
|
|
"TRIGGER_OTEL_LINK_COUNT_LIMIT",
|
|
runtimeEnvironment,
|
|
String(env.TRIGGER_OTEL_LINK_COUNT_LIMIT)
|
|
),
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_ATTRIBUTE_PER_LINK_COUNT_LIMIT",
|
|
value: resolveBuiltInEnvironmentVariableOverrides(
|
|
"TRIGGER_OTEL_ATTRIBUTE_PER_LINK_COUNT_LIMIT",
|
|
runtimeEnvironment,
|
|
String(env.TRIGGER_OTEL_ATTRIBUTE_PER_LINK_COUNT_LIMIT)
|
|
),
|
|
},
|
|
{
|
|
key: "TRIGGER_OTEL_ATTRIBUTE_PER_EVENT_COUNT_LIMIT",
|
|
value: resolveBuiltInEnvironmentVariableOverrides(
|
|
"TRIGGER_OTEL_ATTRIBUTE_PER_EVENT_COUNT_LIMIT",
|
|
runtimeEnvironment,
|
|
String(env.TRIGGER_OTEL_ATTRIBUTE_PER_EVENT_COUNT_LIMIT)
|
|
),
|
|
},
|
|
{
|
|
key: "TRIGGER_WAIT_UNTIL_TIMEOUT_MS",
|
|
value: resolveBuiltInEnvironmentVariableOverrides(
|
|
"TRIGGER_WAIT_UNTIL_TIMEOUT_MS",
|
|
runtimeEnvironment,
|
|
String(env.WAIT_UNTIL_TIMEOUT_MS)
|
|
),
|
|
},
|
|
];
|
|
}
|
|
|
|
function resolveBuiltInEnvironmentVariableOverrides(
|
|
key: string,
|
|
runtimeEnvironment: RuntimeEnvironmentForEnvRepo,
|
|
defaultValue: string
|
|
) {
|
|
const overrides = runtimeEnvironment.builtInEnvironmentVariableOverrides;
|
|
|
|
if (!overrides) {
|
|
return defaultValue;
|
|
}
|
|
|
|
if (
|
|
!Array.isArray(overrides) &&
|
|
typeof overrides === "object" &&
|
|
overrides !== null &&
|
|
key in overrides
|
|
) {
|
|
const value = (overrides as Record<string, unknown>)[key];
|
|
if (typeof value === "string") {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|