Fly.io app connection

This commit is contained in:
x032205
2025-06-16 14:08:42 -04:00
parent 46f9927cf1
commit da5f054a65
25 changed files with 492 additions and 17 deletions

View File

@@ -2223,6 +2223,9 @@ export const AppConnections = {
ONEPASS: { ONEPASS: {
instanceUrl: "The URL of the 1Password Connect Server instance to authenticate with.", instanceUrl: "The URL of the 1Password Connect Server instance to authenticate with.",
apiToken: "The API token used to access the 1Password Connect Server." apiToken: "The API token used to access the 1Password Connect Server."
},
FLYIO: {
accessToken: "The Access Token used to access fly.io."
} }
} }
}; };

View File

@@ -39,6 +39,7 @@ import {
DatabricksConnectionListItemSchema, DatabricksConnectionListItemSchema,
SanitizedDatabricksConnectionSchema SanitizedDatabricksConnectionSchema
} from "@app/services/app-connection/databricks"; } from "@app/services/app-connection/databricks";
import { FlyioConnectionListItemSchema, SanitizedFlyioConnectionSchema } from "@app/services/app-connection/flyio";
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp"; import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github"; import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
import { import {
@@ -100,7 +101,8 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedTeamCityConnectionSchema.options, ...SanitizedTeamCityConnectionSchema.options,
...SanitizedOCIConnectionSchema.options, ...SanitizedOCIConnectionSchema.options,
...SanitizedOracleDBConnectionSchema.options, ...SanitizedOracleDBConnectionSchema.options,
...SanitizedOnePassConnectionSchema.options ...SanitizedOnePassConnectionSchema.options,
...SanitizedFlyioConnectionSchema.options
]); ]);
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
@@ -127,7 +129,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
TeamCityConnectionListItemSchema, TeamCityConnectionListItemSchema,
OCIConnectionListItemSchema, OCIConnectionListItemSchema,
OracleDBConnectionListItemSchema, OracleDBConnectionListItemSchema,
OnePassConnectionListItemSchema OnePassConnectionListItemSchema,
FlyioConnectionListItemSchema
]); ]);
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => { export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {

View File

@@ -0,0 +1,52 @@
import z from "zod";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateFlyioConnectionSchema,
SanitizedFlyioConnectionSchema,
UpdateFlyioConnectionSchema
} from "@app/services/app-connection/flyio";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerFlyioConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.Flyio,
server,
sanitizedResponseSchema: SanitizedFlyioConnectionSchema,
createSchema: CreateFlyioConnectionSchema,
updateSchema: UpdateFlyioConnectionSchema
});
// The following endpoints are for internal Infisical App use only and not part of the public API
server.route({
method: "GET",
url: `/:connectionId/apps`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z
.object({
// TODO(andrey): apps
id: z.string(),
name: z.string()
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const apps = await server.services.appConnection.flyio.listApps(connectionId, req.permission);
return apps;
}
});
};

View File

@@ -11,6 +11,7 @@ import { registerAzureDevOpsConnectionRouter } from "./azure-devops-connection-r
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router"; import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
import { registerCamundaConnectionRouter } from "./camunda-connection-router"; import { registerCamundaConnectionRouter } from "./camunda-connection-router";
import { registerDatabricksConnectionRouter } from "./databricks-connection-router"; import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
import { registerFlyioConnectionRouter } from "./flyio-connection-router";
import { registerGcpConnectionRouter } from "./gcp-connection-router"; import { registerGcpConnectionRouter } from "./gcp-connection-router";
import { registerGitHubConnectionRouter } from "./github-connection-router"; import { registerGitHubConnectionRouter } from "./github-connection-router";
import { registerGitHubRadarConnectionRouter } from "./github-radar-connection-router"; import { registerGitHubRadarConnectionRouter } from "./github-radar-connection-router";
@@ -52,5 +53,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.TeamCity]: registerTeamCityConnectionRouter, [AppConnection.TeamCity]: registerTeamCityConnectionRouter,
[AppConnection.OCI]: registerOCIConnectionRouter, [AppConnection.OCI]: registerOCIConnectionRouter,
[AppConnection.OracleDB]: registerOracleDBConnectionRouter, [AppConnection.OracleDB]: registerOracleDBConnectionRouter,
[AppConnection.OnePass]: registerOnePassConnectionRouter [AppConnection.OnePass]: registerOnePassConnectionRouter,
[AppConnection.Flyio]: registerFlyioConnectionRouter
}; };

