feat(azure-app-integration): label & reference support

This commit is contained in:
Daniel Hougaard
2024-12-16 15:32:38 +01:00
parent 64a982d5e0
commit 0366506213
5 changed files with 109 additions and 32 deletions
+2
View File
@@ -1126,6 +1126,8 @@ export const INTEGRATION = {
shouldAutoRedeploy: "Used by Render to trigger auto deploy.", shouldAutoRedeploy: "Used by Render to trigger auto deploy.",
secretGCPLabel: "The label for GCP secrets.", secretGCPLabel: "The label for GCP secrets.",
secretAWSTag: "The tags for AWS secrets.", secretAWSTag: "The tags for AWS secrets.",
azureUseLabels:
"If enabled, each secret will be given a label that represents which Infisical environment they belong to.",
githubVisibility: githubVisibility:
"Define where the secrets from the Github Integration should be visible. Option 'selected' lets you directly define which repositories to sync secrets to.", "Define where the secrets from the Github Integration should be visible. Option 'selected' lets you directly define which repositories to sync secrets to.",
githubVisibilityRepoIds: githubVisibilityRepoIds:
@@ -299,6 +299,11 @@ const syncSecretsAzureAppConfig = async ({
value: string; value: string;
} }
// Format: {\"uri\":\"https://SOME-KEY-VAULT.vault.azure.net/secrets/SOME-SECRET-KEY\"}
// Also works without the backslash escapes
const azureSecretReferenceUriRegex =
/^\{(\\"|")uri(\\"|"):(\\"|")https:\/\/[a-zA-Z0-9-]+\.vault\.azure\.net\/secrets\/[a-zA-Z0-9-]+\\?\2\}$/;
const getCompleteAzureAppConfigValues = async (url: string) => { const getCompleteAzureAppConfigValues = async (url: string) => {
let result: AzureAppConfigKeyValue[] = []; let result: AzureAppConfigKeyValue[] = [];
while (url) { while (url) {
@@ -405,14 +410,24 @@ const syncSecretsAzureAppConfig = async ({
} }
// create or update secrets on Azure App Config // create or update secrets on Azure App Config
for await (const key of Object.keys(secrets)) { for await (const key of Object.keys(secrets)) {
if (!(key in azureAppConfigSecrets) || secrets[key]?.value !== azureAppConfigSecrets[key]) { if (!(key in azureAppConfigSecrets) || secrets[key]?.value !== azureAppConfigSecrets[key]) {
await request.put( await request.put(
`${integration.app}/kv/${key}?api-version=2023-11-01`, `${integration.app}/kv/${key}?api-version=2023-11-01`,
{ {
value: secrets[key]?.value value: secrets[key]?.value,
...(azureSecretReferenceUriRegex.test(secrets[key]?.value || "") && {
content_type: "application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8"
})
}, },
{ {
...(metadata.azureUseLabels && {
params: {
label: integration.environment.slug
}
}),
headers: { headers: {
Authorization: `Bearer ${accessToken}` Authorization: `Bearer ${accessToken}`
}, },
@@ -432,6 +447,11 @@ const syncSecretsAzureAppConfig = async ({
headers: { headers: {
Authorization: `Bearer ${accessToken}` Authorization: `Bearer ${accessToken}`
}, },
...(metadata.azureUseLabels && {
params: {
label: integration.environment.slug
}
}),
// we force IPV4 because docker setup fails with ipv6 // we force IPV4 because docker setup fails with ipv6
httpsAgent: new https.Agent({ httpsAgent: new https.Agent({
family: 4 family: 4
@@ -35,6 +35,8 @@ export const IntegrationMetadataSchema = z.object({
.optional() .optional()
.describe(INTEGRATION.CREATE.metadata.secretAWSTag), .describe(INTEGRATION.CREATE.metadata.secretAWSTag),
azureUseLabels: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.azureUseLabels),
githubVisibility: z githubVisibility: z
.union([z.literal("selected"), z.literal("private"), z.literal("all")]) .union([z.literal("selected"), z.literal("private"), z.literal("all")])
.optional() .optional()
@@ -80,6 +80,7 @@ export const useCreateIntegration = () => {
key: string; key: string;
value: string; value: string;
}[]; }[];
azureUseLabels?: boolean;
githubVisibility?: string; githubVisibility?: string;
githubVisibilityRepoIds?: string[]; githubVisibilityRepoIds?: string[];
kmsKeyId?: string; kmsKeyId?: string;
@@ -4,7 +4,11 @@ import Head from "next/head";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; import {
faArrowUpRightFromSquare,
faBookOpen,
faQuestionCircle
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import queryString from "query-string"; import queryString from "query-string";
@@ -21,7 +25,9 @@ import {
FormControl, FormControl,
Input, Input,
Select, Select,
SelectItem SelectItem,
Switch,
Tooltip
} from "../../../components/v2"; } from "../../../components/v2";
import { useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth"; import { useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth";
import { useGetWorkspaceById } from "../../../hooks/api/workspace"; import { useGetWorkspaceById } from "../../../hooks/api/workspace";
@@ -39,7 +45,8 @@ const schema = z.object({
secretPath: z.string().trim().min(1, { message: "Secret path is required" }), secretPath: z.string().trim().min(1, { message: "Secret path is required" }),
sourceEnvironment: z.string().trim().min(1, { message: "Source environment is required" }), sourceEnvironment: z.string().trim().min(1, { message: "Source environment is required" }),
initialSyncBehavior: z.nativeEnum(IntegrationSyncBehavior), initialSyncBehavior: z.nativeEnum(IntegrationSyncBehavior),
secretPrefix: z.string().default("") secretPrefix: z.string().default(""),
useLabels: z.boolean().default(false)
}); });
type TFormSchema = z.infer<typeof schema>; type TFormSchema = z.infer<typeof schema>;
@@ -60,6 +67,7 @@ export default function AzureAppConfigurationCreateIntegration() {
const router = useRouter(); const router = useRouter();
const { const {
control, control,
watch,
setValue, setValue,
handleSubmit, handleSubmit,
formState: { isSubmitting } formState: { isSubmitting }
@@ -85,8 +93,11 @@ export default function AzureAppConfigurationCreateIntegration() {
} }
}, [workspace]); }, [workspace]);
const sourceEnv = watch("sourceEnvironment");
const handleIntegrationSubmit = async ({ const handleIntegrationSubmit = async ({
secretPath, secretPath,
useLabels,
sourceEnvironment, sourceEnvironment,
baseUrl, baseUrl,
initialSyncBehavior, initialSyncBehavior,
@@ -103,7 +114,8 @@ export default function AzureAppConfigurationCreateIntegration() {
secretPath, secretPath,
metadata: { metadata: {
initialSyncBehavior, initialSyncBehavior,
secretPrefix secretPrefix,
azureUseLabels: useLabels
} }
}); });
@@ -155,35 +167,75 @@ export default function AzureAppConfigurationCreateIntegration() {
</div> </div>
</CardTitle> </CardTitle>
<div className="px-6"> <div className="px-6">
<Controller <div className="mb-2 -space-y-2">
control={control} <Controller
name="sourceEnvironment" control={control}
render={({ field, fieldState: { error } }) => ( name="sourceEnvironment"
<FormControl render={({ field, fieldState: { error } }) => (
label="Project Environment" <FormControl
errorText={error?.message} label="Project Environment"
isError={Boolean(error)} errorText={error?.message}
> isError={Boolean(error)}
<Select
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-full"
value={field.value}
onValueChange={(val) => {
field.onChange(val);
}}
> >
{workspace?.environments.map((sourceEnvironment) => ( <Select
<SelectItem className="w-full border border-mineshaft-500"
value={sourceEnvironment.slug} dropdownContainerClassName="max-w-full"
key={`source-environment-${sourceEnvironment.slug}`} value={field.value}
onValueChange={(val) => {
field.onChange(val);
}}
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`source-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
control={control}
name="useLabels"
render={({ field: { onChange, value } }) => (
<Switch
id="use-environment-labels"
onCheckedChange={(isChecked) => onChange(isChecked)}
isChecked={value}
>
<div className="flex items-center gap-1">
Use Environment Labels
<Tooltip
content={
<div>
<p>
Use the environment slug as the label on the secret keys created in
Azure App Configuration.
<br />
<br />
{sourceEnv && (
<p>
You have selected the{" "}
<span className="font-semibold">{sourceEnv}</span> environment,
therefore the label will be set to{" "}
<span className="font-semibold">{sourceEnv}</span>.
</p>
)}
</p>
</div>
}
> >
{sourceEnvironment.name} <FontAwesomeIcon icon={faQuestionCircle} size="1x" />
</SelectItem> </Tooltip>
))} </div>
</Select> </Switch>
</FormControl> )}
)} />
/> </div>
<Controller <Controller
control={control} control={control}
name="secretPath" name="secretPath"