feat: Digital Ocean App connection and App Platform secret sync (#4203)
* fix: save wip * feat: final impl * feat: docs * Update backend/src/services/app-connection/digital-ocean/digital-ocean-connection-service.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * chore: remove empty conflict files * Update backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update frontend/src/components/secret-syncs/forms/schemas/digital-ocean-app-platform-sync-destination-schema.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update frontend/src/components/secret-syncs/forms/schemas/digital-ocean-app-platform-sync-destination-schema.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/DigitalOceanAppPlatformSyncFields.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update backend/src/services/secret-sync/digital-ocean-app-platform/digital-ocean-app-platform-sync-schemas.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix: lint * fix: api client * fix: lint and types * fix: typecheck lint * fix: docs * fix: docs * fix: linting --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
@@ -430,4 +430,3 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2290,6 +2290,9 @@ export const AppConnections = {
|
|||||||
accessKey: "The Key used to access Supabase.",
|
accessKey: "The Key used to access Supabase.",
|
||||||
instanceUrl: "The URL used to access Supabase."
|
instanceUrl: "The URL used to access Supabase."
|
||||||
},
|
},
|
||||||
|
DIGITAL_OCEAN_APP_PLATFORM: {
|
||||||
|
apiToken: "The API token used to authenticate with Digital Ocean App Platform."
|
||||||
|
},
|
||||||
OKTA: {
|
OKTA: {
|
||||||
instanceUrl: "The URL used to access your Okta organization.",
|
instanceUrl: "The URL used to access your Okta organization.",
|
||||||
apiToken: "The API token used to authenticate with Okta."
|
apiToken: "The API token used to authenticate with Okta."
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ import {
|
|||||||
DatabricksConnectionListItemSchema,
|
DatabricksConnectionListItemSchema,
|
||||||
SanitizedDatabricksConnectionSchema
|
SanitizedDatabricksConnectionSchema
|
||||||
} from "@app/services/app-connection/databricks";
|
} from "@app/services/app-connection/databricks";
|
||||||
|
import {
|
||||||
|
DigitalOceanConnectionListItemSchema,
|
||||||
|
SanitizedDigitalOceanConnectionSchema
|
||||||
|
} from "@app/services/app-connection/digital-ocean";
|
||||||
import { FlyioConnectionListItemSchema, SanitizedFlyioConnectionSchema } from "@app/services/app-connection/flyio";
|
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";
|
||||||
@@ -140,6 +144,7 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedRailwayConnectionSchema.options,
|
...SanitizedRailwayConnectionSchema.options,
|
||||||
...SanitizedChecklyConnectionSchema.options,
|
...SanitizedChecklyConnectionSchema.options,
|
||||||
...SanitizedSupabaseConnectionSchema.options,
|
...SanitizedSupabaseConnectionSchema.options,
|
||||||
|
...SanitizedDigitalOceanConnectionSchema.options,
|
||||||
...SanitizedOktaConnectionSchema.options
|
...SanitizedOktaConnectionSchema.options
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -178,6 +183,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
RailwayConnectionListItemSchema,
|
RailwayConnectionListItemSchema,
|
||||||
ChecklyConnectionListItemSchema,
|
ChecklyConnectionListItemSchema,
|
||||||
SupabaseConnectionListItemSchema,
|
SupabaseConnectionListItemSchema,
|
||||||
|
DigitalOceanConnectionListItemSchema,
|
||||||
OktaConnectionListItemSchema
|
OktaConnectionListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
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 {
|
||||||
|
CreateDigitalOceanConnectionSchema,
|
||||||
|
SanitizedDigitalOceanConnectionSchema,
|
||||||
|
UpdateDigitalOceanConnectionSchema
|
||||||
|
} from "@app/services/app-connection/digital-ocean";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerDigitalOceanConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.DigitalOcean,
|
||||||
|
server,
|
||||||
|
createSchema: CreateDigitalOceanConnectionSchema,
|
||||||
|
updateSchema: UpdateDigitalOceanConnectionSchema,
|
||||||
|
sanitizedResponseSchema: SanitizedDigitalOceanConnectionSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
// The below endpoints are not exposed and for Infisical App use
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/apps`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
apps: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
spec: z.object({
|
||||||
|
name: z.string()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const apps = await server.services.appConnection.digitalOcean.listApps(connectionId, req.permission);
|
||||||
|
|
||||||
|
return { apps };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -14,6 +14,7 @@ import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
|||||||
import { registerChecklyConnectionRouter } from "./checkly-connection-router";
|
import { registerChecklyConnectionRouter } from "./checkly-connection-router";
|
||||||
import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router";
|
import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router";
|
||||||
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
||||||
|
import { registerDigitalOceanConnectionRouter } from "./digital-ocean-connection-router";
|
||||||
import { registerFlyioConnectionRouter } from "./flyio-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";
|
||||||
@@ -74,5 +75,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.Railway]: registerRailwayConnectionRouter,
|
[AppConnection.Railway]: registerRailwayConnectionRouter,
|
||||||
[AppConnection.Checkly]: registerChecklyConnectionRouter,
|
[AppConnection.Checkly]: registerChecklyConnectionRouter,
|
||||||
[AppConnection.Supabase]: registerSupabaseConnectionRouter,
|
[AppConnection.Supabase]: registerSupabaseConnectionRouter,
|
||||||
|
[AppConnection.DigitalOcean]: registerDigitalOceanConnectionRouter,
|
||||||
[AppConnection.Okta]: registerOktaConnectionRouter
|
[AppConnection.Okta]: registerOktaConnectionRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
CreateDigitalOceanAppPlatformSyncSchema,
|
||||||
|
DigitalOceanAppPlatformSyncSchema,
|
||||||
|
UpdateDigitalOceanAppPlatformSyncSchema
|
||||||
|
} from "@app/services/secret-sync/digital-ocean-app-platform";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerDigitalOceanAppPlatformSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.DigitalOceanAppPlatform,
|
||||||
|
server,
|
||||||
|
responseSchema: DigitalOceanAppPlatformSyncSchema,
|
||||||
|
createSchema: CreateDigitalOceanAppPlatformSyncSchema,
|
||||||
|
updateSchema: UpdateDigitalOceanAppPlatformSyncSchema
|
||||||
|
});
|
||||||
@@ -13,6 +13,7 @@ import { registerChecklySyncRouter } from "./checkly-sync-router";
|
|||||||
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
|
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
|
||||||
import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router";
|
import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router";
|
||||||
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
||||||
|
import { registerDigitalOceanAppPlatformSyncRouter } from "./digital-ocean-app-platform-sync-router";
|
||||||
import { registerFlyioSyncRouter } from "./flyio-sync-router";
|
import { registerFlyioSyncRouter } from "./flyio-sync-router";
|
||||||
import { registerGcpSyncRouter } from "./gcp-sync-router";
|
import { registerGcpSyncRouter } from "./gcp-sync-router";
|
||||||
import { registerGitHubSyncRouter } from "./github-sync-router";
|
import { registerGitHubSyncRouter } from "./github-sync-router";
|
||||||
@@ -59,5 +60,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
|||||||
[SecretSync.Zabbix]: registerZabbixSyncRouter,
|
[SecretSync.Zabbix]: registerZabbixSyncRouter,
|
||||||
[SecretSync.Railway]: registerRailwaySyncRouter,
|
[SecretSync.Railway]: registerRailwaySyncRouter,
|
||||||
[SecretSync.Checkly]: registerChecklySyncRouter,
|
[SecretSync.Checkly]: registerChecklySyncRouter,
|
||||||
|
[SecretSync.DigitalOceanAppPlatform]: registerDigitalOceanAppPlatformSyncRouter,
|
||||||
[SecretSync.Bitbucket]: registerBitbucketSyncRouter
|
[SecretSync.Bitbucket]: registerBitbucketSyncRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ import {
|
|||||||
CloudflareWorkersSyncSchema
|
CloudflareWorkersSyncSchema
|
||||||
} from "@app/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas";
|
} from "@app/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas";
|
||||||
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
|
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
|
||||||
|
import {
|
||||||
|
DigitalOceanAppPlatformSyncListItemSchema,
|
||||||
|
DigitalOceanAppPlatformSyncSchema
|
||||||
|
} from "@app/services/secret-sync/digital-ocean-app-platform";
|
||||||
import { FlyioSyncListItemSchema, FlyioSyncSchema } from "@app/services/secret-sync/flyio";
|
import { FlyioSyncListItemSchema, FlyioSyncSchema } from "@app/services/secret-sync/flyio";
|
||||||
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
|
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
|
||||||
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
||||||
@@ -77,6 +81,7 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
|||||||
ZabbixSyncSchema,
|
ZabbixSyncSchema,
|
||||||
RailwaySyncSchema,
|
RailwaySyncSchema,
|
||||||
ChecklySyncSchema,
|
ChecklySyncSchema,
|
||||||
|
DigitalOceanAppPlatformSyncSchema,
|
||||||
BitbucketSyncSchema
|
BitbucketSyncSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -104,6 +109,7 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
|||||||
GitLabSyncListItemSchema,
|
GitLabSyncListItemSchema,
|
||||||
CloudflarePagesSyncListItemSchema,
|
CloudflarePagesSyncListItemSchema,
|
||||||
CloudflareWorkersSyncListItemSchema,
|
CloudflareWorkersSyncListItemSchema,
|
||||||
|
DigitalOceanAppPlatformSyncListItemSchema,
|
||||||
ZabbixSyncListItemSchema,
|
ZabbixSyncListItemSchema,
|
||||||
RailwaySyncListItemSchema,
|
RailwaySyncListItemSchema,
|
||||||
ChecklySyncListItemSchema,
|
ChecklySyncListItemSchema,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export enum AppConnection {
|
|||||||
Bitbucket = "bitbucket",
|
Bitbucket = "bitbucket",
|
||||||
Checkly = "checkly",
|
Checkly = "checkly",
|
||||||
Supabase = "supabase",
|
Supabase = "supabase",
|
||||||
|
DigitalOcean = "digital-ocean",
|
||||||
Okta = "okta"
|
Okta = "okta"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ import {
|
|||||||
getDatabricksConnectionListItem,
|
getDatabricksConnectionListItem,
|
||||||
validateDatabricksConnectionCredentials
|
validateDatabricksConnectionCredentials
|
||||||
} from "./databricks";
|
} from "./databricks";
|
||||||
|
import {
|
||||||
|
DigitalOceanConnectionMethod,
|
||||||
|
getDigitalOceanConnectionListItem,
|
||||||
|
validateDigitalOceanConnectionCredentials
|
||||||
|
} from "./digital-ocean";
|
||||||
import { FlyioConnectionMethod, getFlyioConnectionListItem, validateFlyioConnectionCredentials } from "./flyio";
|
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";
|
||||||
@@ -157,6 +162,7 @@ export const listAppConnectionOptions = () => {
|
|||||||
getBitbucketConnectionListItem(),
|
getBitbucketConnectionListItem(),
|
||||||
getChecklyConnectionListItem(),
|
getChecklyConnectionListItem(),
|
||||||
getSupabaseConnectionListItem(),
|
getSupabaseConnectionListItem(),
|
||||||
|
getDigitalOceanConnectionListItem(),
|
||||||
getOktaConnectionListItem()
|
getOktaConnectionListItem()
|
||||||
].sort((a, b) => a.name.localeCompare(b.name));
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
@@ -244,6 +250,7 @@ export const validateAppConnectionCredentials = async (
|
|||||||
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Supabase]: validateSupabaseConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Supabase]: validateSupabaseConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.DigitalOcean]: validateDigitalOceanConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Okta]: validateOktaConnectionCredentials as TAppConnectionCredentialsValidator
|
[AppConnection.Okta]: validateOktaConnectionCredentials as TAppConnectionCredentialsValidator
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -283,6 +290,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
case CloudflareConnectionMethod.APIToken:
|
case CloudflareConnectionMethod.APIToken:
|
||||||
case BitbucketConnectionMethod.ApiToken:
|
case BitbucketConnectionMethod.ApiToken:
|
||||||
case ZabbixConnectionMethod.ApiToken:
|
case ZabbixConnectionMethod.ApiToken:
|
||||||
|
case DigitalOceanConnectionMethod.ApiToken:
|
||||||
case OktaConnectionMethod.ApiToken:
|
case OktaConnectionMethod.ApiToken:
|
||||||
return "API Token";
|
return "API Token";
|
||||||
case PostgresConnectionMethod.UsernameAndPassword:
|
case PostgresConnectionMethod.UsernameAndPassword:
|
||||||
@@ -372,6 +380,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
|||||||
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported,
|
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Checkly]: platformManagedCredentialsNotSupported,
|
[AppConnection.Checkly]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Supabase]: platformManagedCredentialsNotSupported,
|
[AppConnection.Supabase]: platformManagedCredentialsNotSupported,
|
||||||
|
[AppConnection.DigitalOcean]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Okta]: platformManagedCredentialsNotSupported
|
[AppConnection.Okta]: platformManagedCredentialsNotSupported
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.Bitbucket]: "Bitbucket",
|
[AppConnection.Bitbucket]: "Bitbucket",
|
||||||
[AppConnection.Checkly]: "Checkly",
|
[AppConnection.Checkly]: "Checkly",
|
||||||
[AppConnection.Supabase]: "Supabase",
|
[AppConnection.Supabase]: "Supabase",
|
||||||
|
[AppConnection.DigitalOcean]: "DigitalOcean App Platform",
|
||||||
[AppConnection.Okta]: "Okta"
|
[AppConnection.Okta]: "Okta"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,5 +74,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
|
|||||||
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular,
|
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Checkly]: AppConnectionPlanType.Regular,
|
[AppConnection.Checkly]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Supabase]: AppConnectionPlanType.Regular,
|
[AppConnection.Supabase]: AppConnectionPlanType.Regular,
|
||||||
|
[AppConnection.DigitalOcean]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Okta]: AppConnectionPlanType.Regular
|
[AppConnection.Okta]: AppConnectionPlanType.Regular
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ import { ValidateCloudflareConnectionCredentialsSchema } from "./cloudflare/clou
|
|||||||
import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service";
|
import { cloudflareConnectionService } from "./cloudflare/cloudflare-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 { ValidateDigitalOceanConnectionCredentialsSchema } from "./digital-ocean";
|
||||||
|
import { digitalOceanAppPlatformConnectionService } from "./digital-ocean/digital-ocean-connection-service";
|
||||||
import { ValidateFlyioConnectionCredentialsSchema } from "./flyio";
|
import { ValidateFlyioConnectionCredentialsSchema } from "./flyio";
|
||||||
import { flyioConnectionService } from "./flyio/flyio-connection-service";
|
import { flyioConnectionService } from "./flyio/flyio-connection-service";
|
||||||
import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
|
import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
|
||||||
@@ -145,6 +147,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema,
|
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema,
|
||||||
[AppConnection.Checkly]: ValidateChecklyConnectionCredentialsSchema,
|
[AppConnection.Checkly]: ValidateChecklyConnectionCredentialsSchema,
|
||||||
[AppConnection.Supabase]: ValidateSupabaseConnectionCredentialsSchema,
|
[AppConnection.Supabase]: ValidateSupabaseConnectionCredentialsSchema,
|
||||||
|
[AppConnection.DigitalOcean]: ValidateDigitalOceanConnectionCredentialsSchema,
|
||||||
[AppConnection.Okta]: ValidateOktaConnectionCredentialsSchema
|
[AppConnection.Okta]: ValidateOktaConnectionCredentialsSchema
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -607,6 +610,7 @@ export const appConnectionServiceFactory = ({
|
|||||||
bitbucket: bitbucketConnectionService(connectAppConnectionById),
|
bitbucket: bitbucketConnectionService(connectAppConnectionById),
|
||||||
checkly: checklyConnectionService(connectAppConnectionById),
|
checkly: checklyConnectionService(connectAppConnectionById),
|
||||||
supabase: supabaseConnectionService(connectAppConnectionById),
|
supabase: supabaseConnectionService(connectAppConnectionById),
|
||||||
|
digitalOcean: digitalOceanAppPlatformConnectionService(connectAppConnectionById),
|
||||||
okta: oktaConnectionService(connectAppConnectionById)
|
okta: oktaConnectionService(connectAppConnectionById)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -87,6 +87,12 @@ import {
|
|||||||
TDatabricksConnectionInput,
|
TDatabricksConnectionInput,
|
||||||
TValidateDatabricksConnectionCredentialsSchema
|
TValidateDatabricksConnectionCredentialsSchema
|
||||||
} from "./databricks";
|
} from "./databricks";
|
||||||
|
import {
|
||||||
|
TDigitalOceanConnection,
|
||||||
|
TDigitalOceanConnectionConfig,
|
||||||
|
TDigitalOceanConnectionInput,
|
||||||
|
TValidateDigitalOceanCredentialsSchema
|
||||||
|
} from "./digital-ocean";
|
||||||
import {
|
import {
|
||||||
TFlyioConnection,
|
TFlyioConnection,
|
||||||
TFlyioConnectionConfig,
|
TFlyioConnectionConfig,
|
||||||
@@ -238,6 +244,7 @@ export type TAppConnection = { id: string } & (
|
|||||||
| TRailwayConnection
|
| TRailwayConnection
|
||||||
| TChecklyConnection
|
| TChecklyConnection
|
||||||
| TSupabaseConnection
|
| TSupabaseConnection
|
||||||
|
| TDigitalOceanConnection
|
||||||
| TOktaConnection
|
| TOktaConnection
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -280,6 +287,7 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TRailwayConnectionInput
|
| TRailwayConnectionInput
|
||||||
| TChecklyConnectionInput
|
| TChecklyConnectionInput
|
||||||
| TSupabaseConnectionInput
|
| TSupabaseConnectionInput
|
||||||
|
| TDigitalOceanConnectionInput
|
||||||
| TOktaConnectionInput
|
| TOktaConnectionInput
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -330,6 +338,7 @@ export type TAppConnectionConfig =
|
|||||||
| TRailwayConnectionConfig
|
| TRailwayConnectionConfig
|
||||||
| TChecklyConnectionConfig
|
| TChecklyConnectionConfig
|
||||||
| TSupabaseConnectionConfig
|
| TSupabaseConnectionConfig
|
||||||
|
| TDigitalOceanConnectionConfig
|
||||||
| TOktaConnectionConfig;
|
| TOktaConnectionConfig;
|
||||||
|
|
||||||
export type TValidateAppConnectionCredentialsSchema =
|
export type TValidateAppConnectionCredentialsSchema =
|
||||||
@@ -367,6 +376,7 @@ export type TValidateAppConnectionCredentialsSchema =
|
|||||||
| TValidateRailwayConnectionCredentialsSchema
|
| TValidateRailwayConnectionCredentialsSchema
|
||||||
| TValidateChecklyConnectionCredentialsSchema
|
| TValidateChecklyConnectionCredentialsSchema
|
||||||
| TValidateSupabaseConnectionCredentialsSchema
|
| TValidateSupabaseConnectionCredentialsSchema
|
||||||
|
| TValidateDigitalOceanCredentialsSchema
|
||||||
| TValidateOktaConnectionCredentialsSchema;
|
| TValidateOktaConnectionCredentialsSchema;
|
||||||
|
|
||||||
export type TListAwsConnectionKmsKeys = {
|
export type TListAwsConnectionKmsKeys = {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export enum DigitalOceanConnectionMethod {
|
||||||
|
ApiToken = "api-token"
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import { DigitalOceanConnectionMethod } from "./digital-ocean-connection-constants";
|
||||||
|
import { DigitalOceanAppPlatformPublicAPI } from "./digital-ocean-connection-public-client";
|
||||||
|
import { DigitalOceanConnectionListItemSchema } from "./digital-ocean-connection-schemas";
|
||||||
|
import { TDigitalOceanConnectionConfig } from "./digital-ocean-connection-types";
|
||||||
|
|
||||||
|
export const getDigitalOceanConnectionListItem = () => {
|
||||||
|
return {
|
||||||
|
name: "Digital Ocean" as z.infer<typeof DigitalOceanConnectionListItemSchema>["name"],
|
||||||
|
app: AppConnection.DigitalOcean as const,
|
||||||
|
methods: Object.values(DigitalOceanConnectionMethod)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateDigitalOceanConnectionCredentials = async (config: TDigitalOceanConnectionConfig) => {
|
||||||
|
try {
|
||||||
|
await DigitalOceanAppPlatformPublicAPI.healthcheck(config);
|
||||||
|
} 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;
|
||||||
|
};
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
/* eslint-disable class-methods-use-this */
|
||||||
|
import { AxiosInstance } from "axios";
|
||||||
|
|
||||||
|
import { createRequestClient } from "@app/lib/config/request";
|
||||||
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
|
|
||||||
|
import { DigitalOceanConnectionMethod } from "./digital-ocean-connection-constants";
|
||||||
|
import {
|
||||||
|
TDigitalOceanApp,
|
||||||
|
TDigitalOceanConnectionConfig,
|
||||||
|
TDigitalOceanVariable
|
||||||
|
} from "./digital-ocean-connection-types";
|
||||||
|
|
||||||
|
class DigitalOceanAppPlatformPublicClient {
|
||||||
|
private readonly client: AxiosInstance;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.client = createRequestClient({
|
||||||
|
baseURL: `${IntegrationUrls.DIGITAL_OCEAN_API_URL}/v2`,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async healthcheck(connection: TDigitalOceanConnectionConfig) {
|
||||||
|
switch (connection.method) {
|
||||||
|
case DigitalOceanConnectionMethod.ApiToken:
|
||||||
|
await this.getApps(connection);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported connection method`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getApps(connection: TDigitalOceanConnectionConfig) {
|
||||||
|
const response = await this.client.get<{ apps: TDigitalOceanApp[] }>(`/apps`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${connection.credentials.apiToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data.apps;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getApp(connection: TDigitalOceanConnectionConfig, appId: string) {
|
||||||
|
const response = await this.client.get<{ app: TDigitalOceanApp }>(`/apps/${appId}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${connection.credentials.apiToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data.app;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVariables(connection: TDigitalOceanConnectionConfig, appId: string): Promise<TDigitalOceanVariable[]> {
|
||||||
|
const app = await this.getApp(connection, appId);
|
||||||
|
return app.spec.envs || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async putVariables(connection: TDigitalOceanConnectionConfig, appId: string, ...input: TDigitalOceanVariable[]) {
|
||||||
|
const response = await this.getApp(connection, appId);
|
||||||
|
|
||||||
|
return this.client.put(
|
||||||
|
`/apps/${appId}`,
|
||||||
|
{
|
||||||
|
spec: {
|
||||||
|
...response.spec,
|
||||||
|
envs: input
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${connection.credentials.apiToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteVariables(connection: TDigitalOceanConnectionConfig, appId: string, ...input: TDigitalOceanVariable[]) {
|
||||||
|
const response = await this.getApp(connection, appId);
|
||||||
|
const existing = response.spec.envs || [];
|
||||||
|
|
||||||
|
const variables = existing.filter((v) => input.find((i) => i.key === v.key));
|
||||||
|
|
||||||
|
return this.client.put(
|
||||||
|
`/apps/${appId}`,
|
||||||
|
{
|
||||||
|
spec: {
|
||||||
|
...response.spec,
|
||||||
|
envs: variables
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${connection.credentials.apiToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DigitalOceanAppPlatformPublicAPI = new DigitalOceanAppPlatformPublicClient();
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
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 { DigitalOceanConnectionMethod } from "./digital-ocean-connection-constants";
|
||||||
|
|
||||||
|
export const DigitalOceanConnectionMethodSchema = z
|
||||||
|
.nativeEnum(DigitalOceanConnectionMethod)
|
||||||
|
.describe(AppConnections.CREATE(AppConnection.DigitalOcean).method);
|
||||||
|
|
||||||
|
export const DigitalOceanConnectionAccessTokenCredentialsSchema = z.object({
|
||||||
|
apiToken: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "API Token required")
|
||||||
|
.max(255)
|
||||||
|
.describe(AppConnections.CREDENTIALS.DIGITAL_OCEAN_APP_PLATFORM.apiToken)
|
||||||
|
});
|
||||||
|
|
||||||
|
const BaseDigitalOceanConnectionSchema = BaseAppConnectionSchema.extend({
|
||||||
|
app: z.literal(AppConnection.DigitalOcean)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DigitalOceanConnectionSchema = BaseDigitalOceanConnectionSchema.extend({
|
||||||
|
method: DigitalOceanConnectionMethodSchema,
|
||||||
|
credentials: DigitalOceanConnectionAccessTokenCredentialsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SanitizedDigitalOceanConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseDigitalOceanConnectionSchema.extend({
|
||||||
|
method: DigitalOceanConnectionMethodSchema,
|
||||||
|
credentials: DigitalOceanConnectionAccessTokenCredentialsSchema.pick({})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ValidateDigitalOceanConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: DigitalOceanConnectionMethodSchema,
|
||||||
|
credentials: DigitalOceanConnectionAccessTokenCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.DigitalOcean).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateDigitalOceanConnectionSchema = ValidateDigitalOceanConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.DigitalOcean)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateDigitalOceanConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: DigitalOceanConnectionAccessTokenCredentialsSchema.optional().describe(
|
||||||
|
AppConnections.UPDATE(AppConnection.DigitalOcean).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.DigitalOcean));
|
||||||
|
|
||||||
|
export const DigitalOceanConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("Digital Ocean"),
|
||||||
|
app: z.literal(AppConnection.DigitalOcean),
|
||||||
|
methods: z.nativeEnum(DigitalOceanConnectionMethod).array()
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import { DigitalOceanAppPlatformPublicAPI } from "./digital-ocean-connection-public-client";
|
||||||
|
import { TDigitalOceanConnection } from "./digital-ocean-connection-types";
|
||||||
|
|
||||||
|
type TGetAppConnectionFunc = (
|
||||||
|
app: AppConnection,
|
||||||
|
connectionId: string,
|
||||||
|
actor: OrgServiceActor
|
||||||
|
) => Promise<TDigitalOceanConnection>;
|
||||||
|
|
||||||
|
export const digitalOceanAppPlatformConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||||
|
const listApps = async (connectionId: string, actor: OrgServiceActor) => {
|
||||||
|
const connection = await getAppConnection(AppConnection.DigitalOcean, connectionId, actor);
|
||||||
|
try {
|
||||||
|
const apps = await DigitalOceanAppPlatformPublicAPI.getApps(connection);
|
||||||
|
return apps;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Failed to list apps on Digital Ocean");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
listApps
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateDigitalOceanConnectionSchema,
|
||||||
|
DigitalOceanConnectionSchema,
|
||||||
|
ValidateDigitalOceanConnectionCredentialsSchema
|
||||||
|
} from "./digital-ocean-connection-schemas";
|
||||||
|
|
||||||
|
export type TDigitalOceanConnection = z.infer<typeof DigitalOceanConnectionSchema>;
|
||||||
|
|
||||||
|
export type TDigitalOceanConnectionInput = z.infer<typeof CreateDigitalOceanConnectionSchema> & {
|
||||||
|
app: AppConnection.DigitalOcean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateDigitalOceanCredentialsSchema = typeof ValidateDigitalOceanConnectionCredentialsSchema;
|
||||||
|
|
||||||
|
export type TDigitalOceanConnectionConfig = DiscriminativePick<
|
||||||
|
TDigitalOceanConnection,
|
||||||
|
"method" | "app" | "credentials"
|
||||||
|
> & {
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDigitalOceanVariable = {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
type: "SECRET" | "GENERAL";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDigitalOceanApp = {
|
||||||
|
id: string;
|
||||||
|
spec: {
|
||||||
|
name: string;
|
||||||
|
services: Array<{
|
||||||
|
name: string;
|
||||||
|
}>;
|
||||||
|
envs?: TDigitalOceanVariable[];
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./digital-ocean-connection-constants";
|
||||||
|
export * from "./digital-ocean-connection-fns";
|
||||||
|
export * from "./digital-ocean-connection-schemas";
|
||||||
|
export * from "./digital-ocean-connection-types";
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
export const DIGITAL_OCEAN_APP_PLATFORM_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||||
|
name: "Digital Ocean App Platform" as const,
|
||||||
|
destination: SecretSync.DigitalOceanAppPlatform,
|
||||||
|
connection: AppConnection.DigitalOcean,
|
||||||
|
canImportSecrets: false
|
||||||
|
};
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/* eslint-disable no-continue */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
|
||||||
|
import { TDigitalOceanVariable } from "@app/services/app-connection/digital-ocean";
|
||||||
|
import { DigitalOceanAppPlatformPublicAPI } from "@app/services/app-connection/digital-ocean/digital-ocean-connection-public-client";
|
||||||
|
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
||||||
|
|
||||||
|
import { SecretSyncError } from "../secret-sync-errors";
|
||||||
|
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||||
|
import { TSecretMap } from "../secret-sync-types";
|
||||||
|
import { TDigitalOceanAppPlatformSyncWithCredentials } from "./digital-ocean-app-platform-sync-types";
|
||||||
|
|
||||||
|
export const DigitalOceanAppPlatformSyncFns = {
|
||||||
|
async getSecrets(secretSync: TDigitalOceanAppPlatformSyncWithCredentials) {
|
||||||
|
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
|
||||||
|
},
|
||||||
|
|
||||||
|
async syncSecrets(secretSync: TDigitalOceanAppPlatformSyncWithCredentials, secretMap: TSecretMap) {
|
||||||
|
const {
|
||||||
|
environment,
|
||||||
|
syncOptions: { disableSecretDeletion, keySchema }
|
||||||
|
} = secretSync;
|
||||||
|
|
||||||
|
const config = secretSync.destinationConfig;
|
||||||
|
|
||||||
|
const existing = await DigitalOceanAppPlatformPublicAPI.getVariables(secretSync.connection, config.appId);
|
||||||
|
|
||||||
|
const variables: Record<string, TDigitalOceanVariable> = Object.fromEntries(existing.map((v) => [v.key, v]));
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(secretMap)) {
|
||||||
|
variables[key] = {
|
||||||
|
key,
|
||||||
|
value: value.value,
|
||||||
|
type: "SECRET"
|
||||||
|
} as TDigitalOceanVariable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!disableSecretDeletion) {
|
||||||
|
for (const v of existing) {
|
||||||
|
if (!matchesSchema(v.key, environment?.slug || "", keySchema)) continue;
|
||||||
|
if (!(v.key in secretMap)) {
|
||||||
|
delete variables[v.key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const vars = Object.values(variables);
|
||||||
|
await DigitalOceanAppPlatformPublicAPI.putVariables(secretSync.connection, config.appId, ...vars);
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeSecrets(secretSync: TDigitalOceanAppPlatformSyncWithCredentials, secretMap: TSecretMap) {
|
||||||
|
const config = secretSync.destinationConfig;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const existingSecrets = await DigitalOceanAppPlatformPublicAPI.getVariables(secretSync.connection, config.appId);
|
||||||
|
|
||||||
|
const vars = Object.entries(existingSecrets)
|
||||||
|
.map(([key, v]) => {
|
||||||
|
if (!(key in secretMap)) return;
|
||||||
|
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
value: v.value,
|
||||||
|
type: "SECRET"
|
||||||
|
} as TDigitalOceanVariable;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as TDigitalOceanVariable[];
|
||||||
|
|
||||||
|
await DigitalOceanAppPlatformPublicAPI.deleteVariables(secretSync.connection, config.appId, ...vars);
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import {
|
||||||
|
BaseSecretSyncSchema,
|
||||||
|
GenericCreateSecretSyncFieldsSchema,
|
||||||
|
GenericUpdateSecretSyncFieldsSchema
|
||||||
|
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||||
|
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
const DigitalOceanAppPlatformSyncDestinationConfigSchema = z.object({
|
||||||
|
appId: z.string().min(1, "Account ID is required").max(255, "Account ID must be less than 255 characters"),
|
||||||
|
appName: z.string().min(1, "Account Name is required").max(255, "Account Name must be less than 255 characters")
|
||||||
|
});
|
||||||
|
|
||||||
|
const DigitalOceanAppPlatformSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||||
|
|
||||||
|
export const DigitalOceanAppPlatformSyncSchema = BaseSecretSyncSchema(
|
||||||
|
SecretSync.DigitalOceanAppPlatform,
|
||||||
|
DigitalOceanAppPlatformSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destination: z.literal(SecretSync.DigitalOceanAppPlatform),
|
||||||
|
destinationConfig: DigitalOceanAppPlatformSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateDigitalOceanAppPlatformSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.DigitalOceanAppPlatform,
|
||||||
|
DigitalOceanAppPlatformSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: DigitalOceanAppPlatformSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateDigitalOceanAppPlatformSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.DigitalOceanAppPlatform,
|
||||||
|
DigitalOceanAppPlatformSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: DigitalOceanAppPlatformSyncDestinationConfigSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DigitalOceanAppPlatformSyncListItemSchema = z.object({
|
||||||
|
name: z.literal("Digital Ocean App Platform"),
|
||||||
|
connection: z.literal(AppConnection.DigitalOcean),
|
||||||
|
destination: z.literal(SecretSync.DigitalOceanAppPlatform),
|
||||||
|
canImportSecrets: z.literal(false)
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { TDigitalOceanConnection, TDigitalOceanVariable } from "@app/services/app-connection/digital-ocean";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateDigitalOceanAppPlatformSyncSchema,
|
||||||
|
DigitalOceanAppPlatformSyncListItemSchema,
|
||||||
|
DigitalOceanAppPlatformSyncSchema
|
||||||
|
} from "./digital-ocean-app-platform-sync-schemas";
|
||||||
|
|
||||||
|
export type TDigitalOceanAppPlatformSyncListItem = z.infer<typeof DigitalOceanAppPlatformSyncListItemSchema>;
|
||||||
|
|
||||||
|
export type TDigitalOceanAppPlatformSync = z.infer<typeof DigitalOceanAppPlatformSyncSchema>;
|
||||||
|
|
||||||
|
export type TDigitalOceanAppPlatformSyncInput = z.infer<typeof CreateDigitalOceanAppPlatformSyncSchema>;
|
||||||
|
|
||||||
|
export type TDigitalOceanAppPlatformSyncWithCredentials = TDigitalOceanAppPlatformSync & {
|
||||||
|
connection: TDigitalOceanConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDigitalOceanAppPlatformSecret = TDigitalOceanVariable & {
|
||||||
|
type: "SECRET";
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./digital-ocean-app-platform-sync-constants";
|
||||||
|
export * from "./digital-ocean-app-platform-sync-fns";
|
||||||
|
export * from "./digital-ocean-app-platform-sync-schemas";
|
||||||
|
export * from "./digital-ocean-app-platform-sync-types";
|
||||||
@@ -26,6 +26,7 @@ export enum SecretSync {
|
|||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway",
|
Railway = "railway",
|
||||||
Checkly = "checkly",
|
Checkly = "checkly",
|
||||||
|
DigitalOceanAppPlatform = "digital-ocean-app-platform",
|
||||||
Bitbucket = "bitbucket"
|
Bitbucket = "bitbucket"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ import { ChecklySyncFns } from "./checkly/checkly-sync-fns";
|
|||||||
import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants";
|
import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants";
|
||||||
import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns";
|
import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns";
|
||||||
import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers";
|
import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers";
|
||||||
|
import {
|
||||||
|
DIGITAL_OCEAN_APP_PLATFORM_SYNC_LIST_OPTION,
|
||||||
|
DigitalOceanAppPlatformSyncFns
|
||||||
|
} from "./digital-ocean-app-platform";
|
||||||
import { FLYIO_SYNC_LIST_OPTION, FlyioSyncFns } from "./flyio";
|
import { FLYIO_SYNC_LIST_OPTION, FlyioSyncFns } from "./flyio";
|
||||||
import { GCP_SYNC_LIST_OPTION } from "./gcp";
|
import { GCP_SYNC_LIST_OPTION } from "./gcp";
|
||||||
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
|
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
|
||||||
@@ -82,6 +86,7 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
|||||||
[SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION,
|
[SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION,
|
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION,
|
[SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION,
|
||||||
|
[SecretSync.DigitalOceanAppPlatform]: DIGITAL_OCEAN_APP_PLATFORM_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Bitbucket]: BITBUCKET_SYNC_LIST_OPTION
|
[SecretSync.Bitbucket]: BITBUCKET_SYNC_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -260,6 +265,8 @@ export const SecretSyncFns = {
|
|||||||
return ChecklySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
return ChecklySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
return SupabaseSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
return SupabaseSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
|
case SecretSync.DigitalOceanAppPlatform:
|
||||||
|
return DigitalOceanAppPlatformSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
case SecretSync.Bitbucket:
|
case SecretSync.Bitbucket:
|
||||||
return BitbucketSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
return BitbucketSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
default:
|
default:
|
||||||
@@ -369,6 +376,9 @@ export const SecretSyncFns = {
|
|||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
secretMap = await SupabaseSyncFns.getSecrets(secretSync);
|
secretMap = await SupabaseSyncFns.getSecrets(secretSync);
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.DigitalOceanAppPlatform:
|
||||||
|
secretMap = await DigitalOceanAppPlatformSyncFns.getSecrets(secretSync);
|
||||||
|
break;
|
||||||
case SecretSync.Bitbucket:
|
case SecretSync.Bitbucket:
|
||||||
secretMap = await BitbucketSyncFns.getSecrets(secretSync);
|
secretMap = await BitbucketSyncFns.getSecrets(secretSync);
|
||||||
break;
|
break;
|
||||||
@@ -459,6 +469,8 @@ export const SecretSyncFns = {
|
|||||||
return ChecklySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
return ChecklySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
return SupabaseSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
return SupabaseSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
|
case SecretSync.DigitalOceanAppPlatform:
|
||||||
|
return DigitalOceanAppPlatformSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
case SecretSync.Bitbucket:
|
case SecretSync.Bitbucket:
|
||||||
return BitbucketSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
return BitbucketSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
|||||||
[SecretSync.Zabbix]: "Zabbix",
|
[SecretSync.Zabbix]: "Zabbix",
|
||||||
[SecretSync.Railway]: "Railway",
|
[SecretSync.Railway]: "Railway",
|
||||||
[SecretSync.Checkly]: "Checkly",
|
[SecretSync.Checkly]: "Checkly",
|
||||||
|
[SecretSync.DigitalOceanAppPlatform]: "Digital Ocean App Platform",
|
||||||
[SecretSync.Bitbucket]: "Bitbucket"
|
[SecretSync.Bitbucket]: "Bitbucket"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,6 +61,7 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
||||||
[SecretSync.Railway]: AppConnection.Railway,
|
[SecretSync.Railway]: AppConnection.Railway,
|
||||||
[SecretSync.Checkly]: AppConnection.Checkly,
|
[SecretSync.Checkly]: AppConnection.Checkly,
|
||||||
|
[SecretSync.DigitalOceanAppPlatform]: AppConnection.DigitalOcean,
|
||||||
[SecretSync.Bitbucket]: AppConnection.Bitbucket
|
[SecretSync.Bitbucket]: AppConnection.Bitbucket
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,5 +93,6 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
|||||||
[SecretSync.Zabbix]: SecretSyncPlanType.Regular,
|
[SecretSync.Zabbix]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Railway]: SecretSyncPlanType.Regular,
|
[SecretSync.Railway]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Checkly]: SecretSyncPlanType.Regular,
|
[SecretSync.Checkly]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.DigitalOceanAppPlatform]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Bitbucket]: SecretSyncPlanType.Regular
|
[SecretSync.Bitbucket]: SecretSyncPlanType.Regular
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -96,6 +96,11 @@ import {
|
|||||||
TCloudflareWorkersSyncListItem,
|
TCloudflareWorkersSyncListItem,
|
||||||
TCloudflareWorkersSyncWithCredentials
|
TCloudflareWorkersSyncWithCredentials
|
||||||
} from "./cloudflare-workers";
|
} from "./cloudflare-workers";
|
||||||
|
import {
|
||||||
|
TDigitalOceanAppPlatformSyncInput,
|
||||||
|
TDigitalOceanAppPlatformSyncListItem,
|
||||||
|
TDigitalOceanAppPlatformSyncWithCredentials
|
||||||
|
} from "./digital-ocean-app-platform/digital-ocean-app-platform-sync-types";
|
||||||
import { TFlyioSync, TFlyioSyncInput, TFlyioSyncListItem, TFlyioSyncWithCredentials } from "./flyio/flyio-sync-types";
|
import { TFlyioSync, TFlyioSyncInput, TFlyioSyncListItem, TFlyioSyncWithCredentials } from "./flyio/flyio-sync-types";
|
||||||
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
|
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
|
||||||
import { TGitLabSync, TGitLabSyncInput, TGitLabSyncListItem, TGitLabSyncWithCredentials } from "./gitlab";
|
import { TGitLabSync, TGitLabSyncInput, TGitLabSyncListItem, TGitLabSyncWithCredentials } from "./gitlab";
|
||||||
@@ -203,6 +208,7 @@ export type TSecretSyncWithCredentials =
|
|||||||
| TRailwaySyncWithCredentials
|
| TRailwaySyncWithCredentials
|
||||||
| TChecklySyncWithCredentials
|
| TChecklySyncWithCredentials
|
||||||
| TSupabaseSyncWithCredentials
|
| TSupabaseSyncWithCredentials
|
||||||
|
| TDigitalOceanAppPlatformSyncWithCredentials
|
||||||
| TBitbucketSyncWithCredentials;
|
| TBitbucketSyncWithCredentials;
|
||||||
|
|
||||||
export type TSecretSyncInput =
|
export type TSecretSyncInput =
|
||||||
@@ -233,6 +239,7 @@ export type TSecretSyncInput =
|
|||||||
| TRailwaySyncInput
|
| TRailwaySyncInput
|
||||||
| TChecklySyncInput
|
| TChecklySyncInput
|
||||||
| TSupabaseSyncInput
|
| TSupabaseSyncInput
|
||||||
|
| TDigitalOceanAppPlatformSyncInput
|
||||||
| TBitbucketSyncInput;
|
| TBitbucketSyncInput;
|
||||||
|
|
||||||
export type TSecretSyncListItem =
|
export type TSecretSyncListItem =
|
||||||
@@ -263,6 +270,7 @@ export type TSecretSyncListItem =
|
|||||||
| TRailwaySyncListItem
|
| TRailwaySyncListItem
|
||||||
| TChecklySyncListItem
|
| TChecklySyncListItem
|
||||||
| TSupabaseSyncListItem
|
| TSupabaseSyncListItem
|
||||||
|
| TDigitalOceanAppPlatformSyncListItem
|
||||||
| TBitbucketSyncListItem;
|
| TBitbucketSyncListItem;
|
||||||
|
|
||||||
export type TSyncOptionsConfig = {
|
export type TSyncOptionsConfig = {
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Available"
|
||||||
|
openapi: "GET /api/v1/app-connections/digital-ocean/available"
|
||||||
|
---
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/app-connections/digital-ocean"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Digital Ocean Connections](/integrations/app-connections/digital-ocean) to learn how to obtain the required credentials.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/app-connections/digital-ocean/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/app-connections/digital-ocean/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/app-connections/digital-ocean/connection-name/{connectionName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/app-connections/digital-ocean"
|
||||||
|
---
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/app-connections/digital-ocean/{connectionId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Digital Ocean Connections](/integrations/app-connections/digital-ocean) to learn how to obtain the required credentials.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/digital-ocean-app-platform"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/secret-syncs/digital-ocean-app-platform/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/digital-ocean-app-platform/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/digital-ocean-app-platform/sync-name/{syncName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/digital-ocean-app-platform"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Remove Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/digital-ocean-app-platform/{syncId}/remove-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Sync Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/digital-ocean-app-platform/{syncId}/sync-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/secret-syncs/digital-ocean-app-platform/{syncId}"
|
||||||
|
---
|
||||||
@@ -476,6 +476,7 @@
|
|||||||
"integrations/app-connections/checkly",
|
"integrations/app-connections/checkly",
|
||||||
"integrations/app-connections/cloudflare",
|
"integrations/app-connections/cloudflare",
|
||||||
"integrations/app-connections/databricks",
|
"integrations/app-connections/databricks",
|
||||||
|
"integrations/app-connections/digital-ocean",
|
||||||
"integrations/app-connections/flyio",
|
"integrations/app-connections/flyio",
|
||||||
"integrations/app-connections/gcp",
|
"integrations/app-connections/gcp",
|
||||||
"integrations/app-connections/github",
|
"integrations/app-connections/github",
|
||||||
@@ -522,6 +523,7 @@
|
|||||||
"integrations/secret-syncs/cloudflare-pages",
|
"integrations/secret-syncs/cloudflare-pages",
|
||||||
"integrations/secret-syncs/cloudflare-workers",
|
"integrations/secret-syncs/cloudflare-workers",
|
||||||
"integrations/secret-syncs/databricks",
|
"integrations/secret-syncs/databricks",
|
||||||
|
"integrations/secret-syncs/digital-ocean-app-platform",
|
||||||
"integrations/secret-syncs/flyio",
|
"integrations/secret-syncs/flyio",
|
||||||
"integrations/secret-syncs/gcp-secret-manager",
|
"integrations/secret-syncs/gcp-secret-manager",
|
||||||
"integrations/secret-syncs/github",
|
"integrations/secret-syncs/github",
|
||||||
@@ -1382,6 +1384,18 @@
|
|||||||
"api-reference/endpoints/app-connections/databricks/update",
|
"api-reference/endpoints/app-connections/databricks/update",
|
||||||
"api-reference/endpoints/app-connections/databricks/delete"
|
"api-reference/endpoints/app-connections/databricks/delete"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Digital Ocean",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/app-connections/digital-ocean/list",
|
||||||
|
"api-reference/endpoints/app-connections/digital-ocean/available",
|
||||||
|
"api-reference/endpoints/app-connections/digital-ocean/get-by-id",
|
||||||
|
"api-reference/endpoints/app-connections/digital-ocean/get-by-name",
|
||||||
|
"api-reference/endpoints/app-connections/digital-ocean/create",
|
||||||
|
"api-reference/endpoints/app-connections/digital-ocean/update",
|
||||||
|
"api-reference/endpoints/app-connections/digital-ocean/delete"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Fly.io",
|
"group": "Fly.io",
|
||||||
@@ -1829,6 +1843,19 @@
|
|||||||
"api-reference/endpoints/secret-syncs/databricks/remove-secrets"
|
"api-reference/endpoints/secret-syncs/databricks/remove-secrets"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Digital Ocean",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/secret-syncs/digital-ocean-app-platform/list",
|
||||||
|
"api-reference/endpoints/secret-syncs/digital-ocean-app-platform/get-by-id",
|
||||||
|
"api-reference/endpoints/secret-syncs/digital-ocean-app-platform/get-by-name",
|
||||||
|
"api-reference/endpoints/secret-syncs/digital-ocean-app-platform/create",
|
||||||
|
"api-reference/endpoints/secret-syncs/digital-ocean-app-platform/update",
|
||||||
|
"api-reference/endpoints/secret-syncs/digital-ocean-app-platform/delete",
|
||||||
|
"api-reference/endpoints/secret-syncs/digital-ocean-app-platform/sync-secrets",
|
||||||
|
"api-reference/endpoints/secret-syncs/digital-ocean-app-platform/remove-secrets"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "Fly.io",
|
"group": "Fly.io",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
|||||||
|
After Width: | Height: | Size: 445 KiB |
|
After Width: | Height: | Size: 400 KiB |
|
After Width: | Height: | Size: 469 KiB |
|
After Width: | Height: | Size: 461 KiB |
|
After Width: | Height: | Size: 487 KiB |
|
After Width: | Height: | Size: 732 KiB |
|
After Width: | Height: | Size: 559 KiB |
|
After Width: | Height: | Size: 578 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 702 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 659 KiB |
|
After Width: | Height: | Size: 653 KiB |
|
After Width: | Height: | Size: 720 KiB |
|
After Width: | Height: | Size: 677 KiB |
|
After Width: | Height: | Size: 637 KiB |
@@ -0,0 +1,113 @@
|
|||||||
|
---
|
||||||
|
title: "DigitalOcean Connection"
|
||||||
|
description: "Learn how to configure a DigitalOcean Connection for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
Infisical supports the use of [API Tokens](https://cloud.digitalocean.com/account/api/tokens) to connect with DigitalOcean.
|
||||||
|
|
||||||
|
## Create a DigitalOcean API Token
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="From the bottom left sidebar, select 'API'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="In the API section, click on 'Generate New Token'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Provide a name and select custom scopes">
|
||||||
|
Give your token a descriptive name and ensure custom scopes is selected.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select appropriate scopes and Click 'Generate Token'">
|
||||||
|
```
|
||||||
|
read:account
|
||||||
|
read:actions
|
||||||
|
read:regions
|
||||||
|
read:sizes
|
||||||
|
read:app/projects
|
||||||
|
update:app
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Generate and copy the API token">
|
||||||
|
Make sure to copy the token now—you won't be able to see it again.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Create a DigitalOcean Connection in Infisical
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Navigate to App Connections">
|
||||||
|
In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select DigitalOcean Connection">
|
||||||
|
Click **+ Add Connection** and choose **DigitalOcean Connection** from the list of integrations.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Fill out the DigitalOcean Connection form">
|
||||||
|
Complete the form by providing:
|
||||||
|
- A descriptive name for the connection
|
||||||
|
- An optional description
|
||||||
|
- The API Token from the previous step
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Connection created">
|
||||||
|
After submitting the form, your **DigitalOcean Connection** will be successfully created and ready to use with your Infisical projects.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab title="API">
|
||||||
|
To create a DigitalOcean Connection via API, send a request to the [Create DigitalOcean Connection](/api-reference/endpoints/app-connections/digital-ocean/create) endpoint.
|
||||||
|
|
||||||
|
### Sample request
|
||||||
|
|
||||||
|
```bash Request
|
||||||
|
curl --request POST \
|
||||||
|
--url https://app.infisical.com/api/v1/app-connections/digital-ocean \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"name": "my-digitalocean-connection",
|
||||||
|
"method": "api-token",
|
||||||
|
"credentials": {
|
||||||
|
"apiToken": "[API TOKEN]"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample response
|
||||||
|
|
||||||
|
```bash Response
|
||||||
|
{
|
||||||
|
"appConnection": {
|
||||||
|
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
|
||||||
|
"name": "my-digitalocean-connection",
|
||||||
|
"description": null,
|
||||||
|
"version": 1,
|
||||||
|
"orgId": "abcdef12-3456-7890-abcd-ef1234567890",
|
||||||
|
"createdAt": "2025-07-19T10:15:00.000Z",
|
||||||
|
"updatedAt": "2025-07-19T10:15:00.000Z",
|
||||||
|
"isPlatformManagedCredentials": false,
|
||||||
|
"credentialsHash": "d41d8cd98f00b204e9800998ecf8427e",
|
||||||
|
"app": "digital-ocean",
|
||||||
|
"method": "api-token",
|
||||||
|
"credentials": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
---
|
||||||
|
title: "DigitalOcean App Platform Sync"
|
||||||
|
description: "Learn how to configure a DigitalOcean App Platform Sync for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
|
||||||
|
- Create a [DigitalOcean Connection](/integrations/app-connections/digital-ocean)
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Add Sync">
|
||||||
|
Navigate to **Project** > **Integrations** and select the **Secret Syncs** tab. Click on the **Add Sync** button.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select 'DigitalOcean App Platform'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Configure source">
|
||||||
|
Configure the **Source** from where secrets should be retrieved, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Environment**: The project environment to retrieve secrets from.
|
||||||
|
- **Secret Path**: The folder path to retrieve secrets from.
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
If you need to sync secrets from multiple folder locations, check out [secret imports](/documentation/platform/secret-reference#secret-imports).
|
||||||
|
</Tip>
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure destination">
|
||||||
|
Configure the **Destination** to where secrets should be deployed, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **DigitalOcean Connection**: The DigitalOcean Connection to authenticate with.
|
||||||
|
- **App**: The App Platform app to sync secrets to.
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure Sync Options">
|
||||||
|
Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync.
|
||||||
|
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
|
||||||
|
<Note>
|
||||||
|
Digital Ocean App Platform does not support importing secrets.
|
||||||
|
</Note>
|
||||||
|
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and `{{environment}}` for the environment.
|
||||||
|
<Note>
|
||||||
|
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
|
||||||
|
</Note>
|
||||||
|
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
|
||||||
|
- **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure details">
|
||||||
|
Configure the **Details** of your DigitalOcean Sync, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Name**: The name of your sync. Must be slug-friendly.
|
||||||
|
- **Description**: An optional description for your sync.
|
||||||
|
</Step>
|
||||||
|
<Step title="Review configuration">
|
||||||
|
Review your DigitalOcean Sync configuration, then click **Create Sync**.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Sync created">
|
||||||
|
If enabled, your DigitalOcean Sync will begin syncing your secrets to the destination endpoint.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab title="API">
|
||||||
|
To create a **DigitalOcean App Platform Sync**, make an API request to the [Create DigitalOcean Sync](/api-reference/endpoints/secret-syncs/digital-ocean/create) API endpoint.
|
||||||
|
|
||||||
|
### Sample request
|
||||||
|
|
||||||
|
```bash Request
|
||||||
|
curl --request POST \
|
||||||
|
--url https://app.infisical.com/api/v1/secret-syncs/digital-ocean-app-platform \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"name": "my-digitalocean-sync",
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"description": "sync to do app",
|
||||||
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"environment": "dev",
|
||||||
|
"secretPath": "/app-secrets",
|
||||||
|
"isEnabled": true,
|
||||||
|
"syncOptions": {
|
||||||
|
"initialSyncBehavior": "overwrite-destination",
|
||||||
|
"autoSyncEnabled": true,
|
||||||
|
"disableSecretDeletion": false
|
||||||
|
},
|
||||||
|
"destinationConfig": {
|
||||||
|
"appId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"appName": "do-todo-app"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample response
|
||||||
|
|
||||||
|
```bash Response
|
||||||
|
{
|
||||||
|
"secretSync": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"name": "my-digitalocean-sync",
|
||||||
|
"description": "sync to do app",
|
||||||
|
"isEnabled": true,
|
||||||
|
"version": 1,
|
||||||
|
"folderId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"createdAt": "2025-07-19T12:00:00Z",
|
||||||
|
"updatedAt": "2025-07-19T12:00:00Z",
|
||||||
|
"syncStatus": "succeeded",
|
||||||
|
"lastSyncJobId": "job-5678",
|
||||||
|
"lastSyncMessage": null,
|
||||||
|
"lastSyncedAt": "2025-07-19T12:00:00Z",
|
||||||
|
"syncOptions": {
|
||||||
|
"initialSyncBehavior": "overwrite-destination",
|
||||||
|
"autoSyncEnabled": true,
|
||||||
|
"disableSecretDeletion": false
|
||||||
|
},
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"connection": {
|
||||||
|
"app": "digital-ocean",
|
||||||
|
"name": "my-digitalocean-connection",
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"slug": "dev",
|
||||||
|
"name": "Development",
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||||
|
},
|
||||||
|
"folder": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"path": "/app-secrets"
|
||||||
|
},
|
||||||
|
"destination": "digital-ocean",
|
||||||
|
"destinationConfig": {
|
||||||
|
"appId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"appName": "do-todo-app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||||
|
import { SingleValue } from "react-select";
|
||||||
|
|
||||||
|
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||||
|
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||||
|
import { useDigitalOceanConnectionListApps } from "@app/hooks/api/appConnections/digital-ocean";
|
||||||
|
import { TDigitalOceanApp } from "@app/hooks/api/appConnections/digital-ocean/types";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
import { TSecretSyncForm } from "../schemas";
|
||||||
|
|
||||||
|
export const DigitalOceanAppPlatformSyncFields = () => {
|
||||||
|
const { control, setValue } = useFormContext<
|
||||||
|
TSecretSyncForm & { destination: SecretSync.DigitalOceanAppPlatform }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const connectionId = useWatch({ name: "connection.id", control });
|
||||||
|
|
||||||
|
const { data: apps = [], isPending: isAccountsLoading } = useDigitalOceanConnectionListApps(
|
||||||
|
connectionId,
|
||||||
|
{
|
||||||
|
enabled: Boolean(connectionId)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SecretSyncConnectionField
|
||||||
|
onChange={() => {
|
||||||
|
setValue("destinationConfig.appId", "");
|
||||||
|
setValue("destinationConfig.appName", "");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="destinationConfig.appId"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Select an app"
|
||||||
|
tooltipClassName="max-w-md"
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
isLoading={isAccountsLoading && Boolean(connectionId)}
|
||||||
|
isDisabled={!connectionId}
|
||||||
|
value={apps.find((p) => p.id === value) ?? null}
|
||||||
|
onChange={(option) => {
|
||||||
|
const v = option as SingleValue<TDigitalOceanApp>;
|
||||||
|
onChange(v?.id ?? null);
|
||||||
|
setValue("destinationConfig.appName", v?.spec.name ?? "");
|
||||||
|
}}
|
||||||
|
options={apps}
|
||||||
|
placeholder="Select an app..."
|
||||||
|
getOptionLabel={(option) => option.spec.name}
|
||||||
|
getOptionValue={(option) => option.id}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -15,6 +15,7 @@ import { ChecklySyncFields } from "./ChecklySyncFields";
|
|||||||
import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields";
|
import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields";
|
||||||
import { CloudflareWorkersSyncFields } from "./CloudflareWorkersSyncFields";
|
import { CloudflareWorkersSyncFields } from "./CloudflareWorkersSyncFields";
|
||||||
import { DatabricksSyncFields } from "./DatabricksSyncFields";
|
import { DatabricksSyncFields } from "./DatabricksSyncFields";
|
||||||
|
import { DigitalOceanAppPlatformSyncFields } from "./DigitalOceanAppPlatformSyncFields";
|
||||||
import { FlyioSyncFields } from "./FlyioSyncFields";
|
import { FlyioSyncFields } from "./FlyioSyncFields";
|
||||||
import { GcpSyncFields } from "./GcpSyncFields";
|
import { GcpSyncFields } from "./GcpSyncFields";
|
||||||
import { GitHubSyncFields } from "./GitHubSyncFields";
|
import { GitHubSyncFields } from "./GitHubSyncFields";
|
||||||
@@ -92,6 +93,8 @@ export const SecretSyncDestinationFields = () => {
|
|||||||
return <ChecklySyncFields />;
|
return <ChecklySyncFields />;
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
return <SupabaseSyncFields />;
|
return <SupabaseSyncFields />;
|
||||||
|
case SecretSync.DigitalOceanAppPlatform:
|
||||||
|
return <DigitalOceanAppPlatformSyncFields />;
|
||||||
case SecretSync.Bitbucket:
|
case SecretSync.Bitbucket:
|
||||||
return <BitbucketSyncFields />;
|
return <BitbucketSyncFields />;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
|||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
|
case SecretSync.DigitalOceanAppPlatform:
|
||||||
case SecretSync.Bitbucket:
|
case SecretSync.Bitbucket:
|
||||||
AdditionalSyncOptionsFieldsComponent = null;
|
AdditionalSyncOptionsFieldsComponent = null;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||||
|
import { GenericFieldLabel } from "@app/components/v2";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
export const DigitalOceanAppPlatformSyncReviewFields = () => {
|
||||||
|
const { watch } = useFormContext<
|
||||||
|
TSecretSyncForm & { destination: SecretSync.DigitalOceanAppPlatform }
|
||||||
|
>();
|
||||||
|
const appName = watch("destinationConfig.appName");
|
||||||
|
|
||||||
|
return <GenericFieldLabel label="App">{appName}</GenericFieldLabel>;
|
||||||
|
};
|
||||||
@@ -24,6 +24,7 @@ import { ChecklySyncReviewFields } from "./ChecklySyncReviewFields";
|
|||||||
import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields";
|
import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields";
|
||||||
import { CloudflareWorkersSyncReviewFields } from "./CloudflareWorkersReviewFields";
|
import { CloudflareWorkersSyncReviewFields } from "./CloudflareWorkersReviewFields";
|
||||||
import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields";
|
import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields";
|
||||||
|
import { DigitalOceanAppPlatformSyncReviewFields } from "./DigitalOceanAppPlatformSyncReviewFields";
|
||||||
import { FlyioSyncReviewFields } from "./FlyioSyncReviewFields";
|
import { FlyioSyncReviewFields } from "./FlyioSyncReviewFields";
|
||||||
import { GcpSyncReviewFields } from "./GcpSyncReviewFields";
|
import { GcpSyncReviewFields } from "./GcpSyncReviewFields";
|
||||||
import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
|
import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
|
||||||
@@ -145,6 +146,9 @@ export const SecretSyncReviewFields = () => {
|
|||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
DestinationFieldsComponent = <SupabaseSyncReviewFields />;
|
DestinationFieldsComponent = <SupabaseSyncReviewFields />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.DigitalOceanAppPlatform:
|
||||||
|
DestinationFieldsComponent = <DigitalOceanAppPlatformSyncReviewFields />;
|
||||||
|
break;
|
||||||
case SecretSync.Bitbucket:
|
case SecretSync.Bitbucket:
|
||||||
DestinationFieldsComponent = <BitbucketSyncReviewFields />;
|
DestinationFieldsComponent = <BitbucketSyncReviewFields />;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
export const DigitalOceanAppPlatformSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||||
|
z.object({
|
||||||
|
destination: z.literal(SecretSync.DigitalOceanAppPlatform),
|
||||||
|
destinationConfig: z.object({
|
||||||
|
appId: z
|
||||||
|
.string()
|
||||||
|
.min(1, "App ID is required")
|
||||||
|
.max(255, "App ID must be less than 255 characters"),
|
||||||
|
appName: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Account Name is required")
|
||||||
|
.max(255, "App Name must be less than 255 characters")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -12,6 +12,7 @@ import { ChecklySyncDestinationSchema } from "./checkly-sync-destination-schema"
|
|||||||
import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema";
|
import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema";
|
||||||
import { CloudflareWorkersSyncDestinationSchema } from "./cloudflare-workers-sync-destination-schema";
|
import { CloudflareWorkersSyncDestinationSchema } from "./cloudflare-workers-sync-destination-schema";
|
||||||
import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema";
|
import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema";
|
||||||
|
import { DigitalOceanAppPlatformSyncDestinationSchema } from "./digital-ocean-app-platform-sync-destination-schema";
|
||||||
import { FlyioSyncDestinationSchema } from "./flyio-sync-destination-schema";
|
import { FlyioSyncDestinationSchema } from "./flyio-sync-destination-schema";
|
||||||
import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
|
import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
|
||||||
import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema";
|
import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema";
|
||||||
@@ -57,6 +58,7 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
|||||||
ZabbixSyncDestinationSchema,
|
ZabbixSyncDestinationSchema,
|
||||||
RailwaySyncDestinationSchema,
|
RailwaySyncDestinationSchema,
|
||||||
ChecklySyncDestinationSchema,
|
ChecklySyncDestinationSchema,
|
||||||
|
DigitalOceanAppPlatformSyncDestinationSchema,
|
||||||
BitbucketSyncDestinationSchema
|
BitbucketSyncDestinationSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ import {
|
|||||||
AzureClientSecretsConnectionMethod,
|
AzureClientSecretsConnectionMethod,
|
||||||
AzureDevOpsConnectionMethod,
|
AzureDevOpsConnectionMethod,
|
||||||
AzureKeyVaultConnectionMethod,
|
AzureKeyVaultConnectionMethod,
|
||||||
BitbucketConnectionMethod,
|
|
||||||
CamundaConnectionMethod,
|
CamundaConnectionMethod,
|
||||||
ChecklyConnectionMethod,
|
|
||||||
CloudflareConnectionMethod,
|
CloudflareConnectionMethod,
|
||||||
DatabricksConnectionMethod,
|
DatabricksConnectionMethod,
|
||||||
FlyioConnectionMethod,
|
FlyioConnectionMethod,
|
||||||
@@ -28,19 +26,14 @@ import {
|
|||||||
GitHubRadarConnectionMethod,
|
GitHubRadarConnectionMethod,
|
||||||
GitLabConnectionMethod,
|
GitLabConnectionMethod,
|
||||||
HCVaultConnectionMethod,
|
HCVaultConnectionMethod,
|
||||||
HerokuConnectionMethod,
|
|
||||||
HumanitecConnectionMethod,
|
HumanitecConnectionMethod,
|
||||||
LdapConnectionMethod,
|
LdapConnectionMethod,
|
||||||
MsSqlConnectionMethod,
|
MsSqlConnectionMethod,
|
||||||
MySqlConnectionMethod,
|
MySqlConnectionMethod,
|
||||||
OCIConnectionMethod,
|
|
||||||
OktaConnectionMethod,
|
OktaConnectionMethod,
|
||||||
OnePassConnectionMethod,
|
OnePassConnectionMethod,
|
||||||
OracleDBConnectionMethod,
|
OracleDBConnectionMethod,
|
||||||
PostgresConnectionMethod,
|
PostgresConnectionMethod,
|
||||||
RailwayConnectionMethod,
|
|
||||||
RenderConnectionMethod,
|
|
||||||
SupabaseConnectionMethod,
|
|
||||||
TAppConnection,
|
TAppConnection,
|
||||||
TeamCityConnectionMethod,
|
TeamCityConnectionMethod,
|
||||||
TerraformCloudConnectionMethod,
|
TerraformCloudConnectionMethod,
|
||||||
@@ -48,6 +41,14 @@ import {
|
|||||||
WindmillConnectionMethod,
|
WindmillConnectionMethod,
|
||||||
ZabbixConnectionMethod
|
ZabbixConnectionMethod
|
||||||
} from "@app/hooks/api/appConnections/types";
|
} from "@app/hooks/api/appConnections/types";
|
||||||
|
import { BitbucketConnectionMethod } from "@app/hooks/api/appConnections/types/bitbucket-connection";
|
||||||
|
import { ChecklyConnectionMethod } from "@app/hooks/api/appConnections/types/checkly-connection";
|
||||||
|
import { DigitalOceanConnectionMethod } from "@app/hooks/api/appConnections/types/digital-ocean";
|
||||||
|
import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection";
|
||||||
|
import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection";
|
||||||
|
import { RailwayConnectionMethod } from "@app/hooks/api/appConnections/types/railway-connection";
|
||||||
|
import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-connection";
|
||||||
|
import { SupabaseConnectionMethod } from "@app/hooks/api/appConnections/types/supabase-connection";
|
||||||
|
|
||||||
export const APP_CONNECTION_MAP: Record<
|
export const APP_CONNECTION_MAP: Record<
|
||||||
AppConnection,
|
AppConnection,
|
||||||
@@ -100,6 +101,10 @@ export const APP_CONNECTION_MAP: Record<
|
|||||||
[AppConnection.Bitbucket]: { name: "Bitbucket", image: "Bitbucket.png" },
|
[AppConnection.Bitbucket]: { name: "Bitbucket", image: "Bitbucket.png" },
|
||||||
[AppConnection.Checkly]: { name: "Checkly", image: "Checkly.png" },
|
[AppConnection.Checkly]: { name: "Checkly", image: "Checkly.png" },
|
||||||
[AppConnection.Supabase]: { name: "Supabase", image: "Supabase.png" },
|
[AppConnection.Supabase]: { name: "Supabase", image: "Supabase.png" },
|
||||||
|
[AppConnection.DigitalOcean]: {
|
||||||
|
name: "Digital Ocean",
|
||||||
|
image: "Digital Ocean.png"
|
||||||
|
},
|
||||||
[AppConnection.Okta]: { name: "Okta", image: "Okta.png" }
|
[AppConnection.Okta]: { name: "Okta", image: "Okta.png" }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -134,6 +139,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
|||||||
case CloudflareConnectionMethod.ApiToken:
|
case CloudflareConnectionMethod.ApiToken:
|
||||||
case BitbucketConnectionMethod.ApiToken:
|
case BitbucketConnectionMethod.ApiToken:
|
||||||
case ZabbixConnectionMethod.ApiToken:
|
case ZabbixConnectionMethod.ApiToken:
|
||||||
|
case DigitalOceanConnectionMethod.ApiToken:
|
||||||
case OktaConnectionMethod.ApiToken:
|
case OktaConnectionMethod.ApiToken:
|
||||||
return { name: "API Token", icon: faKey };
|
return { name: "API Token", icon: faKey };
|
||||||
case PostgresConnectionMethod.UsernameAndPassword:
|
case PostgresConnectionMethod.UsernameAndPassword:
|
||||||
|
|||||||
@@ -102,6 +102,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
|
|||||||
name: "Supabase",
|
name: "Supabase",
|
||||||
image: "Supabase.png"
|
image: "Supabase.png"
|
||||||
},
|
},
|
||||||
|
[SecretSync.DigitalOceanAppPlatform]: {
|
||||||
|
name: "Digital Ocean App Platform",
|
||||||
|
image: "Digital Ocean.png"
|
||||||
|
},
|
||||||
[SecretSync.Bitbucket]: {
|
[SecretSync.Bitbucket]: {
|
||||||
name: "Bitbucket",
|
name: "Bitbucket",
|
||||||
image: "Bitbucket.png"
|
image: "Bitbucket.png"
|
||||||
@@ -136,6 +140,7 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
||||||
[SecretSync.Railway]: AppConnection.Railway,
|
[SecretSync.Railway]: AppConnection.Railway,
|
||||||
[SecretSync.Checkly]: AppConnection.Checkly,
|
[SecretSync.Checkly]: AppConnection.Checkly,
|
||||||
|
[SecretSync.DigitalOceanAppPlatform]: AppConnection.DigitalOcean,
|
||||||
[SecretSync.Bitbucket]: AppConnection.Bitbucket
|
[SecretSync.Bitbucket]: AppConnection.Bitbucket
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./queries";
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
import { appConnectionKeys } from "@app/hooks/api/appConnections";
|
||||||
|
|
||||||
|
import { TDigitalOceanApp } from "./types";
|
||||||
|
|
||||||
|
const digitalOceanAppPlatformConnectionKeys = {
|
||||||
|
all: [...appConnectionKeys.all, "digitalOceanAppPlatform"] as const,
|
||||||
|
listAccounts: (connectionId: string) =>
|
||||||
|
[...digitalOceanAppPlatformConnectionKeys.all, "workspace-scopes", connectionId] as const
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDigitalOceanConnectionListApps = (
|
||||||
|
connectionId: string,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<
|
||||||
|
TDigitalOceanApp[],
|
||||||
|
unknown,
|
||||||
|
TDigitalOceanApp[],
|
||||||
|
ReturnType<typeof digitalOceanAppPlatformConnectionKeys.listAccounts>
|
||||||
|
>,
|
||||||
|
"queryKey" | "queryFn"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: digitalOceanAppPlatformConnectionKeys.listAccounts(connectionId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<{ apps: TDigitalOceanApp[] }>(
|
||||||
|
`/api/v1/app-connections/digital-ocean/${connectionId}/apps`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.apps;
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export type TDigitalOceanAppPlatformVariable = {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
type: "SECRET" | "GENERAL";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDigitalOceanApp = {
|
||||||
|
id: string;
|
||||||
|
spec: {
|
||||||
|
name: string;
|
||||||
|
services: Array<{
|
||||||
|
name: string;
|
||||||
|
}>;
|
||||||
|
envs?: TDigitalOceanAppPlatformVariable[];
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -33,5 +33,6 @@ export enum AppConnection {
|
|||||||
Railway = "railway",
|
Railway = "railway",
|
||||||
Checkly = "checkly",
|
Checkly = "checkly",
|
||||||
Supabase = "supabase",
|
Supabase = "supabase",
|
||||||
|
DigitalOcean = "digital-ocean",
|
||||||
Okta = "okta"
|
Okta = "okta"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,6 +152,10 @@ export type TSupabaseConnectionOption = TAppConnectionOptionBase & {
|
|||||||
app: AppConnection.Supabase;
|
app: AppConnection.Supabase;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TDigitalOceanConnectionOption = TAppConnectionOptionBase & {
|
||||||
|
app: AppConnection.DigitalOcean;
|
||||||
|
};
|
||||||
|
|
||||||
export type TOktaConnectionOption = TAppConnectionOptionBase & {
|
export type TOktaConnectionOption = TAppConnectionOptionBase & {
|
||||||
app: AppConnection.Okta;
|
app: AppConnection.Okta;
|
||||||
};
|
};
|
||||||
@@ -188,6 +192,7 @@ export type TAppConnectionOption =
|
|||||||
| TZabbixConnectionOption
|
| TZabbixConnectionOption
|
||||||
| TRailwayConnectionOption
|
| TRailwayConnectionOption
|
||||||
| TChecklyConnectionOption
|
| TChecklyConnectionOption
|
||||||
|
| TDigitalOceanConnectionOption
|
||||||
| TOktaConnectionOption;
|
| TOktaConnectionOption;
|
||||||
|
|
||||||
export type TAppConnectionOptionMap = {
|
export type TAppConnectionOptionMap = {
|
||||||
@@ -225,5 +230,6 @@ export type TAppConnectionOptionMap = {
|
|||||||
[AppConnection.Railway]: TRailwayConnectionOption;
|
[AppConnection.Railway]: TRailwayConnectionOption;
|
||||||
[AppConnection.Checkly]: TChecklyConnectionOption;
|
[AppConnection.Checkly]: TChecklyConnectionOption;
|
||||||
[AppConnection.Supabase]: TSupabaseConnectionOption;
|
[AppConnection.Supabase]: TSupabaseConnectionOption;
|
||||||
|
[AppConnection.DigitalOcean]: TDigitalOceanConnectionOption;
|
||||||
[AppConnection.Okta]: TOktaConnectionOption;
|
[AppConnection.Okta]: TOktaConnectionOption;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
||||||
|
|
||||||
|
export enum DigitalOceanConnectionMethod {
|
||||||
|
ApiToken = "api-token"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TDigitalOceanConnection = TRootAppConnection & {
|
||||||
|
app: AppConnection.DigitalOcean;
|
||||||
|
method: DigitalOceanConnectionMethod.ApiToken;
|
||||||
|
credentials: {
|
||||||
|
apiToken: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -12,6 +12,7 @@ import { TCamundaConnection } from "./camunda-connection";
|
|||||||
import { TChecklyConnection } from "./checkly-connection";
|
import { TChecklyConnection } from "./checkly-connection";
|
||||||
import { TCloudflareConnection } from "./cloudflare-connection";
|
import { TCloudflareConnection } from "./cloudflare-connection";
|
||||||
import { TDatabricksConnection } from "./databricks-connection";
|
import { TDatabricksConnection } from "./databricks-connection";
|
||||||
|
import { TDigitalOceanConnection } from "./digital-ocean";
|
||||||
import { TFlyioConnection } from "./flyio-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";
|
||||||
@@ -107,6 +108,7 @@ export type TAppConnection =
|
|||||||
| TRailwayConnection
|
| TRailwayConnection
|
||||||
| TChecklyConnection
|
| TChecklyConnection
|
||||||
| TSupabaseConnection
|
| TSupabaseConnection
|
||||||
|
| TDigitalOceanConnection
|
||||||
| TOktaConnection;
|
| TOktaConnection;
|
||||||
|
|
||||||
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
|
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
|
||||||
@@ -178,5 +180,6 @@ export type TAppConnectionMap = {
|
|||||||
[AppConnection.Railway]: TRailwayConnection;
|
[AppConnection.Railway]: TRailwayConnection;
|
||||||
[AppConnection.Checkly]: TChecklyConnection;
|
[AppConnection.Checkly]: TChecklyConnection;
|
||||||
[AppConnection.Supabase]: TSupabaseConnection;
|
[AppConnection.Supabase]: TSupabaseConnection;
|
||||||
|
[AppConnection.DigitalOcean]: TDigitalOceanConnection;
|
||||||
[AppConnection.Okta]: TOktaConnection;
|
[AppConnection.Okta]: TOktaConnection;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export enum SecretSync {
|
|||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway",
|
Railway = "railway",
|
||||||
Checkly = "checkly",
|
Checkly = "checkly",
|
||||||
|
DigitalOceanAppPlatform = "digital-ocean-app-platform",
|
||||||
Bitbucket = "bitbucket"
|
Bitbucket = "bitbucket"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
||||||
|
|
||||||
|
export type TDigitalOceanAppPlatformSync = TRootSecretSync & {
|
||||||
|
destination: SecretSync.DigitalOceanAppPlatform;
|
||||||
|
destinationConfig: {
|
||||||
|
appId: string;
|
||||||
|
appName: string;
|
||||||
|
};
|
||||||
|
connection: {
|
||||||
|
app: AppConnection.DigitalOcean;
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -14,6 +14,7 @@ import { TChecklySync } from "./checkly-sync";
|
|||||||
import { TCloudflarePagesSync } from "./cloudflare-pages-sync";
|
import { TCloudflarePagesSync } from "./cloudflare-pages-sync";
|
||||||
import { TCloudflareWorkersSync } from "./cloudflare-workers-sync";
|
import { TCloudflareWorkersSync } from "./cloudflare-workers-sync";
|
||||||
import { TDatabricksSync } from "./databricks-sync";
|
import { TDatabricksSync } from "./databricks-sync";
|
||||||
|
import { TDigitalOceanAppPlatformSync } from "./digital-ocean-app-platform-sync";
|
||||||
import { TFlyioSync } from "./flyio-sync";
|
import { TFlyioSync } from "./flyio-sync";
|
||||||
import { TGcpSync } from "./gcp-sync";
|
import { TGcpSync } from "./gcp-sync";
|
||||||
import { TGitHubSync } from "./github-sync";
|
import { TGitHubSync } from "./github-sync";
|
||||||
@@ -65,6 +66,7 @@ export type TSecretSync =
|
|||||||
| TRailwaySync
|
| TRailwaySync
|
||||||
| TChecklySync
|
| TChecklySync
|
||||||
| TSupabaseSync
|
| TSupabaseSync
|
||||||
|
| TDigitalOceanAppPlatformSync
|
||||||
| TBitbucketSync;
|
| TBitbucketSync;
|
||||||
|
|
||||||
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { CamundaConnectionForm } from "./CamundaConnectionForm";
|
|||||||
import { ChecklyConnectionForm } from "./ChecklyConnectionForm";
|
import { ChecklyConnectionForm } from "./ChecklyConnectionForm";
|
||||||
import { CloudflareConnectionForm } from "./CloudflareConnectionForm";
|
import { CloudflareConnectionForm } from "./CloudflareConnectionForm";
|
||||||
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
|
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
|
||||||
|
import { DigitalOceanConnectionForm } from "./DigitalOceanConnectionForm";
|
||||||
import { FlyioConnectionForm } from "./FlyioConnectionForm";
|
import { FlyioConnectionForm } from "./FlyioConnectionForm";
|
||||||
import { GcpConnectionForm } from "./GcpConnectionForm";
|
import { GcpConnectionForm } from "./GcpConnectionForm";
|
||||||
import { GitHubConnectionForm } from "./GitHubConnectionForm";
|
import { GitHubConnectionForm } from "./GitHubConnectionForm";
|
||||||
@@ -150,6 +151,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
|||||||
return <ChecklyConnectionForm onSubmit={onSubmit} />;
|
return <ChecklyConnectionForm onSubmit={onSubmit} />;
|
||||||
case AppConnection.Supabase:
|
case AppConnection.Supabase:
|
||||||
return <SupabaseConnectionForm onSubmit={onSubmit} />;
|
return <SupabaseConnectionForm onSubmit={onSubmit} />;
|
||||||
|
case AppConnection.DigitalOcean:
|
||||||
|
return <DigitalOceanConnectionForm onSubmit={onSubmit} />;
|
||||||
case AppConnection.Okta:
|
case AppConnection.Okta:
|
||||||
return <OktaConnectionForm onSubmit={onSubmit} />;
|
return <OktaConnectionForm onSubmit={onSubmit} />;
|
||||||
default:
|
default:
|
||||||
@@ -256,6 +259,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
|||||||
return <ChecklyConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
return <ChecklyConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
case AppConnection.Supabase:
|
case AppConnection.Supabase:
|
||||||
return <SupabaseConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
return <SupabaseConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
|
case AppConnection.DigitalOcean:
|
||||||
|
return <DigitalOceanConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
case AppConnection.Okta:
|
case AppConnection.Okta:
|
||||||
return <OktaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
return <OktaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
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 { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import {
|
||||||
|
DigitalOceanConnectionMethod,
|
||||||
|
TDigitalOceanConnection
|
||||||
|
} from "@app/hooks/api/appConnections/types/digital-ocean";
|
||||||
|
|
||||||
|
import {
|
||||||
|
genericAppConnectionFieldsSchema,
|
||||||
|
GenericAppConnectionsFields
|
||||||
|
} from "./GenericAppConnectionFields";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
appConnection?: TDigitalOceanConnection;
|
||||||
|
onSubmit: (formData: FormData) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rootSchema = genericAppConnectionFieldsSchema.extend({
|
||||||
|
app: z.literal(AppConnection.DigitalOcean)
|
||||||
|
});
|
||||||
|
|
||||||
|
const formSchema = z.discriminatedUnion("method", [
|
||||||
|
rootSchema.extend({
|
||||||
|
method: z.literal(DigitalOceanConnectionMethod.ApiToken),
|
||||||
|
credentials: z.object({
|
||||||
|
apiToken: z.string().trim().min(1, "API Token required")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
type FormData = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const DigitalOceanConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||||
|
const isUpdate = Boolean(appConnection);
|
||||||
|
|
||||||
|
const form = useForm<FormData>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: appConnection ?? {
|
||||||
|
app: AppConnection.DigitalOcean,
|
||||||
|
method: DigitalOceanConnectionMethod.ApiToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 type of token you would like to use to connect with ${
|
||||||
|
APP_CONNECTION_MAP[AppConnection.DigitalOcean].name
|
||||||
|
}. This field cannot be changed after creation.`}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
label="Token Type"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
isDisabled={isUpdate}
|
||||||
|
value={value}
|
||||||
|
onValueChange={(val) => onChange(val)}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
position="popper"
|
||||||
|
dropdownContainerClassName="max-w-none"
|
||||||
|
>
|
||||||
|
{Object.values(DigitalOceanConnectionMethod).map((method) => {
|
||||||
|
return (
|
||||||
|
<SelectItem value={method} key={method}>
|
||||||
|
{getAppConnectionMethodDetails(method).name}{" "}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="credentials.apiToken"
|
||||||
|
control={control}
|
||||||
|
shouldUnregister
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
label="API Key Value"
|
||||||
|
>
|
||||||
|
<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 DigitalOcean"}
|
||||||
|
</Button>
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button colorSchema="secondary" variant="plain">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { TDigitalOceanAppPlatformSync } from "@app/hooks/api/secretSyncs/types/digital-ocean-app-platform-sync";
|
||||||
|
|
||||||
|
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||||
|
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TDigitalOceanAppPlatformSync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DigitalOceanAppPlatformSyncDestinationCol = ({ secretSync }: Props) => {
|
||||||
|
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
|
||||||
|
|
||||||
|
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
||||||
|
};
|
||||||
@@ -12,6 +12,7 @@ import { ChecklySyncDestinationCol } from "./ChecklySyncDestinationCol";
|
|||||||
import { CloudflarePagesSyncDestinationCol } from "./CloudflarePagesSyncDestinationCol";
|
import { CloudflarePagesSyncDestinationCol } from "./CloudflarePagesSyncDestinationCol";
|
||||||
import { CloudflareWorkersSyncDestinationCol } from "./CloudflareWorkersSyncDestinationCol";
|
import { CloudflareWorkersSyncDestinationCol } from "./CloudflareWorkersSyncDestinationCol";
|
||||||
import { DatabricksSyncDestinationCol } from "./DatabricksSyncDestinationCol";
|
import { DatabricksSyncDestinationCol } from "./DatabricksSyncDestinationCol";
|
||||||
|
import { DigitalOceanAppPlatformSyncDestinationCol } from "./DigitalOceanAppPlatformSyncDestinationCol";
|
||||||
import { FlyioSyncDestinationCol } from "./FlyioSyncDestinationCol";
|
import { FlyioSyncDestinationCol } from "./FlyioSyncDestinationCol";
|
||||||
import { GcpSyncDestinationCol } from "./GcpSyncDestinationCol";
|
import { GcpSyncDestinationCol } from "./GcpSyncDestinationCol";
|
||||||
import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol";
|
import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol";
|
||||||
@@ -89,6 +90,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
|
|||||||
return <ChecklySyncDestinationCol secretSync={secretSync} />;
|
return <ChecklySyncDestinationCol secretSync={secretSync} />;
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
return <SupabaseSyncDestinationCol secretSync={secretSync} />;
|
return <SupabaseSyncDestinationCol secretSync={secretSync} />;
|
||||||
|
case SecretSync.DigitalOceanAppPlatform:
|
||||||
|
return <DigitalOceanAppPlatformSyncDestinationCol secretSync={secretSync} />;
|
||||||
case SecretSync.Bitbucket:
|
case SecretSync.Bitbucket:
|
||||||
return <BitbucketSyncDestinationCol secretSync={secretSync} />;
|
return <BitbucketSyncDestinationCol secretSync={secretSync} />;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -174,6 +174,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
|||||||
primaryText = destinationConfig.projectName;
|
primaryText = destinationConfig.projectName;
|
||||||
secondaryText = "Supabase Project";
|
secondaryText = "Supabase Project";
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.DigitalOceanAppPlatform:
|
||||||
|
primaryText = destinationConfig.appName;
|
||||||
|
secondaryText = "Digital Ocean App";
|
||||||
|
break;
|
||||||
case SecretSync.Bitbucket:
|
case SecretSync.Bitbucket:
|
||||||
primaryText = destinationConfig.workspaceSlug;
|
primaryText = destinationConfig.workspaceSlug;
|
||||||
secondaryText = destinationConfig.repositorySlug;
|
secondaryText = destinationConfig.repositorySlug;
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
||||||
|
import { TDigitalOceanAppPlatformSync } from "@app/hooks/api/secretSyncs/types/digital-ocean-app-platform-sync";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TDigitalOceanAppPlatformSync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DigitalOceanAppPlatformSyncDestinationSection = ({ secretSync }: Props) => {
|
||||||
|
const { destinationConfig } = secretSync;
|
||||||
|
|
||||||
|
return <GenericFieldLabel label="App">{destinationConfig.appName}</GenericFieldLabel>;
|
||||||
|
};
|
||||||
@@ -23,6 +23,7 @@ import { ChecklySyncDestinationSection } from "./ChecklySyncDestinationSection";
|
|||||||
import { CloudflarePagesSyncDestinationSection } from "./CloudflarePagesSyncDestinationSection";
|
import { CloudflarePagesSyncDestinationSection } from "./CloudflarePagesSyncDestinationSection";
|
||||||
import { CloudflareWorkersSyncDestinationSection } from "./CloudflareWorkersSyncDestinationSection";
|
import { CloudflareWorkersSyncDestinationSection } from "./CloudflareWorkersSyncDestinationSection";
|
||||||
import { DatabricksSyncDestinationSection } from "./DatabricksSyncDestinationSection";
|
import { DatabricksSyncDestinationSection } from "./DatabricksSyncDestinationSection";
|
||||||
|
import { DigitalOceanAppPlatformSyncDestinationSection } from "./DigitalOceanAppPlatformSyncDestinationSection";
|
||||||
import { FlyioSyncDestinationSection } from "./FlyioSyncDestinationSection";
|
import { FlyioSyncDestinationSection } from "./FlyioSyncDestinationSection";
|
||||||
import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection";
|
import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection";
|
||||||
import { GitHubSyncDestinationSection } from "./GitHubSyncDestinationSection";
|
import { GitHubSyncDestinationSection } from "./GitHubSyncDestinationSection";
|
||||||
@@ -135,6 +136,11 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
|
|||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
DestinationComponents = <SupabaseSyncDestinationSection secretSync={secretSync} />;
|
DestinationComponents = <SupabaseSyncDestinationSection secretSync={secretSync} />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.DigitalOceanAppPlatform:
|
||||||
|
DestinationComponents = (
|
||||||
|
<DigitalOceanAppPlatformSyncDestinationSection secretSync={secretSync} />
|
||||||
|
);
|
||||||
|
break;
|
||||||
case SecretSync.Bitbucket:
|
case SecretSync.Bitbucket:
|
||||||
DestinationComponents = <BitbucketSyncDestinationSection secretSync={secretSync} />;
|
DestinationComponents = <BitbucketSyncDestinationSection secretSync={secretSync} />;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
|||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
|
case SecretSync.DigitalOceanAppPlatform:
|
||||||
case SecretSync.Bitbucket:
|
case SecretSync.Bitbucket:
|
||||||
AdditionalSyncOptionsComponent = null;
|
AdditionalSyncOptionsComponent = null;
|
||||||
break;
|
break;
|
||||||
|
|||||||