View File

@@ -22,7 +22,8 @@ export enum AppConnection {
TeamCity = "teamcity", TeamCity = "teamcity",
OCI = "oci", OCI = "oci",
OracleDB = "oracledb", OracleDB = "oracledb",
OnePass = "1password" OnePass = "1password",
Flyio = "flyio"
} }
export enum AWSRegion { export enum AWSRegion {

View File

@@ -56,6 +56,7 @@ import {
getDatabricksConnectionListItem, getDatabricksConnectionListItem,
validateDatabricksConnectionCredentials validateDatabricksConnectionCredentials
} from "./databricks"; } from "./databricks";
import { FlyioConnectionMethod, getFlyioConnectionListItem, validateFlyioConnectionCredentials } from "./flyio";
import { GcpConnectionMethod, getGcpConnectionListItem, validateGcpConnectionCredentials } from "./gcp"; import { GcpConnectionMethod, getGcpConnectionListItem, validateGcpConnectionCredentials } from "./gcp";
import { getGitHubConnectionListItem, GitHubConnectionMethod, validateGitHubConnectionCredentials } from "./github"; import { getGitHubConnectionListItem, GitHubConnectionMethod, validateGitHubConnectionCredentials } from "./github";
import { import {
@@ -121,7 +122,8 @@ export const listAppConnectionOptions = () => {
getTeamCityConnectionListItem(), getTeamCityConnectionListItem(),
getOCIConnectionListItem(), getOCIConnectionListItem(),
getOracleDBConnectionListItem(), getOracleDBConnectionListItem(),
getOnePassConnectionListItem() getOnePassConnectionListItem(),
getFlyioConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name)); ].sort((a, b) => a.name.localeCompare(b.name));
}; };
@@ -196,7 +198,8 @@ export const validateAppConnectionCredentials = async (
[AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.OCI]: validateOCIConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.OCI]: validateOCIConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.OracleDB]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.OracleDB]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.OnePass]: validateOnePassConnectionCredentials as TAppConnectionCredentialsValidator [AppConnection.OnePass]: validateOnePassConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator
}; };
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection); return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
@@ -238,6 +241,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case HCVaultConnectionMethod.AccessToken: case HCVaultConnectionMethod.AccessToken:
case TeamCityConnectionMethod.AccessToken: case TeamCityConnectionMethod.AccessToken:
case AzureDevOpsConnectionMethod.AccessToken: case AzureDevOpsConnectionMethod.AccessToken:
case FlyioConnectionMethod.AccessToken:
return "Access Token"; return "Access Token";
case Auth0ConnectionMethod.ClientCredentials: case Auth0ConnectionMethod.ClientCredentials:
return "Client Credentials"; return "Client Credentials";
@@ -299,7 +303,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.TeamCity]: platformManagedCredentialsNotSupported, [AppConnection.TeamCity]: platformManagedCredentialsNotSupported,
[AppConnection.OCI]: platformManagedCredentialsNotSupported, [AppConnection.OCI]: platformManagedCredentialsNotSupported,
[AppConnection.OracleDB]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform, [AppConnection.OracleDB]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
[AppConnection.OnePass]: platformManagedCredentialsNotSupported [AppConnection.OnePass]: platformManagedCredentialsNotSupported,
[AppConnection.Flyio]: platformManagedCredentialsNotSupported
}; };
export const enterpriseAppCheck = async ( export const enterpriseAppCheck = async (

View File

@@ -24,7 +24,8 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.TeamCity]: "TeamCity", [AppConnection.TeamCity]: "TeamCity",
[AppConnection.OCI]: "OCI", [AppConnection.OCI]: "OCI",
[AppConnection.OracleDB]: "OracleDB", [AppConnection.OracleDB]: "OracleDB",
[AppConnection.OnePass]: "1Password" [AppConnection.OnePass]: "1Password",
[AppConnection.Flyio]: "Fly.io"
}; };
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = { export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
@@ -51,5 +52,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.OCI]: AppConnectionPlanType.Enterprise, [AppConnection.OCI]: AppConnectionPlanType.Enterprise,
[AppConnection.OracleDB]: AppConnectionPlanType.Enterprise, [AppConnection.OracleDB]: AppConnectionPlanType.Enterprise,
[AppConnection.OnePass]: AppConnectionPlanType.Regular, [AppConnection.OnePass]: AppConnectionPlanType.Regular,
[AppConnection.MySql]: AppConnectionPlanType.Regular [AppConnection.MySql]: AppConnectionPlanType.Regular,
[AppConnection.Flyio]: AppConnectionPlanType.Regular
}; };

