From 94a23bfa2321f7eec97086190325e53378a286c7 Mon Sep 17 00:00:00 2001 From: Afrie Irham Date: Sun, 23 Jul 2023 16:36:26 +0800 Subject: [PATCH] feat: add cloud 66 integration --- backend/src/integrations/apps.ts | 69 +++++++- backend/src/integrations/sync.ts | 94 ++++++++++- backend/src/models/integration.ts | 7 +- backend/src/models/integrationAuth.ts | 9 +- backend/src/variables/integration.ts | 16 +- frontend/public/data/frequentConstants.ts | 3 +- .../public/images/integrations/Cloud 66.png | Bin 0 -> 11514 bytes .../pages/integrations/cloud-66/authorize.tsx | 64 ++++++++ .../pages/integrations/cloud-66/create.tsx | 155 ++++++++++++++++++ .../IntegrationPage.utils.tsx | 3 + 10 files changed, 404 insertions(+), 16 deletions(-) create mode 100644 frontend/public/images/integrations/Cloud 66.png create mode 100644 frontend/src/pages/integrations/cloud-66/authorize.tsx create mode 100644 frontend/src/pages/integrations/cloud-66/create.tsx diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index dea45ac99..34dae9417 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -1,6 +1,3 @@ -import { Octokit } from "@octokit/rest"; -import { IIntegrationAuth } from "../models"; -import { standardRequest } from "../config/request"; import { INTEGRATION_AWS_PARAMETER_STORE, INTEGRATION_AWS_SECRET_MANAGER, @@ -13,6 +10,8 @@ import { INTEGRATION_CIRCLECI_API_URL, INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_CLOUDFLARE_PAGES_API_URL, + INTEGRATION_CLOUD_66, + INTEGRATION_CLOUD_66_API_URL, INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH_API_URL, INTEGRATION_FLYIO, @@ -37,6 +36,9 @@ import { INTEGRATION_VERCEL, INTEGRATION_VERCEL_API_URL } from "../variables"; +import { IIntegrationAuth } from "../models"; +import { Octokit } from "@octokit/rest"; +import { standardRequest } from "../config/request"; interface App { name: string; @@ -162,6 +164,11 @@ const getApps = async ({ accessToken, }); break; + case INTEGRATION_CLOUD_66: + apps = await getAppsCloud66({ + accessToken, + }); + break; } return apps; @@ -842,4 +849,60 @@ const getAppsCodefresh = async ({ return apps; }; + +/** + * Return list of projects for Cloud 66 integration + */ + +const getAppsCloud66 = async ({ accessToken }: { accessToken: string }) => { + interface Cloud66Apps { + uid: string; + name: string; + account_id: number; + git: string; + git_branch: string; + environment: string; + cloud: string; + fqdn: string; + language: string; + framework: string; + status: number; + health: number; + last_activity: string; + last_activity_iso: string; + maintenance_mode: boolean; + has_loadbalancer: boolean; + created_at: string; + updated_at: string; + deploy_directory: string; + cloud_status: string; + backend: string; + version: string; + revision: string; + is_busy: boolean; + account_name: string; + is_cluster: boolean; + is_inside_cluster: boolean; + cluster_name: any; + application_address: string; + configstore_namespace: string; + } + + const stacks = ( + await standardRequest.get(`${INTEGRATION_CLOUD_66_API_URL}/3/stacks`, { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept-Encoding": "application/json" + } + }) + ).data.response as Cloud66Apps[] + + const apps = stacks.map((app) => ({ + name: app.name, + appId: app.uid + })); + + return apps; +}; + export { getApps }; diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 2c2ed8d1f..d8e6b6116 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -1,14 +1,10 @@ -import _ from "lodash"; -import AWS from "aws-sdk"; import { CreateSecretCommand, GetSecretValueCommand, ResourceNotFoundException, SecretsManagerClient, - UpdateSecretCommand, + UpdateSecretCommand } from "@aws-sdk/client-secrets-manager"; -import { Octokit } from "@octokit/rest"; -import sodium from "libsodium-wrappers"; import { IIntegration, IIntegrationAuth } from "../models"; import { INTEGRATION_AWS_PARAMETER_STORE, @@ -22,6 +18,8 @@ import { INTEGRATION_CIRCLECI_API_URL, INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_CLOUDFLARE_PAGES_API_URL, + INTEGRATION_CLOUD_66, + INTEGRATION_CLOUD_66_API_URL, INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH_API_URL, INTEGRATION_FLYIO, @@ -47,6 +45,10 @@ import { INTEGRATION_VERCEL, INTEGRATION_VERCEL_API_URL } from "../variables"; +import AWS from "aws-sdk"; +import { Octokit } from "@octokit/rest"; +import _ from "lodash"; +import sodium from "libsodium-wrappers"; import { standardRequest } from "../config/request"; /** @@ -220,6 +222,13 @@ const syncSecrets = async ({ accessToken, }); break; + case INTEGRATION_CLOUD_66: + await syncSecretsCloud66({ + integration, + secrets, + accessToken + }); + break; } }; @@ -2101,4 +2110,79 @@ const syncSecretsCodefresh = async ({ ); }; + +const syncSecretsCloud66 = async ({ + integration, + secrets, + accessToken +}: { + integration: IIntegration; + secrets: any; + accessToken: string; +}) => { + interface Cloud66Secret { + id: number; + key: string; + value: string; + readonly: boolean; + created_at: string; + updated_at: string; + is_password: boolean; + is_generated: boolean; + history: any[]; + } + + // get all current secrets + const currentSecrets = ( + await standardRequest.get( + `${INTEGRATION_CLOUD_66_API_URL}/3/stacks/${integration.appId}/environments`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: "application/json" + } + } + ) + ).data.response as Cloud66Secret[]; + + // filter out generated secrets + const secretsToDelete = currentSecrets.filter((s) => !s.is_generated); + + // delete all current secrets + await Promise.all( + secretsToDelete.map( + async (s) => + await standardRequest.delete( + `${INTEGRATION_CLOUD_66_API_URL}/3/stacks/${integration.appId}/environments/${s.key}`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: "application/json" + } + } + ) + ) + ); + + // add new secrets + await Promise.all( + Object.entries(secrets).map( + async ([key, value]) => + await standardRequest.post( + `${INTEGRATION_CLOUD_66_API_URL}/3/stacks/${integration.appId}/environments`, + { + key, + value + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: "application/json" + } + } + ) + ) + ); +}; + export { syncSecrets }; diff --git a/backend/src/models/integration.ts b/backend/src/models/integration.ts index 05e089f37..f3234b616 100644 --- a/backend/src/models/integration.ts +++ b/backend/src/models/integration.ts @@ -1,4 +1,3 @@ -import { Schema, Types, model } from "mongoose"; import { INTEGRATION_AWS_PARAMETER_STORE, INTEGRATION_AWS_SECRET_MANAGER, @@ -7,6 +6,7 @@ import { INTEGRATION_CHECKLY, INTEGRATION_CIRCLECI, INTEGRATION_CLOUDFLARE_PAGES, + INTEGRATION_CLOUD_66, INTEGRATION_CODEFRESH, INTEGRATION_FLYIO, INTEGRATION_GITHUB, @@ -21,6 +21,7 @@ import { INTEGRATION_TRAVISCI, INTEGRATION_VERCEL } from "../variables"; +import { Schema, Types, model } from "mongoose"; export interface IIntegration { _id: Types.ObjectId; @@ -59,6 +60,7 @@ export interface IIntegration { | "cloudflare-pages" | "bitbucket" | "codefresh" + | "cloud-66" integrationAuth: Types.ObjectId; } @@ -149,7 +151,8 @@ const integrationSchema = new Schema( INTEGRATION_HASHICORP_VAULT, INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_BITBUCKET, - INTEGRATION_CODEFRESH + INTEGRATION_CODEFRESH, + INTEGRATION_CLOUD_66, ], required: true, }, diff --git a/backend/src/models/integrationAuth.ts b/backend/src/models/integrationAuth.ts index 409249bf3..b1f4e4c7e 100644 --- a/backend/src/models/integrationAuth.ts +++ b/backend/src/models/integrationAuth.ts @@ -1,4 +1,3 @@ -import { Document, Schema, Types, model } from "mongoose"; import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, @@ -9,6 +8,7 @@ import { INTEGRATION_BITBUCKET, INTEGRATION_CIRCLECI, INTEGRATION_CLOUDFLARE_PAGES, + INTEGRATION_CLOUD_66, INTEGRATION_CODEFRESH, INTEGRATION_FLYIO, INTEGRATION_GITHUB, @@ -23,6 +23,7 @@ import { INTEGRATION_TRAVISCI, INTEGRATION_VERCEL } from "../variables"; +import { Document, Schema, Types, model } from "mongoose"; export interface IIntegrationAuth extends Document { _id: Types.ObjectId; @@ -46,7 +47,8 @@ export interface IIntegrationAuth extends Document { | "checkly" | "cloudflare-pages" | "codefresh" - | "bitbucket"; + | "bitbucket" + | "cloud-66"; teamId: string; accountId: string; url: string; @@ -93,7 +95,8 @@ const integrationAuthSchema = new Schema( INTEGRATION_HASHICORP_VAULT, INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_BITBUCKET, - INTEGRATION_CODEFRESH + INTEGRATION_CODEFRESH, + INTEGRATION_CLOUD_66, ], required: true, }, diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index e99b5a3bf..c005ac715 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -29,6 +29,7 @@ export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault"; export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages"; export const INTEGRATION_BITBUCKET = "bitbucket"; export const INTEGRATION_CODEFRESH = "codefresh"; +export const INTEGRATION_CLOUD_66 = "cloud-66"; export const INTEGRATION_SET = new Set([ INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU, @@ -46,7 +47,8 @@ export const INTEGRATION_SET = new Set([ INTEGRATION_HASHICORP_VAULT, INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_BITBUCKET, - INTEGRATION_CODEFRESH + INTEGRATION_CODEFRESH, + INTEGRATION_CLOUD_66 ]); // integration types @@ -79,6 +81,7 @@ export const INTEGRATION_CHECKLY_API_URL = "https://api.checklyhq.com"; export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com"; export const INTEGRATION_BITBUCKET_API_URL = "https://api.bitbucket.org"; export const INTEGRATION_CODEFRESH_API_URL = "https://g.codefresh.io/api"; +export const INTEGRATION_CLOUD_66_API_URL = "https://app.cloud66.com/api"; export const getIntegrationOptions = async () => { const INTEGRATION_OPTIONS = [ @@ -271,7 +274,16 @@ export const getIntegrationOptions = async () => { type: "pat", clientId: "", docsLink: "", - } + }, + { + name: "Cloud 66", + slug: "cloud-66", + image: "Cloud 66.png", + isAvailable: true, + type: "pat", + clientId: "", + docsLink: "", + }, ] return INTEGRATION_OPTIONS; diff --git a/frontend/public/data/frequentConstants.ts b/frontend/public/data/frequentConstants.ts index d4c0aa729..996860826 100644 --- a/frontend/public/data/frequentConstants.ts +++ b/frontend/public/data/frequentConstants.ts @@ -22,7 +22,8 @@ const integrationSlugNameMapping: Mapping = { "hashicorp-vault": "Vault", "cloudflare-pages": "Cloudflare Pages", "codefresh": "Codefresh", - bitbucket: "BitBucket" + bitbucket: "BitBucket", + "cloud-66": "Cloud 66" }; const envMapping: Mapping = { diff --git a/frontend/public/images/integrations/Cloud 66.png b/frontend/public/images/integrations/Cloud 66.png new file mode 100644 index 0000000000000000000000000000000000000000..20d841080413fdf13515849712330a9ea61e5d89 GIT binary patch literal 11514 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≺NJ(^3n>Al? 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 g5fl9X00030|3nHb*LGxt8vp { + try { + setApiKeyErrorText(""); + if (apiKey.length === 0) { + setApiKeyErrorText("API Key cannot be blank"); + return; + } + + setIsLoading(true); + + const integrationAuth = await saveIntegrationAccessToken({ + workspaceId: localStorage.getItem("projectData.id"), + integration: "cloud-66", + accessId: null, + accessToken: apiKey, + url: null, + namespace: null + }); + + setIsLoading(false); + + router.push(`/integrations/cloud-66/create?integrationAuthId=${integrationAuth._id}`); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Cloud 66 Integration + + setApiKey(e.target.value)} /> + + + +
+ ); +} + +Cloud66CreateIntegrationPage.requireAuth = true; diff --git a/frontend/src/pages/integrations/cloud-66/create.tsx b/frontend/src/pages/integrations/cloud-66/create.tsx new file mode 100644 index 000000000..326e417e5 --- /dev/null +++ b/frontend/src/pages/integrations/cloud-66/create.tsx @@ -0,0 +1,155 @@ +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import queryString from "query-string"; + +import { + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "../../../components/v2"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById, +} from "../../../hooks/api/integrationAuth"; +import { useGetWorkspaceById } from "../../../hooks/api/workspace"; +import createIntegration from "../../api/integrations/createIntegration"; + +export default function Cloud66CreateIntegrationPage() { + const router = useRouter(); + + const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); + + const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? ""); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "" + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); + const [targetApp, setTargetApp] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (workspace) { + setSelectedSourceEnvironment(workspace.environments[0].slug); + } + }, [workspace]); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + } else { + setTargetApp("none"); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?._id) return; + + setIsLoading(true); + + await createIntegration({ + integrationAuthId: integrationAuth?._id, + isActive: true, + app: targetApp, + appId: + integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp) + ?.appId ?? null, + sourceEnvironment: selectedSourceEnvironment, + targetEnvironment: null, + targetEnvironmentId: null, + targetService: null, + targetServiceId: null, + owner: null, + path: null, + region: null, + secretPath + }); + + setIsLoading(false); + + router.push(`/integrations/${localStorage.getItem("projectData.id")}`); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && + workspace && + selectedSourceEnvironment && + integrationAuthApps && + targetApp ? ( +
+ + Cloud 66 Integration + + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + + +
+ ) : ( +
+ ); +} + +Cloud66CreateIntegrationPage.requireAuth = true; diff --git a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx index a5be32502..9a3316822 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx @@ -98,6 +98,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => case "codefresh": link = `${window.location.origin}/integrations/codefresh/authorize`; break; + case "cloud-66": + link = `${window.location.origin}/integrations/cloud-66/authorize`; + break; default: break; }