From 79de7c5f08cbacdd9eabb9ee204db760f2c64341 Mon Sep 17 00:00:00 2001 From: Sid <58144379+sidwebworks@users.noreply.github.com> Date: Fri, 1 Aug 2025 00:24:40 +0530 Subject: [PATCH] feat: Add Netlify app connection and secrets sync (#4205) * 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 * feat: Netlify app connection and secrets sync * feat: docs * fix: type check * fix: api client * fix: lint and types * fix: typecheck lint * fix: docs * fix: lint * fix: lint * fix: PR changes * fix: typecheck * fix: PR changes * fix PR changes * fix: PR Change * fix: type error * Small tweaks * fix: support is_secret * fix: revert is_secret * fix: force update existing netlify secret --------- Co-authored-by: sidwebworks Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: x032205 --- backend/src/lib/api-docs/constants.ts | 10 + .../app-connection-router.ts | 6 + .../routes/v1/app-connection-routers/index.ts | 2 + .../netlify-connection-router.ts | 87 ++++++ .../routes/v1/secret-sync-routers/index.ts | 2 + .../netlify-sync-router.ts | 17 ++ .../secret-sync-routers/secret-sync-router.ts | 3 + .../app-connection/app-connection-enums.ts | 1 + .../app-connection/app-connection-fns.ts | 6 +- .../app-connection/app-connection-maps.ts | 2 + .../app-connection/app-connection-service.ts | 4 + .../app-connection/app-connection-types.ts | 10 + .../services/app-connection/netlify/index.ts | 4 + .../netlify/netlify-connection-constants.ts | 3 + .../netlify/netlify-connection-fns.ts | 35 +++ .../netlify-connection-public-client.ts | 261 ++++++++++++++++++ .../netlify/netlify-connection-schemas.ts | 67 +++++ .../netlify/netlify-connection-service.ts | 42 +++ .../netlify/netlify-connection-types.ts | 51 ++++ .../src/services/secret-sync/netlify/index.ts | 4 + .../netlify/netlify-sync-constants.ts | 19 ++ .../secret-sync/netlify/netlify-sync-fns.ts | 125 +++++++++ .../netlify/netlify-sync-schemas.ts | 68 +++++ .../secret-sync/netlify/netlify-sync-types.ts | 15 + .../services/secret-sync/secret-sync-enums.ts | 1 + .../services/secret-sync/secret-sync-fns.ts | 9 + .../services/secret-sync/secret-sync-maps.ts | 3 + .../services/secret-sync/secret-sync-types.ts | 5 + .../app-connections/netlify/available.mdx | 4 + .../app-connections/netlify/create.mdx | 8 + .../app-connections/netlify/delete.mdx | 4 + .../app-connections/netlify/get-by-id.mdx | 4 + .../app-connections/netlify/get-by-name.mdx | 4 + .../app-connections/netlify/list.mdx | 4 + .../app-connections/netlify/update.mdx | 8 + .../endpoints/secret-syncs/netlify/create.mdx | 4 + .../endpoints/secret-syncs/netlify/delete.mdx | 4 + .../secret-syncs/netlify/get-by-id.mdx | 4 + .../secret-syncs/netlify/get-by-name.mdx | 4 + .../secret-syncs/netlify/import-secrets.mdx | 4 + .../endpoints/secret-syncs/netlify/list.mdx | 4 + .../secret-syncs/netlify/remove-secrets.mdx | 4 + .../secret-syncs/netlify/sync-secrets.mdx | 4 + .../endpoints/secret-syncs/netlify/update.mdx | 4 + docs/docs.json | 27 ++ .../netlify/app-connection-create-api-key.png | Bin 0 -> 931961 bytes .../netlify/app-connection-create-form.png | Bin 0 -> 551735 bytes .../netlify/app-connection-dashboard.png | Bin 0 -> 806930 bytes .../netlify/app-connection-form.png | Bin 0 -> 505910 bytes .../netlify/app-connection-generated.png | Bin 0 -> 705978 bytes .../netlify/app-connection-key-generated.png | Bin 0 -> 501968 bytes .../netlify/app-connection-option.png | Bin 0 -> 465721 bytes .../netlify/app-connection-profile.png | Bin 0 -> 944726 bytes .../secret-syncs/netlify/select-option.png | Bin 0 -> 594506 bytes .../secret-syncs/netlify/sync-created.png | Bin 0 -> 1123704 bytes .../secret-syncs/netlify/sync-destination.png | Bin 0 -> 714466 bytes .../secret-syncs/netlify/sync-details.png | Bin 0 -> 673633 bytes .../secret-syncs/netlify/sync-options.png | Bin 0 -> 738490 bytes .../secret-syncs/netlify/sync-review.png | Bin 0 -> 704822 bytes .../secret-syncs/netlify/sync-source.png | Bin 0 -> 655895 bytes docs/integrations/app-connections/netlify.mdx | 102 +++++++ docs/integrations/secret-syncs/netlify.mdx | 161 +++++++++++ .../secret-syncs/forms/EditSecretSyncForm.tsx | 2 +- .../NetlifySyncFields.tsx | 144 ++++++++++ .../SecretSyncDestinationFields.tsx | 3 + .../SecretSyncOptionsFields.tsx | 1 + .../NetlifySyncReviewFields.tsx | 22 ++ .../SecretSyncReviewFields.tsx | 4 + .../netlify-sync-destination-schema.ts | 26 ++ .../forms/schemas/secret-sync-schema.ts | 2 + frontend/src/helpers/appConnections.ts | 6 + frontend/src/helpers/secretSyncs.ts | 5 + .../src/hooks/api/appConnections/enums.ts | 1 + .../hooks/api/appConnections/netlify/index.ts | 2 + .../api/appConnections/netlify/queries.tsx | 65 +++++ .../hooks/api/appConnections/netlify/types.ts | 9 + .../api/appConnections/types/app-options.ts | 6 + .../hooks/api/appConnections/types/index.ts | 3 + .../types/netlify-connection.ts | 14 + frontend/src/hooks/api/secretSyncs/enums.ts | 1 + .../src/hooks/api/secretSyncs/types/index.ts | 2 + .../api/secretSyncs/types/netlify-sync.ts | 20 ++ .../AppConnectionForm/AppConnectionForm.tsx | 3 + .../NetlifyConnectionForm.tsx | 136 +++++++++ .../NetlifySyncDestinationCol.tsx | 14 + .../SecretSyncDestinationCol.tsx | 3 + .../SecretSyncTable/helpers/index.ts | 4 + .../NetlifySyncDestinationSection.tsx | 22 ++ .../SecretSyncDestinatonSection.tsx | 4 + .../SecretSyncOptionsSection.tsx | 1 + 90 files changed, 1747 insertions(+), 2 deletions(-) create mode 100644 backend/src/server/routes/v1/app-connection-routers/netlify-connection-router.ts create mode 100644 backend/src/server/routes/v1/secret-sync-routers/netlify-sync-router.ts create mode 100644 backend/src/services/app-connection/netlify/index.ts create mode 100644 backend/src/services/app-connection/netlify/netlify-connection-constants.ts create mode 100644 backend/src/services/app-connection/netlify/netlify-connection-fns.ts create mode 100644 backend/src/services/app-connection/netlify/netlify-connection-public-client.ts create mode 100644 backend/src/services/app-connection/netlify/netlify-connection-schemas.ts create mode 100644 backend/src/services/app-connection/netlify/netlify-connection-service.ts create mode 100644 backend/src/services/app-connection/netlify/netlify-connection-types.ts create mode 100644 backend/src/services/secret-sync/netlify/index.ts create mode 100644 backend/src/services/secret-sync/netlify/netlify-sync-constants.ts create mode 100644 backend/src/services/secret-sync/netlify/netlify-sync-fns.ts create mode 100644 backend/src/services/secret-sync/netlify/netlify-sync-schemas.ts create mode 100644 backend/src/services/secret-sync/netlify/netlify-sync-types.ts create mode 100644 docs/api-reference/endpoints/app-connections/netlify/available.mdx create mode 100644 docs/api-reference/endpoints/app-connections/netlify/create.mdx create mode 100644 docs/api-reference/endpoints/app-connections/netlify/delete.mdx create mode 100644 docs/api-reference/endpoints/app-connections/netlify/get-by-id.mdx create mode 100644 docs/api-reference/endpoints/app-connections/netlify/get-by-name.mdx create mode 100644 docs/api-reference/endpoints/app-connections/netlify/list.mdx create mode 100644 docs/api-reference/endpoints/app-connections/netlify/update.mdx create mode 100644 docs/api-reference/endpoints/secret-syncs/netlify/create.mdx create mode 100644 docs/api-reference/endpoints/secret-syncs/netlify/delete.mdx create mode 100644 docs/api-reference/endpoints/secret-syncs/netlify/get-by-id.mdx create mode 100644 docs/api-reference/endpoints/secret-syncs/netlify/get-by-name.mdx create mode 100644 docs/api-reference/endpoints/secret-syncs/netlify/import-secrets.mdx create mode 100644 docs/api-reference/endpoints/secret-syncs/netlify/list.mdx create mode 100644 docs/api-reference/endpoints/secret-syncs/netlify/remove-secrets.mdx create mode 100644 docs/api-reference/endpoints/secret-syncs/netlify/sync-secrets.mdx create mode 100644 docs/api-reference/endpoints/secret-syncs/netlify/update.mdx create mode 100644 docs/images/app-connections/netlify/app-connection-create-api-key.png create mode 100644 docs/images/app-connections/netlify/app-connection-create-form.png create mode 100644 docs/images/app-connections/netlify/app-connection-dashboard.png create mode 100644 docs/images/app-connections/netlify/app-connection-form.png create mode 100644 docs/images/app-connections/netlify/app-connection-generated.png create mode 100644 docs/images/app-connections/netlify/app-connection-key-generated.png create mode 100644 docs/images/app-connections/netlify/app-connection-option.png create mode 100644 docs/images/app-connections/netlify/app-connection-profile.png create mode 100644 docs/images/secret-syncs/netlify/select-option.png create mode 100644 docs/images/secret-syncs/netlify/sync-created.png create mode 100644 docs/images/secret-syncs/netlify/sync-destination.png create mode 100644 docs/images/secret-syncs/netlify/sync-details.png create mode 100644 docs/images/secret-syncs/netlify/sync-options.png create mode 100644 docs/images/secret-syncs/netlify/sync-review.png create mode 100644 docs/images/secret-syncs/netlify/sync-source.png create mode 100644 docs/integrations/app-connections/netlify.mdx create mode 100644 docs/integrations/secret-syncs/netlify.mdx create mode 100644 frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/NetlifySyncFields.tsx create mode 100644 frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/NetlifySyncReviewFields.tsx create mode 100644 frontend/src/components/secret-syncs/forms/schemas/netlify-sync-destination-schema.ts create mode 100644 frontend/src/hooks/api/appConnections/netlify/index.ts create mode 100644 frontend/src/hooks/api/appConnections/netlify/queries.tsx create mode 100644 frontend/src/hooks/api/appConnections/netlify/types.ts create mode 100644 frontend/src/hooks/api/appConnections/types/netlify-connection.ts create mode 100644 frontend/src/hooks/api/secretSyncs/types/netlify-sync.ts create mode 100644 frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/NetlifyConnectionForm.tsx create mode 100644 frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/NetlifySyncDestinationCol.tsx create mode 100644 frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/NetlifySyncDestinationSection.tsx diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 69653a0be..72b283a0f 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2302,6 +2302,9 @@ export const AppConnections = { DIGITAL_OCEAN_APP_PLATFORM: { apiToken: "The API token used to authenticate with Digital Ocean App Platform." }, + NETLIFY: { + accessToken: "The Access token used to authenticate with Netlify." + }, OKTA: { instanceUrl: "The URL used to access your Okta organization.", apiToken: "The API token used to authenticate with Okta." @@ -2533,6 +2536,13 @@ export const SecretSyncs = { workspaceSlug: "The Bitbucket Workspace slug to sync secrets to.", repositorySlug: "The Bitbucket Repository slug to sync secrets to.", environmentId: "The Bitbucket Deployment Environment uuid to sync secrets to." + }, + NETLIFY: { + accountId: "The ID of the Netlify account to sync secrets to.", + accountName: "The name of the Netlify account to sync secrets to.", + siteName: "The name of the Netlify site to sync secrets to.", + siteId: "The ID of the Netlify site to sync secrets to.", + context: "The Netlify context to sync secrets to." } } }; diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts index 8468d7305..f184f2001 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts @@ -75,6 +75,10 @@ import { import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap"; import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql"; import { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql"; +import { + NetlifyConnectionListItemSchema, + SanitizedNetlifyConnectionSchema +} from "@app/services/app-connection/netlify"; import { OktaConnectionListItemSchema, SanitizedOktaConnectionSchema } from "@app/services/app-connection/okta"; import { PostgresConnectionListItemSchema, @@ -145,6 +149,7 @@ const SanitizedAppConnectionSchema = z.union([ ...SanitizedChecklyConnectionSchema.options, ...SanitizedSupabaseConnectionSchema.options, ...SanitizedDigitalOceanConnectionSchema.options, + ...SanitizedNetlifyConnectionSchema.options, ...SanitizedOktaConnectionSchema.options ]); @@ -184,6 +189,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ ChecklyConnectionListItemSchema, SupabaseConnectionListItemSchema, DigitalOceanConnectionListItemSchema, + NetlifyConnectionListItemSchema, OktaConnectionListItemSchema ]); diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index 6744e46b3..28e3b4e49 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -26,6 +26,7 @@ import { registerHumanitecConnectionRouter } from "./humanitec-connection-router import { registerLdapConnectionRouter } from "./ldap-connection-router"; import { registerMsSqlConnectionRouter } from "./mssql-connection-router"; import { registerMySqlConnectionRouter } from "./mysql-connection-router"; +import { registerNetlifyConnectionRouter } from "./netlify-connection-router"; import { registerOktaConnectionRouter } from "./okta-connection-router"; import { registerPostgresConnectionRouter } from "./postgres-connection-router"; import { registerRailwayConnectionRouter } from "./railway-connection-router"; @@ -76,5 +77,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record { + registerAppConnectionEndpoints({ + app: AppConnection.Netlify, + server, + sanitizedResponseSchema: SanitizedNetlifyConnectionSchema, + createSchema: CreateNetlifyConnectionSchema, + updateSchema: UpdateNetlifyConnectionSchema + }); + + // The below endpoints are not exposed and for Infisical App use + server.route({ + method: "GET", + url: `/:connectionId/accounts`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid() + }), + response: { + 200: z.object({ + accounts: z + .object({ + name: z.string(), + id: z.string() + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId } = req.params; + + const accounts = await server.services.appConnection.netlify.listAccounts(connectionId, req.permission); + + return { accounts }; + } + }); + + server.route({ + method: "GET", + url: `/:connectionId/accounts/:accountId/sites`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid(), + accountId: z.string() + }), + response: { + 200: z.object({ + sites: z + .object({ + name: z.string(), + id: z.string() + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId, accountId } = req.params; + + const sites = await server.services.appConnection.netlify.listSites(connectionId, req.permission, accountId); + + return { sites }; + } + }); +}; diff --git a/backend/src/server/routes/v1/secret-sync-routers/index.ts b/backend/src/server/routes/v1/secret-sync-routers/index.ts index 03cf9d053..fed56277e 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/index.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/index.ts @@ -21,6 +21,7 @@ import { registerGitLabSyncRouter } from "./gitlab-sync-router"; import { registerHCVaultSyncRouter } from "./hc-vault-sync-router"; import { registerHerokuSyncRouter } from "./heroku-sync-router"; import { registerHumanitecSyncRouter } from "./humanitec-sync-router"; +import { registerNetlifySyncRouter } from "./netlify-sync-router"; import { registerRailwaySyncRouter } from "./railway-sync-router"; import { registerRenderSyncRouter } from "./render-sync-router"; import { registerSupabaseSyncRouter } from "./supabase-sync-router"; @@ -61,5 +62,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record + registerSyncSecretsEndpoints({ + destination: SecretSync.Netlify, + server, + responseSchema: NetlifySyncSchema, + createSchema: CreateNetlifySyncSchema, + updateSchema: UpdateNetlifySyncSchema + }); diff --git a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts index 0388ab53e..71e8b2cca 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts @@ -44,6 +44,7 @@ import { GitLabSyncListItemSchema, GitLabSyncSchema } from "@app/services/secret import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault"; import { HerokuSyncListItemSchema, HerokuSyncSchema } from "@app/services/secret-sync/heroku"; import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec"; +import { NetlifySyncListItemSchema, NetlifySyncSchema } from "@app/services/secret-sync/netlify"; import { RailwaySyncListItemSchema, RailwaySyncSchema } from "@app/services/secret-sync/railway/railway-sync-schemas"; import { RenderSyncListItemSchema, RenderSyncSchema } from "@app/services/secret-sync/render/render-sync-schemas"; import { SupabaseSyncListItemSchema, SupabaseSyncSchema } from "@app/services/secret-sync/supabase"; @@ -82,6 +83,7 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [ RailwaySyncSchema, ChecklySyncSchema, DigitalOceanAppPlatformSyncSchema, + NetlifySyncSchema, BitbucketSyncSchema ]); @@ -114,6 +116,7 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [ RailwaySyncListItemSchema, ChecklySyncListItemSchema, SupabaseSyncListItemSchema, + NetlifySyncListItemSchema, BitbucketSyncListItemSchema ]); diff --git a/backend/src/services/app-connection/app-connection-enums.ts b/backend/src/services/app-connection/app-connection-enums.ts index 833522cbb..263cbdd9b 100644 --- a/backend/src/services/app-connection/app-connection-enums.ts +++ b/backend/src/services/app-connection/app-connection-enums.ts @@ -34,6 +34,7 @@ export enum AppConnection { Checkly = "checkly", Supabase = "supabase", DigitalOcean = "digital-ocean", + Netlify = "netlify", Okta = "okta" } diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index d72fca654..9ffc358b6 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -97,6 +97,7 @@ import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnection import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql"; import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums"; import { getMySqlConnectionListItem } from "./mysql/mysql-connection-fns"; +import { getNetlifyConnectionListItem, validateNetlifyConnectionCredentials } from "./netlify"; import { getOktaConnectionListItem, OktaConnectionMethod, validateOktaConnectionCredentials } from "./okta"; import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres"; import { getRailwayConnectionListItem, validateRailwayConnectionCredentials } from "./railway"; @@ -163,6 +164,7 @@ export const listAppConnectionOptions = () => { getChecklyConnectionListItem(), getSupabaseConnectionListItem(), getDigitalOceanConnectionListItem(), + getNetlifyConnectionListItem(), getOktaConnectionListItem() ].sort((a, b) => a.name.localeCompare(b.name)); }; @@ -251,7 +253,8 @@ export const validateAppConnectionCredentials = async ( [AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Supabase]: validateSupabaseConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.DigitalOcean]: validateDigitalOceanConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.Okta]: validateOktaConnectionCredentials as TAppConnectionCredentialsValidator + [AppConnection.Okta]: validateOktaConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.Netlify]: validateNetlifyConnectionCredentials as TAppConnectionCredentialsValidator }; return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection, gatewayService); @@ -381,6 +384,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record< [AppConnection.Checkly]: platformManagedCredentialsNotSupported, [AppConnection.Supabase]: platformManagedCredentialsNotSupported, [AppConnection.DigitalOcean]: platformManagedCredentialsNotSupported, + [AppConnection.Netlify]: platformManagedCredentialsNotSupported, [AppConnection.Okta]: platformManagedCredentialsNotSupported }; diff --git a/backend/src/services/app-connection/app-connection-maps.ts b/backend/src/services/app-connection/app-connection-maps.ts index 603d30ccd..155b68ce1 100644 --- a/backend/src/services/app-connection/app-connection-maps.ts +++ b/backend/src/services/app-connection/app-connection-maps.ts @@ -36,6 +36,7 @@ export const APP_CONNECTION_NAME_MAP: Record = { [AppConnection.Checkly]: "Checkly", [AppConnection.Supabase]: "Supabase", [AppConnection.DigitalOcean]: "DigitalOcean App Platform", + [AppConnection.Netlify]: "Netlify", [AppConnection.Okta]: "Okta" }; @@ -75,5 +76,6 @@ export const APP_CONNECTION_PLAN_MAP: Record { + return { + name: "Netlify" as const, + app: AppConnection.Netlify as const, + methods: Object.values(NetlifyConnectionMethod) + }; +}; + +export const validateNetlifyConnectionCredentials = async (config: TNetlifyConnectionConfig) => { + try { + await NetlifyPublicAPI.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; +}; diff --git a/backend/src/services/app-connection/netlify/netlify-connection-public-client.ts b/backend/src/services/app-connection/netlify/netlify-connection-public-client.ts new file mode 100644 index 000000000..d1328539b --- /dev/null +++ b/backend/src/services/app-connection/netlify/netlify-connection-public-client.ts @@ -0,0 +1,261 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable no-await-in-loop */ +/* eslint-disable class-methods-use-this */ +import { AxiosInstance, AxiosRequestConfig, AxiosResponse, HttpStatusCode, isAxiosError } from "axios"; + +import { createRequestClient } from "@app/lib/config/request"; +import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; + +import { NetlifyConnectionMethod } from "./netlify-connection-constants"; +import { TNetlifyAccount, TNetlifyConnectionConfig, TNetlifySite, TNetlifyVariable } from "./netlify-connection-types"; + +export function getNetlifyAuthHeaders(connection: TNetlifyConnectionConfig): Record { + switch (connection.method) { + case NetlifyConnectionMethod.AccessToken: + return { + Authorization: `Bearer ${connection.credentials.accessToken}` + }; + default: + throw new Error(`Unsupported Netlify connection method`); + } +} + +export function getNetlifyRatelimiter(response: AxiosResponse): { + maxAttempts: number; + isRatelimited: boolean; + wait: () => Promise; +} { + const wait = (seconds: number = 60) => { + return new Promise((res) => { + setTimeout(res, seconds * 1000); + }); + }; + + let remaining = parseInt(response.headers["X-RateLimit-Remaining"] as string, 10); + let isRatelimited = response.status === HttpStatusCode.TooManyRequests; + + if (isRatelimited) { + if (Math.round(remaining) > 0) { + isRatelimited = true; + remaining += 1; // Jitter to ensure we wait at least 1 second + } else { + remaining = 60; + } + } + + return { + isRatelimited, + wait: () => wait(remaining), + maxAttempts: 3 + }; +} + +type NetlifyParams = { + account_id: string; + context_name?: string; + site_id?: string; +}; + +class NetlifyPublicClient { + private client: AxiosInstance; + + constructor() { + this.client = createRequestClient({ + baseURL: `${IntegrationUrls.NETLIFY_API_URL}/api/v1`, + headers: { + "Content-Type": "application/json" + } + }); + } + + async send(connection: TNetlifyConnectionConfig, config: AxiosRequestConfig, retryAttempt = 0): Promise { + const response = await this.client.request({ + ...config, + timeout: 1000 * 60, // 60 seconds timeout + validateStatus: (status) => (status >= 200 && status < 300) || status === HttpStatusCode.TooManyRequests, + headers: getNetlifyAuthHeaders(connection) + }); + const limiter = getNetlifyRatelimiter(response); + + if (limiter.isRatelimited && retryAttempt <= limiter.maxAttempts) { + await limiter.wait(); + return this.send(connection, config, retryAttempt + 1); + } + + return response.data; + } + + healthcheck(connection: TNetlifyConnectionConfig) { + switch (connection.method) { + case NetlifyConnectionMethod.AccessToken: + return this.getNetlifyAccounts(connection); + default: + throw new Error(`Unsupported Netlify connection method`); + } + } + + async getVariables( + connection: TNetlifyConnectionConfig, + { account_id, ...params }: NetlifyParams, + limit: number = 50, + page: number = 1 + ) { + const res = await this.send(connection, { + method: "GET", + url: `/accounts/${account_id}/env`, + params: { + ...params, + limit, + page + } + }); + + return res; + } + + async createVariable( + connection: TNetlifyConnectionConfig, + { account_id, ...params }: NetlifyParams, + ...variables: TNetlifyVariable[] + ) { + const res = await this.send(connection, { + method: "POST", + url: `/accounts/${account_id}/env`, + data: variables, + params + }); + + return res; + } + + async updateVariableValue( + connection: TNetlifyConnectionConfig, + { account_id, ...params }: NetlifyParams, + variable: TNetlifyVariable + ) { + const res = await this.send(connection, { + method: "PATCH", + url: `/accounts/${account_id}/env/${variable.key}`, + data: variable, + params + }); + + return res; + } + + async updateVariable( + connection: TNetlifyConnectionConfig, + { account_id, ...params }: NetlifyParams, + variable: TNetlifyVariable + ) { + const res = await this.send(connection, { + method: "PUT", + url: `/accounts/${account_id}/env/${variable.key}`, + data: variable, + params + }); + + return res; + } + + async getVariable( + connection: TNetlifyConnectionConfig, + { account_id, ...params }: NetlifyParams, + variable: Pick + ) { + try { + const res = await this.send(connection, { + method: "GET", + url: `/accounts/${account_id}/env/${variable.key}`, + params + }); + + return res; + } catch (error) { + if (isAxiosError(error) && error.response?.status === HttpStatusCode.NotFound) { + return null; + } + + throw error; + } + } + + async upsertVariable(connection: TNetlifyConnectionConfig, params: NetlifyParams, variable: TNetlifyVariable) { + const res = await this.getVariable(connection, params, variable); + + if (!res) { + return this.createVariable(connection, params, variable); + } + + if (res.is_secret) { + await this.deleteVariable(connection, params, variable); + return this.createVariable(connection, params, variable); + } + + return this.updateVariable(connection, params, variable); + } + + async deleteVariable( + connection: TNetlifyConnectionConfig, + { account_id, ...params }: NetlifyParams, + variable: Pick + ) { + try { + const res = await this.send(connection, { + method: "DELETE", + url: `/accounts/${account_id}/env/${variable.key}`, + params + }); + + return res; + } catch (error) { + if (isAxiosError(error) && error.response?.status === HttpStatusCode.NotFound) { + return null; + } + + throw error; + } + } + + async deleteVariableValue( + connection: TNetlifyConnectionConfig, + { account_id, value_id, ...params }: NetlifyParams & { value_id: string }, + variable: Pick + ) { + try { + const res = await this.send(connection, { + method: "DELETE", + url: `/accounts/${account_id}/${variable.key}/value/${value_id}`, + params + }); + + return res; + } catch (error) { + if (isAxiosError(error) && error.response?.status === HttpStatusCode.NotFound) { + return null; + } + + throw error; + } + } + + async getSites(connection: TNetlifyConnectionConfig, accountId: string) { + const res = await this.send(connection, { + method: "GET", + url: `/${accountId}/sites` + }); + + return res; + } + + async getNetlifyAccounts(connection: TNetlifyConnectionConfig) { + const res = await this.send(connection, { + method: "GET", + url: `/accounts` + }); + + return res; + } +} + +export const NetlifyPublicAPI = new NetlifyPublicClient(); diff --git a/backend/src/services/app-connection/netlify/netlify-connection-schemas.ts b/backend/src/services/app-connection/netlify/netlify-connection-schemas.ts new file mode 100644 index 000000000..45fb89760 --- /dev/null +++ b/backend/src/services/app-connection/netlify/netlify-connection-schemas.ts @@ -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 { NetlifyConnectionMethod } from "./netlify-connection-constants"; + +export const NetlifyConnectionMethodSchema = z + .nativeEnum(NetlifyConnectionMethod) + .describe(AppConnections.CREATE(AppConnection.Netlify).method); + +export const NetlifyConnectionAccessTokenCredentialsSchema = z.object({ + accessToken: z + .string() + .trim() + .min(1, "Access Token required") + .max(255) + .describe(AppConnections.CREDENTIALS.NETLIFY.accessToken) +}); + +const BaseNetlifyConnectionSchema = BaseAppConnectionSchema.extend({ + app: z.literal(AppConnection.Netlify) +}); + +export const NetlifyConnectionSchema = BaseNetlifyConnectionSchema.extend({ + method: NetlifyConnectionMethodSchema, + credentials: NetlifyConnectionAccessTokenCredentialsSchema +}); + +export const SanitizedNetlifyConnectionSchema = z.discriminatedUnion("method", [ + BaseNetlifyConnectionSchema.extend({ + method: NetlifyConnectionMethodSchema, + credentials: NetlifyConnectionAccessTokenCredentialsSchema.pick({}) + }) +]); + +export const ValidateNetlifyConnectionCredentialsSchema = z.discriminatedUnion("method", [ + z.object({ + method: NetlifyConnectionMethodSchema, + credentials: NetlifyConnectionAccessTokenCredentialsSchema.describe( + AppConnections.CREATE(AppConnection.Netlify).credentials + ) + }) +]); + +export const CreateNetlifyConnectionSchema = ValidateNetlifyConnectionCredentialsSchema.and( + GenericCreateAppConnectionFieldsSchema(AppConnection.Netlify) +); + +export const UpdateNetlifyConnectionSchema = z + .object({ + credentials: NetlifyConnectionAccessTokenCredentialsSchema.optional().describe( + AppConnections.UPDATE(AppConnection.Netlify).credentials + ) + }) + .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Netlify)); + +export const NetlifyConnectionListItemSchema = z.object({ + name: z.literal("Netlify"), + app: z.literal(AppConnection.Netlify), + methods: z.nativeEnum(NetlifyConnectionMethod).array() +}); diff --git a/backend/src/services/app-connection/netlify/netlify-connection-service.ts b/backend/src/services/app-connection/netlify/netlify-connection-service.ts new file mode 100644 index 000000000..c2cf65e24 --- /dev/null +++ b/backend/src/services/app-connection/netlify/netlify-connection-service.ts @@ -0,0 +1,42 @@ +import { logger } from "@app/lib/logger"; +import { OrgServiceActor } from "@app/lib/types"; + +import { AppConnection } from "../app-connection-enums"; +import { NetlifyPublicAPI } from "./netlify-connection-public-client"; +import { TNetlifyConnection } from "./netlify-connection-types"; + +type TGetAppConnectionFunc = ( + app: AppConnection, + connectionId: string, + actor: OrgServiceActor +) => Promise; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const netlifyConnectionService = (getAppConnection: TGetAppConnectionFunc) => { + const listAccounts = async (connectionId: string, actor: OrgServiceActor) => { + const appConnection = await getAppConnection(AppConnection.Netlify, connectionId, actor); + try { + const accounts = await NetlifyPublicAPI.getNetlifyAccounts(appConnection); + return accounts; + } catch (error) { + logger.error(error, "Failed to list accounts on Netlify"); + return []; + } + }; + + const listSites = async (connectionId: string, actor: OrgServiceActor, accountId: string) => { + const appConnection = await getAppConnection(AppConnection.Netlify, connectionId, actor); + try { + const sites = await NetlifyPublicAPI.getSites(appConnection, accountId); + return sites; + } catch (error) { + logger.error(error, "Failed to list sites on Netlify"); + return []; + } + }; + + return { + listAccounts, + listSites + }; +}; diff --git a/backend/src/services/app-connection/netlify/netlify-connection-types.ts b/backend/src/services/app-connection/netlify/netlify-connection-types.ts new file mode 100644 index 000000000..dc32f48d0 --- /dev/null +++ b/backend/src/services/app-connection/netlify/netlify-connection-types.ts @@ -0,0 +1,51 @@ +import z from "zod"; + +import { DiscriminativePick } from "@app/lib/types"; + +import { AppConnection } from "../app-connection-enums"; +import { + CreateNetlifyConnectionSchema, + NetlifyConnectionSchema, + ValidateNetlifyConnectionCredentialsSchema +} from "./netlify-connection-schemas"; + +export type TNetlifyConnection = z.infer; + +export type TNetlifyConnectionInput = z.infer & { + app: AppConnection.Netlify; +}; + +export type TValidateNetlifyConnectionCredentialsSchema = typeof ValidateNetlifyConnectionCredentialsSchema; + +export type TNetlifyConnectionConfig = DiscriminativePick & { + orgId: string; +}; + +export type TNetlifyVariable = { + key: string; + id?: string; // ID of the variable (present in responses) + created_at?: string; + updated_at?: string; + is_secret?: boolean; + scopes?: ("builds" | "functions" | "runtime" | "post_processing")[]; + values: TNetlifyVariableValue[]; +}; + +export type TNetlifyVariableValue = { + id?: string; + context?: string; // "all", "dev", "branch-deploy", etc. + value?: string; // Omitted in response if `is_secret` is true + site_id?: string; // Optional: overrides at site-level + created_at?: string; + updated_at?: string; +}; + +export type TNetlifyAccount = { + id: string; + name: string; +}; + +export type TNetlifySite = { + id: string; + name: string; +}; diff --git a/backend/src/services/secret-sync/netlify/index.ts b/backend/src/services/secret-sync/netlify/index.ts new file mode 100644 index 000000000..2730a7757 --- /dev/null +++ b/backend/src/services/secret-sync/netlify/index.ts @@ -0,0 +1,4 @@ +export * from "./netlify-sync-constants"; +export * from "./netlify-sync-fns"; +export * from "./netlify-sync-schemas"; +export * from "./netlify-sync-types"; diff --git a/backend/src/services/secret-sync/netlify/netlify-sync-constants.ts b/backend/src/services/secret-sync/netlify/netlify-sync-constants.ts new file mode 100644 index 000000000..c07f4c77a --- /dev/null +++ b/backend/src/services/secret-sync/netlify/netlify-sync-constants.ts @@ -0,0 +1,19 @@ +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 NETLIFY_SYNC_LIST_OPTION: TSecretSyncListItem = { + name: "Netlify", + destination: SecretSync.Netlify, + connection: AppConnection.Netlify, + canImportSecrets: true +}; + +export enum NetlifySyncContext { + All = "all", + DeployPreview = "deploy-preview", + Production = "production", + BranchDeploy = "branch-deploy", + Dev = "dev", + Branch = "branch" +} diff --git a/backend/src/services/secret-sync/netlify/netlify-sync-fns.ts b/backend/src/services/secret-sync/netlify/netlify-sync-fns.ts new file mode 100644 index 000000000..23445623d --- /dev/null +++ b/backend/src/services/secret-sync/netlify/netlify-sync-fns.ts @@ -0,0 +1,125 @@ +/* eslint-disable no-continue */ +import { NetlifyPublicAPI } from "@app/services/app-connection/netlify/netlify-connection-public-client"; +import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; +import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; + +import { SecretSyncError } from "../secret-sync-errors"; +import type { TNetlifySyncWithCredentials } from "./netlify-sync-types"; + +export const NetlifySyncFns = { + async getSecrets(secretSync: TNetlifySyncWithCredentials): Promise { + const config = secretSync.destinationConfig; + + const params = { + account_id: config.accountId, + context_name: config.context, // Only used in the case of getVariables + site_id: config.siteId + }; + + const variables = await NetlifyPublicAPI.getVariables(secretSync.connection, params); + + const map = {} as TSecretMap; + + for (const variable of variables) { + if (variable.is_secret) continue; + + const latest = variable.values.sort((a, b) => Number(b.created_at) - Number(a.created_at))[0]; + + map[variable.key] = { + value: latest?.value ?? "" + }; + } + + return map; + }, + + async syncSecrets(secretSync: TNetlifySyncWithCredentials, secretMap: TSecretMap) { + const { + environment, + syncOptions: { disableSecretDeletion, keySchema } + } = secretSync; + + const config = secretSync.destinationConfig; + + const params = { + account_id: config.accountId, + context_name: config.context, + site_id: config.siteId + }; + + const variables = await NetlifyPublicAPI.getVariables(secretSync.connection, params); + + const existing = Object.fromEntries(variables.map((variable) => [variable.key, variable])); + + for await (const key of Object.keys(secretMap)) { + try { + const entry = secretMap[key]; + + await NetlifyPublicAPI.upsertVariable(secretSync.connection, params, { + key, + is_secret: false, + values: [ + { + value: entry.value, + context: config.context + } + ] + }); + } catch (error) { + throw new SecretSyncError({ + error, + secretKey: key + }); + } + } + + if (disableSecretDeletion) return; + + for await (const key of Object.keys(existing)) { + try { + // eslint-disable-next-line no-continue, @typescript-eslint/no-unsafe-argument + if (!matchesSchema(key, environment?.slug || "", keySchema)) continue; + + if (!secretMap[key]) { + await NetlifyPublicAPI.deleteVariable(secretSync.connection, params, { + key + }); + } + } catch (error) { + throw new SecretSyncError({ + error, + secretKey: key + }); + } + } + }, + + async removeSecrets(secretSync: TNetlifySyncWithCredentials, secretMap: TSecretMap) { + const config = secretSync.destinationConfig; + + const params = { + account_id: config.accountId, + context_name: config.context, + site_id: config.siteId + }; + + const variables = await NetlifyPublicAPI.getVariables(secretSync.connection, params); + + const existingSecrets = Object.fromEntries(variables.map((variable) => [variable.key, variable])); + + for await (const secret of Object.keys(existingSecrets)) { + try { + if (secret in secretMap) { + await NetlifyPublicAPI.deleteVariable(secretSync.connection, params, { + key: secret + }); + } + } catch (error) { + throw new SecretSyncError({ + error, + secretKey: secret + }); + } + } + } +}; diff --git a/backend/src/services/secret-sync/netlify/netlify-sync-schemas.ts b/backend/src/services/secret-sync/netlify/netlify-sync-schemas.ts new file mode 100644 index 000000000..cd9a985c9 --- /dev/null +++ b/backend/src/services/secret-sync/netlify/netlify-sync-schemas.ts @@ -0,0 +1,68 @@ +import { z } from "zod"; + +import { SecretSyncs } from "@app/lib/api-docs"; +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"; + +import { NetlifySyncContext } from "./netlify-sync-constants"; + +const NetlifySyncDestinationConfigSchema = z.object({ + accountId: z + .string() + .min(1, "Account ID is required") + .max(255, "Account ID must be less than 255 characters") + .describe(SecretSyncs.DESTINATION_CONFIG.NETLIFY.accountId), + accountName: z + .string() + .min(1, "Account Name is required") + .max(255, "Account Name must be less than 255 characters") + .optional() + .describe(SecretSyncs.DESTINATION_CONFIG.NETLIFY.accountName), + siteId: z + .string() + .min(1, "Site ID is required") + .max(255, "Site ID must be less than 255 characters") + .optional() + .describe(SecretSyncs.DESTINATION_CONFIG.NETLIFY.siteId), + siteName: z + .string() + .min(1, "Site Name is required") + .max(255, "Site Name must be less than 255 characters") + .optional() + .describe(SecretSyncs.DESTINATION_CONFIG.NETLIFY.siteName), + context: z.nativeEnum(NetlifySyncContext).optional().describe(SecretSyncs.DESTINATION_CONFIG.NETLIFY.context) +}); + +const NetlifySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; + +export const NetlifySyncSchema = BaseSecretSyncSchema(SecretSync.Netlify, NetlifySyncOptionsConfig).extend({ + destination: z.literal(SecretSync.Netlify), + destinationConfig: NetlifySyncDestinationConfigSchema +}); + +export const CreateNetlifySyncSchema = GenericCreateSecretSyncFieldsSchema( + SecretSync.Netlify, + NetlifySyncOptionsConfig +).extend({ + destinationConfig: NetlifySyncDestinationConfigSchema +}); + +export const UpdateNetlifySyncSchema = GenericUpdateSecretSyncFieldsSchema( + SecretSync.Netlify, + NetlifySyncOptionsConfig +).extend({ + destinationConfig: NetlifySyncDestinationConfigSchema.optional() +}); + +export const NetlifySyncListItemSchema = z.object({ + name: z.literal("Netlify"), + connection: z.literal(AppConnection.Netlify), + destination: z.literal(SecretSync.Netlify), + canImportSecrets: z.literal(true) +}); diff --git a/backend/src/services/secret-sync/netlify/netlify-sync-types.ts b/backend/src/services/secret-sync/netlify/netlify-sync-types.ts new file mode 100644 index 000000000..2cef04686 --- /dev/null +++ b/backend/src/services/secret-sync/netlify/netlify-sync-types.ts @@ -0,0 +1,15 @@ +import z from "zod"; + +import { TNetlifyConnection } from "@app/services/app-connection/netlify"; + +import { CreateNetlifySyncSchema, NetlifySyncListItemSchema, NetlifySyncSchema } from "./netlify-sync-schemas"; + +export type TNetlifySyncListItem = z.infer; + +export type TNetlifySync = z.infer; + +export type TNetlifySyncInput = z.infer; + +export type TNetlifySyncWithCredentials = TNetlifySync & { + connection: TNetlifyConnection; +}; diff --git a/backend/src/services/secret-sync/secret-sync-enums.ts b/backend/src/services/secret-sync/secret-sync-enums.ts index 55859200a..fe0dc9f56 100644 --- a/backend/src/services/secret-sync/secret-sync-enums.ts +++ b/backend/src/services/secret-sync/secret-sync-enums.ts @@ -27,6 +27,7 @@ export enum SecretSync { Railway = "railway", Checkly = "checkly", DigitalOceanAppPlatform = "digital-ocean-app-platform", + Netlify = "netlify", Bitbucket = "bitbucket" } diff --git a/backend/src/services/secret-sync/secret-sync-fns.ts b/backend/src/services/secret-sync/secret-sync-fns.ts index 6fa46f1e6..3ff7cefbc 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -48,6 +48,7 @@ import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault"; import { HEROKU_SYNC_LIST_OPTION, HerokuSyncFns } from "./heroku"; import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec"; import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns"; +import { NETLIFY_SYNC_LIST_OPTION, NetlifySyncFns } from "./netlify"; import { RAILWAY_SYNC_LIST_OPTION } from "./railway/railway-sync-constants"; import { RailwaySyncFns } from "./railway/railway-sync-fns"; import { RENDER_SYNC_LIST_OPTION, RenderSyncFns } from "./render"; @@ -88,6 +89,7 @@ const SECRET_SYNC_LIST_OPTIONS: Record = { [SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION, [SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION, [SecretSync.DigitalOceanAppPlatform]: DIGITAL_OCEAN_APP_PLATFORM_SYNC_LIST_OPTION, + [SecretSync.Netlify]: NETLIFY_SYNC_LIST_OPTION, [SecretSync.Bitbucket]: BITBUCKET_SYNC_LIST_OPTION }; @@ -269,6 +271,8 @@ export const SecretSyncFns = { return SupabaseSyncFns.syncSecrets(secretSync, schemaSecretMap); case SecretSync.DigitalOceanAppPlatform: return DigitalOceanAppPlatformSyncFns.syncSecrets(secretSync, schemaSecretMap); + case SecretSync.Netlify: + return NetlifySyncFns.syncSecrets(secretSync, schemaSecretMap); case SecretSync.Bitbucket: return BitbucketSyncFns.syncSecrets(secretSync, schemaSecretMap); default: @@ -381,6 +385,9 @@ export const SecretSyncFns = { case SecretSync.DigitalOceanAppPlatform: secretMap = await DigitalOceanAppPlatformSyncFns.getSecrets(secretSync); break; + case SecretSync.Netlify: + secretMap = await NetlifySyncFns.getSecrets(secretSync); + break; case SecretSync.Bitbucket: secretMap = await BitbucketSyncFns.getSecrets(secretSync); break; @@ -473,6 +480,8 @@ export const SecretSyncFns = { return SupabaseSyncFns.removeSecrets(secretSync, schemaSecretMap); case SecretSync.DigitalOceanAppPlatform: return DigitalOceanAppPlatformSyncFns.removeSecrets(secretSync, schemaSecretMap); + case SecretSync.Netlify: + return NetlifySyncFns.removeSecrets(secretSync, schemaSecretMap); case SecretSync.Bitbucket: return BitbucketSyncFns.removeSecrets(secretSync, schemaSecretMap); default: diff --git a/backend/src/services/secret-sync/secret-sync-maps.ts b/backend/src/services/secret-sync/secret-sync-maps.ts index 2f2b3c427..04e91051d 100644 --- a/backend/src/services/secret-sync/secret-sync-maps.ts +++ b/backend/src/services/secret-sync/secret-sync-maps.ts @@ -30,6 +30,7 @@ export const SECRET_SYNC_NAME_MAP: Record = { [SecretSync.Railway]: "Railway", [SecretSync.Checkly]: "Checkly", [SecretSync.DigitalOceanAppPlatform]: "Digital Ocean App Platform", + [SecretSync.Netlify]: "Netlify", [SecretSync.Bitbucket]: "Bitbucket" }; @@ -62,6 +63,7 @@ export const SECRET_SYNC_CONNECTION_MAP: Record = { [SecretSync.Railway]: AppConnection.Railway, [SecretSync.Checkly]: AppConnection.Checkly, [SecretSync.DigitalOceanAppPlatform]: AppConnection.DigitalOcean, + [SecretSync.Netlify]: AppConnection.Netlify, [SecretSync.Bitbucket]: AppConnection.Bitbucket }; @@ -94,5 +96,6 @@ export const SECRET_SYNC_PLAN_MAP: Record = { [SecretSync.Railway]: SecretSyncPlanType.Regular, [SecretSync.Checkly]: SecretSyncPlanType.Regular, [SecretSync.DigitalOceanAppPlatform]: SecretSyncPlanType.Regular, + [SecretSync.Netlify]: SecretSyncPlanType.Regular, [SecretSync.Bitbucket]: SecretSyncPlanType.Regular }; diff --git a/backend/src/services/secret-sync/secret-sync-types.ts b/backend/src/services/secret-sync/secret-sync-types.ts index a844fbe48..6435e19d3 100644 --- a/backend/src/services/secret-sync/secret-sync-types.ts +++ b/backend/src/services/secret-sync/secret-sync-types.ts @@ -117,6 +117,7 @@ import { THumanitecSyncListItem, THumanitecSyncWithCredentials } from "./humanitec"; +import { TNetlifySync, TNetlifySyncInput, TNetlifySyncListItem, TNetlifySyncWithCredentials } from "./netlify"; import { TRailwaySync, TRailwaySyncInput, @@ -178,6 +179,7 @@ export type TSecretSync = | TRailwaySync | TChecklySync | TSupabaseSync + | TNetlifySync | TBitbucketSync; export type TSecretSyncWithCredentials = @@ -209,6 +211,7 @@ export type TSecretSyncWithCredentials = | TChecklySyncWithCredentials | TSupabaseSyncWithCredentials | TDigitalOceanAppPlatformSyncWithCredentials + | TNetlifySyncWithCredentials | TBitbucketSyncWithCredentials; export type TSecretSyncInput = @@ -240,6 +243,7 @@ export type TSecretSyncInput = | TChecklySyncInput | TSupabaseSyncInput | TDigitalOceanAppPlatformSyncInput + | TNetlifySyncInput | TBitbucketSyncInput; export type TSecretSyncListItem = @@ -271,6 +275,7 @@ export type TSecretSyncListItem = | TChecklySyncListItem | TSupabaseSyncListItem | TDigitalOceanAppPlatformSyncListItem + | TNetlifySyncListItem | TBitbucketSyncListItem; export type TSyncOptionsConfig = { diff --git a/docs/api-reference/endpoints/app-connections/netlify/available.mdx b/docs/api-reference/endpoints/app-connections/netlify/available.mdx new file mode 100644 index 000000000..19ca363cc --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/netlify/available.mdx @@ -0,0 +1,4 @@ +--- +title: "Available" +openapi: "GET /api/v1/app-connections/netlify/available" +--- diff --git a/docs/api-reference/endpoints/app-connections/netlify/create.mdx b/docs/api-reference/endpoints/app-connections/netlify/create.mdx new file mode 100644 index 000000000..8c99c14e7 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/netlify/create.mdx @@ -0,0 +1,8 @@ +--- +title: "Create" +openapi: "POST /api/v1/app-connections/netlify" +--- + + + Check out the configuration docs for [Netlify Connections](/integrations/app-connections/netlify) to learn how to obtain the required credentials. + diff --git a/docs/api-reference/endpoints/app-connections/netlify/delete.mdx b/docs/api-reference/endpoints/app-connections/netlify/delete.mdx new file mode 100644 index 000000000..8ab1741a8 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/netlify/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/app-connections/netlify/{connectionId}" +--- diff --git a/docs/api-reference/endpoints/app-connections/netlify/get-by-id.mdx b/docs/api-reference/endpoints/app-connections/netlify/get-by-id.mdx new file mode 100644 index 000000000..cfc49a128 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/netlify/get-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by ID" +openapi: "GET /api/v1/app-connections/netlify/{connectionId}" +--- diff --git a/docs/api-reference/endpoints/app-connections/netlify/get-by-name.mdx b/docs/api-reference/endpoints/app-connections/netlify/get-by-name.mdx new file mode 100644 index 000000000..5869b5d23 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/netlify/get-by-name.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by Name" +openapi: "GET /api/v1/app-connections/netlify/connection-name/{connectionName}" +--- diff --git a/docs/api-reference/endpoints/app-connections/netlify/list.mdx b/docs/api-reference/endpoints/app-connections/netlify/list.mdx new file mode 100644 index 000000000..50de77482 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/netlify/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v1/app-connections/netlify" +--- diff --git a/docs/api-reference/endpoints/app-connections/netlify/update.mdx b/docs/api-reference/endpoints/app-connections/netlify/update.mdx new file mode 100644 index 000000000..f3a981095 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/netlify/update.mdx @@ -0,0 +1,8 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/app-connections/netlify/{connectionId}" +--- + + + Check out the configuration docs for [Netlify Connections](/integrations/app-connections/netlify) to learn how to obtain the required credentials. + diff --git a/docs/api-reference/endpoints/secret-syncs/netlify/create.mdx b/docs/api-reference/endpoints/secret-syncs/netlify/create.mdx new file mode 100644 index 000000000..6fd4a4391 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/netlify/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create" +openapi: "POST /api/v1/secret-syncs/netlify" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/netlify/delete.mdx b/docs/api-reference/endpoints/secret-syncs/netlify/delete.mdx new file mode 100644 index 000000000..051d6825c --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/netlify/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/secret-syncs/netlify/{syncId}" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/netlify/get-by-id.mdx b/docs/api-reference/endpoints/secret-syncs/netlify/get-by-id.mdx new file mode 100644 index 000000000..d207e29a6 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/netlify/get-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by ID" +openapi: "GET /api/v1/secret-syncs/netlify/{syncId}" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/netlify/get-by-name.mdx b/docs/api-reference/endpoints/secret-syncs/netlify/get-by-name.mdx new file mode 100644 index 000000000..caee424b2 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/netlify/get-by-name.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by Name" +openapi: "GET /api/v1/secret-syncs/netlify/sync-name/{syncName}" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/netlify/import-secrets.mdx b/docs/api-reference/endpoints/secret-syncs/netlify/import-secrets.mdx new file mode 100644 index 000000000..41fdadf4f --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/netlify/import-secrets.mdx @@ -0,0 +1,4 @@ +--- +title: "Import Secrets" +openapi: "POST /api/v1/secret-syncs/netlify/{syncId}/import-secrets" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/netlify/list.mdx b/docs/api-reference/endpoints/secret-syncs/netlify/list.mdx new file mode 100644 index 000000000..e366d82e9 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/netlify/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v1/secret-syncs/netlify" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/netlify/remove-secrets.mdx b/docs/api-reference/endpoints/secret-syncs/netlify/remove-secrets.mdx new file mode 100644 index 000000000..5d402e061 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/netlify/remove-secrets.mdx @@ -0,0 +1,4 @@ +--- +title: "Remove Secrets" +openapi: "POST /api/v1/secret-syncs/netlify/{syncId}/remove-secrets" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/netlify/sync-secrets.mdx b/docs/api-reference/endpoints/secret-syncs/netlify/sync-secrets.mdx new file mode 100644 index 000000000..437bc8524 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/netlify/sync-secrets.mdx @@ -0,0 +1,4 @@ +--- +title: "Sync Secrets" +openapi: "POST /api/v1/secret-syncs/netlify/{syncId}/sync-secrets" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/netlify/update.mdx b/docs/api-reference/endpoints/secret-syncs/netlify/update.mdx new file mode 100644 index 000000000..e51ba3270 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/netlify/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/secret-syncs/netlify/{syncId}" +--- diff --git a/docs/docs.json b/docs/docs.json index 6729db05a..4459a67de 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -497,6 +497,7 @@ "integrations/app-connections/ldap", "integrations/app-connections/mssql", "integrations/app-connections/mysql", + "integrations/app-connections/netlify", "integrations/app-connections/oci", "integrations/app-connections/okta", "integrations/app-connections/oracledb", @@ -540,6 +541,7 @@ "integrations/secret-syncs/hashicorp-vault", "integrations/secret-syncs/heroku", "integrations/secret-syncs/humanitec", + "integrations/secret-syncs/netlify", "integrations/secret-syncs/oci-vault", "integrations/secret-syncs/railway", "integrations/secret-syncs/render", @@ -1557,6 +1559,18 @@ "api-reference/endpoints/app-connections/mysql/update", "api-reference/endpoints/app-connections/mysql/delete" ] + }, + { + "group": "Netlify", + "pages": [ + "api-reference/endpoints/app-connections/netlify/list", + "api-reference/endpoints/app-connections/netlify/available", + "api-reference/endpoints/app-connections/netlify/get-by-id", + "api-reference/endpoints/app-connections/netlify/get-by-name", + "api-reference/endpoints/app-connections/netlify/create", + "api-reference/endpoints/app-connections/netlify/update", + "api-reference/endpoints/app-connections/netlify/delete" + ] }, { "group": "OCI", @@ -1978,6 +1992,19 @@ "api-reference/endpoints/secret-syncs/humanitec/remove-secrets" ] }, + { + "group": "Netlify", + "pages": [ + "api-reference/endpoints/secret-syncs/netlify/list", + "api-reference/endpoints/secret-syncs/netlify/get-by-id", + "api-reference/endpoints/secret-syncs/netlify/get-by-name", + "api-reference/endpoints/secret-syncs/netlify/create", + "api-reference/endpoints/secret-syncs/netlify/update", + "api-reference/endpoints/secret-syncs/netlify/delete", + "api-reference/endpoints/secret-syncs/netlify/sync-secrets", + "api-reference/endpoints/secret-syncs/netlify/remove-secrets" + ] + }, { "group": "OCI", "pages": [ diff --git a/docs/images/app-connections/netlify/app-connection-create-api-key.png b/docs/images/app-connections/netlify/app-connection-create-api-key.png new file mode 100644 index 0000000000000000000000000000000000000000..b7832db9eb4e6827d146ea724b612481c0917272 GIT binary patch literal 931961 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�IPgP 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