View File

@@ -49,6 +49,8 @@ import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
import { camundaConnectionService } from "./camunda/camunda-connection-service"; import { camundaConnectionService } from "./camunda/camunda-connection-service";
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks"; import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
import { databricksConnectionService } from "./databricks/databricks-connection-service"; import { databricksConnectionService } from "./databricks/databricks-connection-service";
import { ValidateFlyioConnectionCredentialsSchema } from "./flyio";
import { flyioConnectionService } from "./flyio/flyio-connection-service";
import { ValidateGcpConnectionCredentialsSchema } from "./gcp"; import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
import { gcpConnectionService } from "./gcp/gcp-connection-service"; import { gcpConnectionService } from "./gcp/gcp-connection-service";
import { ValidateGitHubConnectionCredentialsSchema } from "./github"; import { ValidateGitHubConnectionCredentialsSchema } from "./github";
@@ -104,7 +106,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.TeamCity]: ValidateTeamCityConnectionCredentialsSchema, [AppConnection.TeamCity]: ValidateTeamCityConnectionCredentialsSchema,
[AppConnection.OCI]: ValidateOCIConnectionCredentialsSchema, [AppConnection.OCI]: ValidateOCIConnectionCredentialsSchema,
[AppConnection.OracleDB]: ValidateOracleDBConnectionCredentialsSchema, [AppConnection.OracleDB]: ValidateOracleDBConnectionCredentialsSchema,
[AppConnection.OnePass]: ValidateOnePassConnectionCredentialsSchema [AppConnection.OnePass]: ValidateOnePassConnectionCredentialsSchema,
[AppConnection.Flyio]: ValidateFlyioConnectionCredentialsSchema
}; };
export const appConnectionServiceFactory = ({ export const appConnectionServiceFactory = ({
@@ -509,6 +512,7 @@ export const appConnectionServiceFactory = ({
windmill: windmillConnectionService(connectAppConnectionById), windmill: windmillConnectionService(connectAppConnectionById),
teamcity: teamcityConnectionService(connectAppConnectionById), teamcity: teamcityConnectionService(connectAppConnectionById),
oci: ociConnectionService(connectAppConnectionById, licenseService), oci: ociConnectionService(connectAppConnectionById, licenseService),
onepass: onePassConnectionService(connectAppConnectionById) onepass: onePassConnectionService(connectAppConnectionById),
flyio: flyioConnectionService(connectAppConnectionById)
}; };
}; };

View File

@@ -68,6 +68,12 @@ import {
TDatabricksConnectionInput, TDatabricksConnectionInput,
TValidateDatabricksConnectionCredentialsSchema TValidateDatabricksConnectionCredentialsSchema
} from "./databricks"; } from "./databricks";
import {
TFlyioConnection,
TFlyioConnectionConfig,
TFlyioConnectionInput,
TValidateFlyioConnectionCredentialsSchema
} from "./flyio";
import { import {
TGcpConnection, TGcpConnection,
TGcpConnectionConfig, TGcpConnectionConfig,
@@ -161,6 +167,7 @@ export type TAppConnection = { id: string } & (
| TOCIConnection | TOCIConnection
| TOracleDBConnection | TOracleDBConnection
| TOnePassConnection | TOnePassConnection
| TFlyioConnection
); );
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>; export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
@@ -192,6 +199,7 @@ export type TAppConnectionInput = { id: string } & (
| TOCIConnectionInput | TOCIConnectionInput
| TOracleDBConnectionInput | TOracleDBConnectionInput
| TOnePassConnectionInput | TOnePassConnectionInput
| TFlyioConnectionInput
); );
export type TSqlConnectionInput = export type TSqlConnectionInput =
@@ -230,7 +238,8 @@ export type TAppConnectionConfig =
| TLdapConnectionConfig | TLdapConnectionConfig
| TTeamCityConnectionConfig | TTeamCityConnectionConfig
| TOCIConnectionConfig | TOCIConnectionConfig
| TOnePassConnectionConfig; | TOnePassConnectionConfig
| TFlyioConnectionConfig;
export type TValidateAppConnectionCredentialsSchema = export type TValidateAppConnectionCredentialsSchema =
| TValidateAwsConnectionCredentialsSchema | TValidateAwsConnectionCredentialsSchema
@@ -256,7 +265,8 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateTeamCityConnectionCredentialsSchema | TValidateTeamCityConnectionCredentialsSchema
| TValidateOCIConnectionCredentialsSchema | TValidateOCIConnectionCredentialsSchema
| TValidateOracleDBConnectionCredentialsSchema | TValidateOracleDBConnectionCredentialsSchema
| TValidateOnePassConnectionCredentialsSchema; | TValidateOnePassConnectionCredentialsSchema
| TValidateFlyioConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = { export type TListAwsConnectionKmsKeys = {
connectionId: string; connectionId: string;

View File

@@ -0,0 +1,3 @@
export enum FlyioConnectionMethod {
AccessToken = "access-token"
}

View File

@@ -0,0 +1,65 @@
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 { 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 {
await request.post(
"https://api.fly.io/graphql",
{ query: "query { viewer { id email } }" },
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/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"
});
}
return config.credentials;
};
export const listFlyioApps = async (appConnection: TFlyioConnection) => {
const { accessToken } = appConnection.credentials;
const resp = await request.post<{ data: { apps: { nodes: TFlyioApp[] } } }>(
"https://api.fly.io/graphql",
{
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;
};

View File

@@ -0,0 +1,56 @@
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").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()
});

View File

@@ -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
};
};

