merge deconflict

This commit is contained in:
Scott Wilson
2025-04-15 14:32:21 -07:00
237 changed files with 7423 additions and 1530 deletions
@@ -6,6 +6,7 @@ export enum AppConnection {
AzureKeyVault = "azure-key-vault",
AzureAppConfiguration = "azure-app-configuration",
Humanitec = "humanitec",
TerraformCloud = "terraform-cloud",
Vercel = "vercel",
Postgres = "postgres",
MsSql = "mssql",
@@ -43,6 +43,11 @@ import {
} from "./humanitec";
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
import {
getTerraformCloudConnectionListItem,
TerraformCloudConnectionMethod,
validateTerraformCloudConnectionCredentials
} from "./terraform-cloud";
import { VercelConnectionMethod } from "./vercel";
import { getVercelConnectionListItem, validateVercelConnectionCredentials } from "./vercel/vercel-connection-fns";
@@ -55,6 +60,7 @@ export const listAppConnectionOptions = () => {
getAzureAppConfigurationConnectionListItem(),
getDatabricksConnectionListItem(),
getHumanitecConnectionListItem(),
getTerraformCloudConnectionListItem(),
getVercelConnectionListItem(),
getPostgresConnectionListItem(),
getMsSqlConnectionListItem(),
@@ -121,6 +127,7 @@ export const validateAppConnectionCredentials = async (
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator
};
@@ -146,6 +153,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case CamundaConnectionMethod.ClientCredentials:
return "Client Credentials";
case HumanitecConnectionMethod.ApiToken:
case TerraformCloudConnectionMethod.ApiToken:
case VercelConnectionMethod.ApiToken:
return "API Token";
case PostgresConnectionMethod.UsernameAndPassword:
@@ -193,6 +201,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.Humanitec]: platformManagedCredentialsNotSupported,
[AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
[AppConnection.Auth0]: platformManagedCredentialsNotSupported
@@ -8,6 +8,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.AzureAppConfiguration]: "Azure App Configuration",
[AppConnection.Databricks]: "Databricks",
[AppConnection.Humanitec]: "Humanitec",
[AppConnection.TerraformCloud]: "Terraform Cloud",
[AppConnection.Vercel]: "Vercel",
[AppConnection.Postgres]: "PostgreSQL",
[AppConnection.MsSql]: "Microsoft SQL Server",
@@ -45,6 +45,8 @@ import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
import { ValidateTerraformCloudConnectionCredentialsSchema } from "./terraform-cloud";
import { terraformCloudConnectionService } from "./terraform-cloud/terraform-cloud-connection-service";
import { ValidateVercelConnectionCredentialsSchema } from "./vercel";
import { vercelConnectionService } from "./vercel/vercel-connection-service";
@@ -64,6 +66,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.AzureAppConfiguration]: ValidateAzureAppConfigurationConnectionCredentialsSchema,
[AppConnection.Databricks]: ValidateDatabricksConnectionCredentialsSchema,
[AppConnection.Humanitec]: ValidateHumanitecConnectionCredentialsSchema,
[AppConnection.TerraformCloud]: ValidateTerraformCloudConnectionCredentialsSchema,
[AppConnection.Vercel]: ValidateVercelConnectionCredentialsSchema,
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
@@ -440,6 +443,7 @@ export const appConnectionServiceFactory = ({
databricks: databricksConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
aws: awsConnectionService(connectAppConnectionById),
humanitec: humanitecConnectionService(connectAppConnectionById),
terraformCloud: terraformCloudConnectionService(connectAppConnectionById),
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
vercel: vercelConnectionService(connectAppConnectionById),
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService)
@@ -63,6 +63,12 @@ import {
TPostgresConnectionInput,
TValidatePostgresConnectionCredentialsSchema
} from "./postgres";
import {
TTerraformCloudConnection,
TTerraformCloudConnectionConfig,
TTerraformCloudConnectionInput,
TValidateTerraformCloudConnectionCredentialsSchema
} from "./terraform-cloud";
import {
TValidateVercelConnectionCredentialsSchema,
TVercelConnection,
@@ -78,6 +84,7 @@ export type TAppConnection = { id: string } & (
| TAzureAppConfigurationConnection
| TDatabricksConnection
| THumanitecConnection
| TTerraformCloudConnection
| TVercelConnection
| TPostgresConnection
| TMsSqlConnection
@@ -97,6 +104,7 @@ export type TAppConnectionInput = { id: string } & (
| TAzureAppConfigurationConnectionInput
| TDatabricksConnectionInput
| THumanitecConnectionInput
| TTerraformCloudConnectionInput
| TVercelConnectionInput
| TPostgresConnectionInput
| TMsSqlConnectionInput
@@ -123,6 +131,7 @@ export type TAppConnectionConfig =
| TAzureAppConfigurationConnectionConfig
| TDatabricksConnectionConfig
| THumanitecConnectionConfig
| TTerraformCloudConnectionConfig
| TSqlConnectionConfig
| TCamundaConnectionConfig
| TVercelConnectionConfig
@@ -140,6 +149,7 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateMsSqlConnectionCredentialsSchema
| TValidateCamundaConnectionCredentialsSchema
| TValidateVercelConnectionCredentialsSchema
| TValidateTerraformCloudConnectionCredentialsSchema
| TValidateAuth0ConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = {
@@ -0,0 +1,4 @@
export * from "./terraform-cloud-connection-enums";
export * from "./terraform-cloud-connection-fns";
export * from "./terraform-cloud-connection-schemas";
export * from "./terraform-cloud-connection-types";
@@ -0,0 +1,3 @@
export enum TerraformCloudConnectionMethod {
ApiToken = "api-token"
}
@@ -0,0 +1,135 @@
import { AxiosError, AxiosResponse } from "axios";
import { request } from "@app/lib/config/request";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { TerraformCloudConnectionMethod } from "./terraform-cloud-connection-enums";
import {
TTerraformCloudConnection,
TTerraformCloudConnectionConfig,
TTerraformCloudOrganization,
TTerraformCloudVariableSet,
TTerraformCloudWorkspace
} from "./terraform-cloud-connection-types";
export const getTerraformCloudConnectionListItem = () => {
return {
name: "Terraform Cloud" as const,
app: AppConnection.TerraformCloud as const,
methods: Object.values(TerraformCloudConnectionMethod) as [TerraformCloudConnectionMethod.ApiToken]
};
};
export const validateTerraformCloudConnectionCredentials = async (config: TTerraformCloudConnectionConfig) => {
const { credentials: inputCredentials } = config;
let response: AxiosResponse<{ data: TTerraformCloudOrganization[] }> | null = null;
try {
response = await request.get<{ data: TTerraformCloudOrganization[] }>(
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/organizations`,
{
headers: {
Authorization: `Bearer ${inputCredentials.apiToken}`,
"Content-Type": "application/vnd.api+json"
}
}
);
} catch (error: unknown) {
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to validate connection - verify credentials"
});
}
if (!response?.data) {
throw new InternalServerError({
message: "Failed to get organizations: Response was empty"
});
}
return inputCredentials;
};
export const listOrganizations = async (
appConnection: TTerraformCloudConnection
): Promise<TTerraformCloudOrganization[]> => {
const {
credentials: { apiToken }
} = appConnection;
const headers = {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/vnd.api+json"
};
const fetchAllPages = async <T>(url: string): Promise<T[]> => {
let results: T[] = [];
let nextUrl: string | null = url;
while (nextUrl) {
// eslint-disable-next-line no-await-in-loop
const res: AxiosResponse<{ data: T[]; links?: { next?: string } }> = await request.get(nextUrl, { headers });
results = results.concat(res.data.data);
nextUrl = res.data.links?.next || null;
}
return results;
};
const orgEntities = await fetchAllPages<{ id: string; attributes: { name: string } }>(
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/organizations`
);
const orgsWithVariableSetsAndWorkspaces: TTerraformCloudOrganization[] = [];
const variableSetPromises = orgEntities.map((org) =>
fetchAllPages<{ id: string; attributes: { name: string; description?: string; global?: boolean } }>(
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/organizations/${org.id}/varsets`
).catch(() => [])
);
const workspacePromises = orgEntities.map((org) =>
fetchAllPages<{ id: string; attributes: { name: string } }>(
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/organizations/${org.id}/workspaces`
).catch(() => [])
);
const [variableSetResults, workspaceResults] = await Promise.all([
Promise.all(variableSetPromises),
Promise.all(workspacePromises)
]);
for (let i = 0; i < orgEntities.length; i += 1) {
const org = orgEntities[i];
const variableSetsData = variableSetResults[i];
const workspacesData = workspaceResults[i];
const variableSets: TTerraformCloudVariableSet[] = variableSetsData.map((varSet) => ({
id: varSet.id,
name: varSet.attributes.name,
description: varSet.attributes.description,
global: varSet.attributes.global
}));
const workspaces: TTerraformCloudWorkspace[] = workspacesData.map((workspace) => ({
id: workspace.id,
name: workspace.attributes.name
}));
orgsWithVariableSetsAndWorkspaces.push({
id: org.id,
name: org.attributes.name,
variableSets,
workspaces
});
}
return orgsWithVariableSetsAndWorkspaces;
};
@@ -0,0 +1,60 @@
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { TerraformCloudConnectionMethod } from "./terraform-cloud-connection-enums";
export const TerraformCloudConnectionAccessTokenCredentialsSchema = z.object({
apiToken: z.string().trim().min(1, "API Token required").describe(AppConnections.CREDENTIALS.TERRAFORM_CLOUD.apiToken)
});
const BaseTerraformCloudConnectionSchema = BaseAppConnectionSchema.extend({
app: z.literal(AppConnection.TerraformCloud)
});
export const TerraformCloudConnectionSchema = BaseTerraformCloudConnectionSchema.extend({
method: z.literal(TerraformCloudConnectionMethod.ApiToken),
credentials: TerraformCloudConnectionAccessTokenCredentialsSchema
});
export const SanitizedTerraformCloudConnectionSchema = z.discriminatedUnion("method", [
BaseTerraformCloudConnectionSchema.extend({
method: z.literal(TerraformCloudConnectionMethod.ApiToken),
credentials: TerraformCloudConnectionAccessTokenCredentialsSchema.pick({})
})
]);
export const ValidateTerraformCloudConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z
.literal(TerraformCloudConnectionMethod.ApiToken)
.describe(AppConnections?.CREATE(AppConnection.TerraformCloud).method),
credentials: TerraformCloudConnectionAccessTokenCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.TerraformCloud).credentials
)
})
]);
export const CreateTerraformCloudConnectionSchema = ValidateTerraformCloudConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.TerraformCloud)
);
export const UpdateTerraformCloudConnectionSchema = z
.object({
credentials: TerraformCloudConnectionAccessTokenCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.TerraformCloud).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.TerraformCloud));
export const TerraformCloudConnectionListItemSchema = z.object({
name: z.literal("Terraform Cloud"),
app: z.literal(AppConnection.TerraformCloud),
methods: z.nativeEnum(TerraformCloudConnectionMethod).array()
});
@@ -0,0 +1,29 @@
import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { listOrganizations as getTerraformCloudOrganizations } from "./terraform-cloud-connection-fns";
import { TTerraformCloudConnection } from "./terraform-cloud-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TTerraformCloudConnection>;
export const terraformCloudConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listOrganizations = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.TerraformCloud, connectionId, actor);
try {
const organizations = await getTerraformCloudOrganizations(appConnection);
return organizations;
} catch (error) {
logger.error(error, "Failed to establish connection with Terraform Cloud");
return [];
}
};
return {
listOrganizations
};
};
@@ -0,0 +1,45 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateTerraformCloudConnectionSchema,
TerraformCloudConnectionSchema,
ValidateTerraformCloudConnectionCredentialsSchema
} from "./terraform-cloud-connection-schemas";
export type TTerraformCloudConnection = z.infer<typeof TerraformCloudConnectionSchema>;
export type TTerraformCloudConnectionInput = z.infer<typeof CreateTerraformCloudConnectionSchema> & {
app: AppConnection.TerraformCloud;
};
export type TValidateTerraformCloudConnectionCredentialsSchema =
typeof ValidateTerraformCloudConnectionCredentialsSchema;
export type TTerraformCloudConnectionConfig = DiscriminativePick<
TTerraformCloudConnectionInput,
"method" | "app" | "credentials"
> & {
orgId: string;
};
export type TTerraformCloudVariableSet = {
id: string;
name: string;
description?: string;
global?: boolean;
};
export type TTerraformCloudWorkspace = {
id: string;
name: string;
};
export type TTerraformCloudOrganization = {
id: string;
name: string;
variableSets: TTerraformCloudVariableSet[];
workspaces: TTerraformCloudWorkspace[];
};