Merge remote-tracking branch 'origin/main' into feat/add-render-app-connection-and-secret-sync

This commit is contained in:
Sheen Capadngan
2025-06-19 03:14:23 +08:00
281 changed files with 6700 additions and 1520 deletions
@@ -23,7 +23,8 @@ export enum AppConnection {
OCI = "oci",
OracleDB = "oracledb",
OnePass = "1password",
Render = "render"
Render = "render",
Flyio = "flyio"
}
export enum AWSRegion {
@@ -56,6 +56,7 @@ import {
getDatabricksConnectionListItem,
validateDatabricksConnectionCredentials
} from "./databricks";
import { FlyioConnectionMethod, getFlyioConnectionListItem, validateFlyioConnectionCredentials } from "./flyio";
import { GcpConnectionMethod, getGcpConnectionListItem, validateGcpConnectionCredentials } from "./gcp";
import { getGitHubConnectionListItem, GitHubConnectionMethod, validateGitHubConnectionCredentials } from "./github";
import {
@@ -124,7 +125,8 @@ export const listAppConnectionOptions = () => {
getOCIConnectionListItem(),
getOracleDBConnectionListItem(),
getOnePassConnectionListItem(),
getRenderConnectionListItem()
getRenderConnectionListItem(),
getFlyioConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name));
};
@@ -200,7 +202,8 @@ export const validateAppConnectionCredentials = async (
[AppConnection.OCI]: validateOCIConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.OracleDB]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.OnePass]: validateOnePassConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Render]: validateRenderConnectionCredentials as TAppConnectionCredentialsValidator
[AppConnection.Render]: validateRenderConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator
};
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
@@ -242,6 +245,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case HCVaultConnectionMethod.AccessToken:
case TeamCityConnectionMethod.AccessToken:
case AzureDevOpsConnectionMethod.AccessToken:
case FlyioConnectionMethod.AccessToken:
return "Access Token";
case Auth0ConnectionMethod.ClientCredentials:
return "Client Credentials";
@@ -306,7 +310,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.OCI]: platformManagedCredentialsNotSupported,
[AppConnection.OracleDB]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
[AppConnection.OnePass]: platformManagedCredentialsNotSupported,
[AppConnection.Render]: platformManagedCredentialsNotSupported
[AppConnection.Render]: platformManagedCredentialsNotSupported,
[AppConnection.Flyio]: platformManagedCredentialsNotSupported
};
export const enterpriseAppCheck = async (
@@ -25,7 +25,8 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.OCI]: "OCI",
[AppConnection.OracleDB]: "OracleDB",
[AppConnection.OnePass]: "1Password",
[AppConnection.Render]: "Render"
[AppConnection.Render]: "Render",
[AppConnection.Flyio]: "Fly.io"
};
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
@@ -53,5 +54,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.OracleDB]: AppConnectionPlanType.Enterprise,
[AppConnection.OnePass]: AppConnectionPlanType.Regular,
[AppConnection.MySql]: AppConnectionPlanType.Regular,
[AppConnection.Render]: AppConnectionPlanType.Regular
[AppConnection.Render]: AppConnectionPlanType.Regular,
[AppConnection.Flyio]: AppConnectionPlanType.Regular
};
@@ -5,7 +5,7 @@ import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-c
import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/app-connections/oracledb";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { generateHash } from "@app/lib/crypto/encryption";
import { DatabaseErrorCode } from "@app/lib/error-codes";
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
@@ -49,6 +49,8 @@ import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
import { camundaConnectionService } from "./camunda/camunda-connection-service";
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
import { databricksConnectionService } from "./databricks/databricks-connection-service";
import { ValidateFlyioConnectionCredentialsSchema } from "./flyio";
import { flyioConnectionService } from "./flyio/flyio-connection-service";
import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
import { gcpConnectionService } from "./gcp/gcp-connection-service";
import { ValidateGitHubConnectionCredentialsSchema } from "./github";
@@ -107,7 +109,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.OCI]: ValidateOCIConnectionCredentialsSchema,
[AppConnection.OracleDB]: ValidateOracleDBConnectionCredentialsSchema,
[AppConnection.OnePass]: ValidateOnePassConnectionCredentialsSchema,
[AppConnection.Render]: ValidateRenderConnectionCredentialsSchema
[AppConnection.Render]: ValidateRenderConnectionCredentialsSchema,
[AppConnection.Flyio]: ValidateFlyioConnectionCredentialsSchema
};
export const appConnectionServiceFactory = ({
@@ -513,6 +516,7 @@ export const appConnectionServiceFactory = ({
teamcity: teamcityConnectionService(connectAppConnectionById),
oci: ociConnectionService(connectAppConnectionById, licenseService),
onepass: onePassConnectionService(connectAppConnectionById),
render: renderConnectionService(connectAppConnectionById)
render: renderConnectionService(connectAppConnectionById),
flyio: flyioConnectionService(connectAppConnectionById)
};
};
@@ -68,6 +68,12 @@ import {
TDatabricksConnectionInput,
TValidateDatabricksConnectionCredentialsSchema
} from "./databricks";
import {
TFlyioConnection,
TFlyioConnectionConfig,
TFlyioConnectionInput,
TValidateFlyioConnectionCredentialsSchema
} from "./flyio";
import {
TGcpConnection,
TGcpConnectionConfig,
@@ -168,6 +174,7 @@ export type TAppConnection = { id: string } & (
| TOracleDBConnection
| TOnePassConnection
| TRenderConnection
| TFlyioConnection
);
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
@@ -200,6 +207,7 @@ export type TAppConnectionInput = { id: string } & (
| TOracleDBConnectionInput
| TOnePassConnectionInput
| TRenderConnectionInput
| TFlyioConnectionInput
);
export type TSqlConnectionInput =
@@ -239,7 +247,8 @@ export type TAppConnectionConfig =
| TTeamCityConnectionConfig
| TOCIConnectionConfig
| TOnePassConnectionConfig
| TRenderConnectionConfig;
| TRenderConnectionConfig
| TFlyioConnectionConfig;
export type TValidateAppConnectionCredentialsSchema =
| TValidateAwsConnectionCredentialsSchema
@@ -266,7 +275,8 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateOCIConnectionCredentialsSchema
| TValidateOracleDBConnectionCredentialsSchema
| TValidateOnePassConnectionCredentialsSchema
| TValidateRenderConnectionCredentialsSchema;
| TValidateRenderConnectionCredentialsSchema
| TValidateFlyioConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = {
connectionId: string;
@@ -0,0 +1,3 @@
export enum FlyioConnectionMethod {
AccessToken = "access-token"
}
@@ -0,0 +1,72 @@
import { AxiosError } from "axios";
import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { FlyioConnectionMethod } from "./flyio-connection-enums";
import { TFlyioApp, TFlyioConnection, TFlyioConnectionConfig } from "./flyio-connection-types";
export const getFlyioConnectionListItem = () => {
return {
name: "Fly.io" as const,
app: AppConnection.Flyio as const,
methods: Object.values(FlyioConnectionMethod) as [FlyioConnectionMethod.AccessToken]
};
};
export const validateFlyioConnectionCredentials = async (config: TFlyioConnectionConfig) => {
const { accessToken } = config.credentials;
try {
const resp = await request.post<{ data: { viewer: { id: string | null; email: string } } | null }>(
IntegrationUrls.FLYIO_API_URL,
{ query: "query { viewer { id email } }" },
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
);
if (resp.data.data === null) {
throw new BadRequestError({
message: "Unable to validate connection: Invalid access token provided."
});
}
} 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"
});
}
return config.credentials;
};
export const listFlyioApps = async (appConnection: TFlyioConnection) => {
const { accessToken } = appConnection.credentials;
const resp = await request.post<{ data: { apps: { nodes: TFlyioApp[] } } }>(
IntegrationUrls.FLYIO_API_URL,
{
query:
"query GetApps { apps { nodes { id name hostname status organization { id slug } currentRelease { version status createdAt } } } }"
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
Accept: "application/json"
}
}
);
return resp.data.data.apps.nodes;
};
@@ -0,0 +1,62 @@
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 { FlyioConnectionMethod } from "./flyio-connection-enums";
export const FlyioConnectionAccessTokenCredentialsSchema = z.object({
accessToken: z
.string()
.trim()
.min(1, "Access Token required")
.max(1000)
.startsWith("FlyV1", "Token must start with 'FlyV1'")
.describe(AppConnections.CREDENTIALS.FLYIO.accessToken)
});
const BaseFlyioConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Flyio) });
export const FlyioConnectionSchema = BaseFlyioConnectionSchema.extend({
method: z.literal(FlyioConnectionMethod.AccessToken),
credentials: FlyioConnectionAccessTokenCredentialsSchema
});
export const SanitizedFlyioConnectionSchema = z.discriminatedUnion("method", [
BaseFlyioConnectionSchema.extend({
method: z.literal(FlyioConnectionMethod.AccessToken),
credentials: FlyioConnectionAccessTokenCredentialsSchema.pick({})
})
]);
export const ValidateFlyioConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z.literal(FlyioConnectionMethod.AccessToken).describe(AppConnections.CREATE(AppConnection.Flyio).method),
credentials: FlyioConnectionAccessTokenCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.Flyio).credentials
)
})
]);
export const CreateFlyioConnectionSchema = ValidateFlyioConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Flyio)
);
export const UpdateFlyioConnectionSchema = z
.object({
credentials: FlyioConnectionAccessTokenCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.Flyio).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Flyio));
export const FlyioConnectionListItemSchema = z.object({
name: z.literal("Fly.io"),
app: z.literal(AppConnection.Flyio),
methods: z.nativeEnum(FlyioConnectionMethod).array()
});
@@ -0,0 +1,30 @@
import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { listFlyioApps } from "./flyio-connection-fns";
import { TFlyioConnection } from "./flyio-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TFlyioConnection>;
export const flyioConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listApps = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Flyio, connectionId, actor);
try {
const apps = await listFlyioApps(appConnection);
return apps;
} catch (error) {
logger.error(error, "Failed to establish connection with fly.io");
return [];
}
};
return {
listApps
};
};
@@ -0,0 +1,27 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateFlyioConnectionSchema,
FlyioConnectionSchema,
ValidateFlyioConnectionCredentialsSchema
} from "./flyio-connection-schemas";
export type TFlyioConnection = z.infer<typeof FlyioConnectionSchema>;
export type TFlyioConnectionInput = z.infer<typeof CreateFlyioConnectionSchema> & {
app: AppConnection.Flyio;
};
export type TValidateFlyioConnectionCredentialsSchema = typeof ValidateFlyioConnectionCredentialsSchema;
export type TFlyioConnectionConfig = DiscriminativePick<TFlyioConnectionInput, "method" | "app" | "credentials"> & {
orgId: string;
};
export type TFlyioApp = {
id: string;
name: string;
};
@@ -0,0 +1,4 @@
export * from "./flyio-connection-enums";
export * from "./flyio-connection-fns";
export * from "./flyio-connection-schemas";
export * from "./flyio-connection-types";