View File

@@ -0,0 +1,28 @@
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;
};
// TODO(andrey): apps
export type TFlyioApp = {
id: string;
name: string;
};

View File

@@ -0,0 +1,4 @@
export * from "./flyio-connection-enums";
export * from "./flyio-connection-fns";
export * from "./flyio-connection-schemas";
export * from "./flyio-connection-types";

View File

@@ -19,6 +19,7 @@ import {
AzureKeyVaultConnectionMethod, AzureKeyVaultConnectionMethod,
CamundaConnectionMethod, CamundaConnectionMethod,
DatabricksConnectionMethod, DatabricksConnectionMethod,
FlyioConnectionMethod,
GcpConnectionMethod, GcpConnectionMethod,
GitHubConnectionMethod, GitHubConnectionMethod,
GitHubRadarConnectionMethod, GitHubRadarConnectionMethod,
@@ -78,7 +79,8 @@ export const APP_CONNECTION_MAP: Record<
[AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 }, [AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 },
[AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" }, [AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" },
[AppConnection.OCI]: { name: "OCI", image: "Oracle.png", enterprise: true }, [AppConnection.OCI]: { name: "OCI", image: "Oracle.png", enterprise: true },
[AppConnection.OnePass]: { name: "1Password", image: "1Password.png" } [AppConnection.OnePass]: { name: "1Password", image: "1Password.png" },
[AppConnection.Flyio]: { name: "Fly.io", image: "Flyio.svg" }
}; };
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => { export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
@@ -117,6 +119,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
case TeamCityConnectionMethod.AccessToken: case TeamCityConnectionMethod.AccessToken:
case AzureDevOpsConnectionMethod.AccessToken: case AzureDevOpsConnectionMethod.AccessToken:
case WindmillConnectionMethod.AccessToken: case WindmillConnectionMethod.AccessToken:
case FlyioConnectionMethod.AccessToken:
return { name: "Access Token", icon: faKey }; return { name: "Access Token", icon: faKey };
case Auth0ConnectionMethod.ClientCredentials: case Auth0ConnectionMethod.ClientCredentials:
return { name: "Client Credentials", icon: faServer }; return { name: "Client Credentials", icon: faServer };

View File

@@ -22,5 +22,6 @@ export enum AppConnection {
LDAP = "ldap", LDAP = "ldap",
TeamCity = "teamcity", TeamCity = "teamcity",
OCI = "oci", OCI = "oci",
OnePass = "1password" OnePass = "1password",
Flyio = "flyio"
} }

View File

@@ -0,0 +1,2 @@
export * from "./queries";
export * from "./types";

View File

