feat: Checkly app connection and secrets sync (#4078)

* feat: checkly app connection
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Sid
2025-07-16 00:07:42 +05:30
committed by GitHub
co-authored by greptile-apps[bot]
parent 023079be16
commit 7ef077228e
91 changed files with 1448 additions and 36 deletions
@@ -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 CHECKLY_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Checkly",
destination: SecretSync.Checkly,
connection: AppConnection.Checkly,
canImportSecrets: false
};
@@ -0,0 +1,102 @@
/* eslint-disable no-continue */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ChecklyPublicAPI } from "@app/services/app-connection/checkly/checkly-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 { TChecklySyncWithCredentials } from "./checkly-sync-types";
export const ChecklySyncFns = {
async getSecrets(secretSync: TChecklySyncWithCredentials) {
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
},
async syncSecrets(secretSync: TChecklySyncWithCredentials, secretMap: TSecretMap) {
const {
environment,
syncOptions: { disableSecretDeletion, keySchema }
} = secretSync;
const config = secretSync.destinationConfig;
const variables = await ChecklyPublicAPI.getVariables(secretSync.connection, config.accountId);
const checklySecrets = Object.fromEntries(variables!.map((variable) => [variable.key, variable]));
for await (const key of Object.keys(secretMap)) {
try {
const entry = secretMap[key];
// If value is empty, we skip the upsert - checkly does not allow empty values
if (entry.value.trim() === "") {
// Delete the secret from Checkly if its empty
if (!disableSecretDeletion) {
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
key
});
}
continue; // Skip empty values
}
await ChecklyPublicAPI.upsertVariable(secretSync.connection, config.accountId, {
key,
value: entry.value,
secret: true,
locked: true
});
} catch (error) {
throw new SecretSyncError({
error,
secretKey: key
});
}
}
if (disableSecretDeletion) return;
for await (const key of Object.keys(checklySecrets)) {
try {
// eslint-disable-next-line no-continue
if (!matchesSchema(key, environment?.slug || "", keySchema)) continue;
if (!secretMap[key]) {
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
key
});
}
} catch (error) {
throw new SecretSyncError({
error,
secretKey: key
});
}
}
},
async removeSecrets(secretSync: TChecklySyncWithCredentials, secretMap: TSecretMap) {
const config = secretSync.destinationConfig;
const variables = await ChecklyPublicAPI.getVariables(secretSync.connection, config.accountId);
const checklySecrets = Object.fromEntries(variables!.map((variable) => [variable.key, variable]));
for await (const secret of Object.keys(checklySecrets)) {
try {
if (secret in secretMap) {
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
key: secret
});
}
} catch (error) {
throw new SecretSyncError({
error,
secretKey: secret
});
}
}
}
};
@@ -0,0 +1,43 @@
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 ChecklySyncDestinationConfigSchema = z.object({
accountId: z.string().min(1, "Account ID is required").max(255, "Account ID must be less than 255 characters"),
accountName: z.string().min(1, "Account Name is required").max(255, "Account ID must be less than 255 characters")
});
const ChecklySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
export const ChecklySyncSchema = BaseSecretSyncSchema(SecretSync.Checkly, ChecklySyncOptionsConfig).extend({
destination: z.literal(SecretSync.Checkly),
destinationConfig: ChecklySyncDestinationConfigSchema
});
export const CreateChecklySyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.Checkly,
ChecklySyncOptionsConfig
).extend({
destinationConfig: ChecklySyncDestinationConfigSchema
});
export const UpdateChecklySyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.Checkly,
ChecklySyncOptionsConfig
).extend({
destinationConfig: ChecklySyncDestinationConfigSchema.optional()
});
export const ChecklySyncListItemSchema = z.object({
name: z.literal("Checkly"),
connection: z.literal(AppConnection.Checkly),
destination: z.literal(SecretSync.Checkly),
canImportSecrets: z.literal(false)
});
@@ -0,0 +1,23 @@
import z from "zod";
import { TChecklyConnection, TChecklyVariable } from "@app/services/app-connection/checkly";
import { ChecklySyncListItemSchema, ChecklySyncSchema, CreateChecklySyncSchema } from "./checkly-sync-schemas";
export type TChecklySyncListItem = z.infer<typeof ChecklySyncListItemSchema>;
export type TChecklySync = z.infer<typeof ChecklySyncSchema>;
export type TChecklySyncInput = z.infer<typeof CreateChecklySyncSchema>;
export type TChecklySyncWithCredentials = TChecklySync & {
connection: TChecklyConnection;
};
export type TChecklySecret = TChecklyVariable;
export type TChecklyVariablesGraphResponse = {
data: {
variables: Record<string, string>;
};
};
@@ -24,7 +24,8 @@ export enum SecretSync {
CloudflareWorkers = "cloudflare-workers",
Zabbix = "zabbix",
Railway = "railway"
Railway = "railway",
Checkly = "checkly"
}
export enum SecretSyncInitialSyncBehavior {
@@ -29,6 +29,8 @@ import { AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, azureAppConfigurationSyncFact
import { AZURE_DEVOPS_SYNC_LIST_OPTION, azureDevOpsSyncFactory } from "./azure-devops";
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault";
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants";
import { ChecklySyncFns } from "./checkly/checkly-sync-fns";
import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants";
import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns";
import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers";
@@ -76,7 +78,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.CloudflareWorkers]: CLOUDFLARE_WORKERS_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
};
export const listSecretSyncOptions = () => {
@@ -250,6 +253,8 @@ export const SecretSyncFns = {
return ZabbixSyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.Railway:
return RailwaySyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.Checkly:
return ChecklySyncFns.syncSecrets(secretSync, schemaSecretMap);
default:
throw new Error(
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -351,6 +356,9 @@ export const SecretSyncFns = {
case SecretSync.Railway:
secretMap = await RailwaySyncFns.getSecrets(secretSync);
break;
case SecretSync.Checkly:
secretMap = await ChecklySyncFns.getSecrets(secretSync);
break;
default:
throw new Error(
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -434,6 +442,8 @@ export const SecretSyncFns = {
return ZabbixSyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.Railway:
return RailwaySyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.Checkly:
return ChecklySyncFns.removeSecrets(secretSync, schemaSecretMap);
default:
throw new Error(
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -27,7 +27,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.CloudflareWorkers]: "Cloudflare Workers",
[SecretSync.Zabbix]: "Zabbix",
[SecretSync.Railway]: "Railway"
[SecretSync.Railway]: "Railway",
[SecretSync.Checkly]: "Checkly"
};
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
@@ -56,7 +57,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
[SecretSync.Zabbix]: AppConnection.Zabbix,
[SecretSync.Railway]: AppConnection.Railway
[SecretSync.Railway]: AppConnection.Railway,
[SecretSync.Checkly]: AppConnection.Checkly
};
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
@@ -85,5 +87,6 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
[SecretSync.CloudflareWorkers]: SecretSyncPlanType.Regular,
[SecretSync.Zabbix]: SecretSyncPlanType.Regular,
[SecretSync.Railway]: SecretSyncPlanType.Regular
[SecretSync.Railway]: SecretSyncPlanType.Regular,
[SecretSync.Checkly]: SecretSyncPlanType.Regular
};
@@ -72,6 +72,12 @@ import {
TAzureKeyVaultSyncListItem,
TAzureKeyVaultSyncWithCredentials
} from "./azure-key-vault";
import {
TChecklySync,
TChecklySyncInput,
TChecklySyncListItem,
TChecklySyncWithCredentials
} from "./checkly/checkly-sync-types";
import {
TCloudflarePagesSync,
TCloudflarePagesSyncInput,
@@ -152,7 +158,8 @@ export type TSecretSync =
| TCloudflarePagesSync
| TCloudflareWorkersSync
| TZabbixSync
| TRailwaySync;
| TRailwaySync
| TChecklySync;
export type TSecretSyncWithCredentials =
| TAwsParameterStoreSyncWithCredentials
@@ -179,7 +186,8 @@ export type TSecretSyncWithCredentials =
| TCloudflarePagesSyncWithCredentials
| TCloudflareWorkersSyncWithCredentials
| TZabbixSyncWithCredentials
| TRailwaySyncWithCredentials;
| TRailwaySyncWithCredentials
| TChecklySyncWithCredentials;
export type TSecretSyncInput =
| TAwsParameterStoreSyncInput
@@ -206,7 +214,8 @@ export type TSecretSyncInput =
| TCloudflarePagesSyncInput
| TCloudflareWorkersSyncInput
| TZabbixSyncInput
| TRailwaySyncInput;
| TRailwaySyncInput
| TChecklySyncInput;
export type TSecretSyncListItem =
| TAwsParameterStoreSyncListItem
@@ -233,7 +242,8 @@ export type TSecretSyncListItem =
| TCloudflarePagesSyncListItem
| TCloudflareWorkersSyncListItem
| TZabbixSyncListItem
| TRailwaySyncListItem;
| TRailwaySyncListItem
| TChecklySyncListItem;
export type TSyncOptionsConfig = {
canImportSecrets: boolean;