mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-19 10:36:23 +00:00
Initial Commit for terraform cloud intergation
This commit is contained in:
@@ -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";
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export enum TerraformCloudConnectionMethod {
|
||||
API_TOKEN = "api-token"
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
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,
|
||||
TTerraformCloudProject,
|
||||
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.API_TOKEN]
|
||||
};
|
||||
};
|
||||
|
||||
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 orgsResponse = await request.get<{ data: { id: string; attributes: { name: string } }[] }>(
|
||||
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/organizations`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Content-Type": "application/vnd.api+json"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!orgsResponse.data?.data) {
|
||||
throw new InternalServerError({
|
||||
message: "Failed to get organizations: Response was empty"
|
||||
});
|
||||
}
|
||||
|
||||
const orgEntities = orgsResponse.data.data;
|
||||
const orgsWithProjectsAndWorkspaces: TTerraformCloudOrganization[] = [];
|
||||
|
||||
const projectPromises = orgEntities.map((org) =>
|
||||
request
|
||||
.get<{ data: { id: string; attributes: { name: string } }[] }>(
|
||||
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/organizations/${org.id}/projects`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Content-Type": "application/vnd.api+json"
|
||||
}
|
||||
}
|
||||
)
|
||||
.catch(() => ({ data: { data: [] } }))
|
||||
);
|
||||
|
||||
const workspacePromises = orgEntities.map((org) =>
|
||||
request
|
||||
.get<{ data: { id: string; attributes: { name: string } }[] }>(
|
||||
`${IntegrationUrls.TERRAFORM_CLOUD_API_URL}/api/v2/organizations/${org.id}/workspaces`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Content-Type": "application/vnd.api+json"
|
||||
}
|
||||
}
|
||||
)
|
||||
.catch(() => ({ data: { data: [] } }))
|
||||
);
|
||||
|
||||
const [projectResponses, workspaceResponses] = await Promise.all([
|
||||
Promise.all(projectPromises),
|
||||
Promise.all(workspacePromises)
|
||||
]);
|
||||
|
||||
for (let i = 0; i < orgEntities.length; i += 1) {
|
||||
const org = orgEntities[i];
|
||||
const projectsData = projectResponses[i].data?.data || [];
|
||||
const workspacesData = workspaceResponses[i].data?.data || [];
|
||||
|
||||
const projects: TTerraformCloudProject[] = projectsData.map((project) => ({
|
||||
id: project.id,
|
||||
name: project.attributes.name
|
||||
}));
|
||||
|
||||
const workspaces: TTerraformCloudWorkspace[] = workspacesData.map((workspace) => ({
|
||||
id: workspace.id,
|
||||
name: workspace.attributes.name
|
||||
}));
|
||||
|
||||
orgsWithProjectsAndWorkspaces.push({
|
||||
id: org.id,
|
||||
name: org.attributes.name,
|
||||
projects,
|
||||
workspaces
|
||||
});
|
||||
}
|
||||
|
||||
return orgsWithProjectsAndWorkspaces;
|
||||
};
|
||||
+60
@@ -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")
|
||||
});
|
||||
|
||||
const BaseTerraformCloudConnectionSchema = BaseAppConnectionSchema.extend({
|
||||
app: z.literal(AppConnection.TerraformCloud)
|
||||
});
|
||||
|
||||
export const TerraformCloudConnectionSchema = BaseTerraformCloudConnectionSchema.extend({
|
||||
method: z.literal(TerraformCloudConnectionMethod.API_TOKEN),
|
||||
credentials: TerraformCloudConnectionAccessTokenCredentialsSchema
|
||||
});
|
||||
|
||||
export const SanitizedTerraformCloudConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseTerraformCloudConnectionSchema.extend({
|
||||
method: z.literal(TerraformCloudConnectionMethod.API_TOKEN),
|
||||
credentials: TerraformCloudConnectionAccessTokenCredentialsSchema.pick({})
|
||||
})
|
||||
]);
|
||||
|
||||
export const ValidateTerraformCloudConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z
|
||||
.literal(TerraformCloudConnectionMethod.API_TOKEN)
|
||||
.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()
|
||||
});
|
||||
+29
@@ -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
|
||||
};
|
||||
};
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
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 TValidateTerraformCloudConnectionCredentials = typeof ValidateTerraformCloudConnectionCredentialsSchema;
|
||||
|
||||
export type TTerraformCloudConnectionConfig = DiscriminativePick<
|
||||
TTerraformCloudConnectionInput,
|
||||
"method" | "app" | "credentials"
|
||||
> & {
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type TerraformCloudOrg = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TerraformCloudApp = {
|
||||
name: string;
|
||||
id: string;
|
||||
envs: { name: string; id: string }[];
|
||||
};
|
||||
|
||||
export type TerraformCloudOrgWithApps = TerraformCloudOrg & {
|
||||
apps: TerraformCloudApp[];
|
||||
};
|
||||
|
||||
export type TTerraformCloudProject = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TTerraformCloudWorkspace = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TTerraformCloudOrganization = {
|
||||
id: string;
|
||||
name: string;
|
||||
projects: TTerraformCloudProject[];
|
||||
workspaces: TTerraformCloudWorkspace[];
|
||||
};
|
||||
|
||||
export type TTerraformCloudConnectionOrganization = TTerraformCloudOrganization;
|
||||
export type TTerraformCloudConnectionProject = TTerraformCloudProject;
|
||||
export type TTerraformCloudConnectionWorkspace = TTerraformCloudWorkspace;
|
||||
|
||||
export enum TerraformCloudSyncScope {
|
||||
Project = "project",
|
||||
Workspace = "workspace"
|
||||
}
|
||||
Reference in New Issue
Block a user