@@ -0,0 +1,36 @@
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { appConnectionKeys } from "../queries";
import { TFlyioApp } from "./types";
const flyioConnectionKeys = {
all: [...appConnectionKeys.all, "flyio"] as const,
listApps: (connectionId: string) => [...flyioConnectionKeys.all, "apps", connectionId] as const
};
export const useFlyioConnectionListApps = (
connectionId: string,
options?: Omit<
UseQueryOptions<
TFlyioApp[],
unknown,
TFlyioApp[],
ReturnType<typeof flyioConnectionKeys.listApps>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: flyioConnectionKeys.listApps(connectionId),
queryFn: async () => {
const { data } = await apiRequest.get<TFlyioApp[]>(
`/api/v1/app-connections/flyio/${connectionId}/apps`
);
return data;
},
...options
});
};

View File

@@ -0,0 +1,5 @@
// TODO(andrey): apps
export type TFlyioApp = {
id: string;
name: string;
};

View File

@@ -110,6 +110,10 @@ export type TOnePassConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.OnePass; app: AppConnection.OnePass;
}; };
export type TFlyioConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.Flyio;
};
export type TAppConnectionOption = export type TAppConnectionOption =
| TAwsConnectionOption | TAwsConnectionOption
| TGitHubConnectionOption | TGitHubConnectionOption
@@ -132,7 +136,8 @@ export type TAppConnectionOption =
| THCVaultConnectionOption | THCVaultConnectionOption
| TTeamCityConnectionOption | TTeamCityConnectionOption
| TOCIConnectionOption | TOCIConnectionOption
| TOnePassConnectionOption; | TOnePassConnectionOption
| TFlyioConnectionOption;
export type TAppConnectionOptionMap = { export type TAppConnectionOptionMap = {
[AppConnection.AWS]: TAwsConnectionOption; [AppConnection.AWS]: TAwsConnectionOption;
@@ -159,4 +164,5 @@ export type TAppConnectionOptionMap = {
[AppConnection.TeamCity]: TTeamCityConnectionOption; [AppConnection.TeamCity]: TTeamCityConnectionOption;
[AppConnection.OCI]: TOCIConnectionOption; [AppConnection.OCI]: TOCIConnectionOption;
[AppConnection.OnePass]: TOnePassConnectionOption; [AppConnection.OnePass]: TOnePassConnectionOption;
[AppConnection.Flyio]: TFlyioConnectionOption;
}; };

View File

@@ -0,0 +1,13 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
export enum FlyioConnectionMethod {
AccessToken = "access-token"
}
export type TFlyioConnection = TRootAppConnection & { app: AppConnection.Flyio } & {
method: FlyioConnectionMethod.AccessToken;
credentials: {
accessToken: string;
};
};

View File

@@ -9,6 +9,7 @@ import { TAzureDevOpsConnection } from "./azure-devops-connection";
import { TAzureKeyVaultConnection } from "./azure-key-vault-connection"; import { TAzureKeyVaultConnection } from "./azure-key-vault-connection";
import { TCamundaConnection } from "./camunda-connection"; import { TCamundaConnection } from "./camunda-connection";
import { TDatabricksConnection } from "./databricks-connection"; import { TDatabricksConnection } from "./databricks-connection";
import { TFlyioConnection } from "./flyio-connection";
import { TGcpConnection } from "./gcp-connection"; import { TGcpConnection } from "./gcp-connection";
import { TGitHubConnection } from "./github-connection"; import { TGitHubConnection } from "./github-connection";
import { TGitHubRadarConnection } from "./github-radar-connection"; import { TGitHubRadarConnection } from "./github-radar-connection";
@@ -34,6 +35,7 @@ export * from "./azure-devops-connection";
export * from "./azure-key-vault-connection"; export * from "./azure-key-vault-connection";
export * from "./camunda-connection"; export * from "./camunda-connection";
export * from "./databricks-connection"; export * from "./databricks-connection";
export * from "./flyio-connection";
export * from "./gcp-connection"; export * from "./gcp-connection";
export * from "./github-connection"; export * from "./github-connection";
export * from "./github-radar-connection"; export * from "./github-radar-connection";
@@ -74,7 +76,8 @@ export type TAppConnection =
| TLdapConnection | TLdapConnection
| TTeamCityConnection | TTeamCityConnection
| TOCIConnection | TOCIConnection
| TOnePassConnection; | TOnePassConnection
| TFlyioConnection;
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">; export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
@@ -126,4 +129,5 @@ export type TAppConnectionMap = {
[AppConnection.TeamCity]: TTeamCityConnection; [AppConnection.TeamCity]: TTeamCityConnection;
[AppConnection.OCI]: TOCIConnection; [AppConnection.OCI]: TOCIConnection;
[AppConnection.OnePass]: TOnePassConnection; [AppConnection.OnePass]: TOnePassConnection;
[AppConnection.Flyio]: TFlyioConnection;
}; };

View File

@@ -18,6 +18,7 @@ import { AzureDevOpsConnectionForm } from "./AzureDevOpsConnectionForm";
import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm"; import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm";
import { CamundaConnectionForm } from "./CamundaConnectionForm"; import { CamundaConnectionForm } from "./CamundaConnectionForm";
import { DatabricksConnectionForm } from "./DatabricksConnectionForm"; import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
import { FlyioConnectionForm } from "./FlyioConnectionForm";
import { GcpConnectionForm } from "./GcpConnectionForm"; import { GcpConnectionForm } from "./GcpConnectionForm";
import { GitHubConnectionForm } from "./GitHubConnectionForm"; import { GitHubConnectionForm } from "./GitHubConnectionForm";
import { GitHubRadarConnectionForm } from "./GitHubRadarConnectionForm"; import { GitHubRadarConnectionForm } from "./GitHubRadarConnectionForm";
@@ -119,6 +120,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
return <OCIConnectionForm onSubmit={onSubmit} />; return <OCIConnectionForm onSubmit={onSubmit} />;
case AppConnection.OnePass: case AppConnection.OnePass:
return <OnePassConnectionForm onSubmit={onSubmit} />; return <OnePassConnectionForm onSubmit={onSubmit} />;
case AppConnection.Flyio:
return <FlyioConnectionForm onSubmit={onSubmit} />;
default: default:
throw new Error(`Unhandled App ${app}`); throw new Error(`Unhandled App ${app}`);
} }
@@ -203,6 +206,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
return <OCIConnectionForm onSubmit={onSubmit} appConnection={appConnection} />; return <OCIConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.OnePass: case AppConnection.OnePass:
return <OnePassConnectionForm onSubmit={onSubmit} appConnection={appConnection} />; return <OnePassConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Flyio:
return <FlyioConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
default: default:
throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`); throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`);
} }

View File

@@ -0,0 +1,132 @@
import { Controller, FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
Button,
FormControl,
ModalClose,
SecretInput,
Select,
SelectItem
} from "@app/components/v2";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
import { FlyioConnectionMethod, TFlyioConnection } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import {
genericAppConnectionFieldsSchema,
GenericAppConnectionsFields
} from "./GenericAppConnectionFields";
type Props = {
appConnection?: TFlyioConnection;
onSubmit: (formData: FormData) => void;
};
const rootSchema = genericAppConnectionFieldsSchema.extend({
app: z.literal(AppConnection.Flyio)
});
const formSchema = z.discriminatedUnion("method", [
rootSchema.extend({
method: z.literal(FlyioConnectionMethod.AccessToken),
credentials: z.object({
accessToken: z.string().trim().min(1, "Access Token required")
})
})
]);
type FormData = z.infer<typeof formSchema>;
export const FlyioConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isUpdate = Boolean(appConnection);
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: appConnection ?? {
app: AppConnection.Flyio,
method: FlyioConnectionMethod.AccessToken
}
});
const {
handleSubmit,
control,
formState: { isSubmitting, isDirty }
} = form;
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
{!isUpdate && <GenericAppConnectionsFields />}
<Controller
name="method"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
tooltipText={`The method you would like to use to connect with ${
APP_CONNECTION_MAP[AppConnection.Flyio].name
}. This field cannot be changed after creation.`}
errorText={error?.message}
isError={Boolean(error?.message)}
label="Method"
>
<Select
isDisabled={isUpdate}
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
position="popper"
dropdownContainerClassName="max-w-none"
>
{Object.values(FlyioConnectionMethod).map((method) => {
return (
<SelectItem value={method} key={method}>
{getAppConnectionMethodDetails(method).name}{" "}
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
<Controller
name="credentials.accessToken"
control={control}
shouldUnregister
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Access Token"
>
<SecretInput
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
colorSchema="secondary"
isLoading={isSubmitting}
isDisabled={isSubmitting || !isDirty}
>
{isUpdate ? "Update Credentials" : "Connect to Fly.io"}
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</form>
</FormProvider>
);
};