mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-01 01:32:16 +00:00
feat(vault): add Scaleway Secret Manager provider
Adds Scaleway Secret Manager as a first-class vault provider alongside HashiCorp Vault/OpenBao, Infisical, AWS, Doppler and Azure Key Vault. - New `scaleway` provider type, zod-validated config (region, project ID, API secret key, overridable API URL) and migration adding the enum value - Plain `fetch` client (no new dependency) against the Secret Manager v1beta1 REST API: access-by-path for reads, paginated listing for the env editor autocomplete, and a connection test - Refs are `[folder/]name[:field]` — the field selector extracts a key from JSON/key-value secrets, like the AWS provider - Secret key masked in API responses like every other provider credential - Settings -> Secrets form with region picker, brand icon and reference format help
This commit is contained in:
@@ -42,6 +42,7 @@ const providerLabels = {
|
||||
aws: "AWS Secrets Manager",
|
||||
doppler: "Doppler",
|
||||
azure: "Azure Key Vault",
|
||||
scaleway: "Scaleway Secret Manager",
|
||||
} as const;
|
||||
|
||||
type ProviderType = keyof typeof providerLabels;
|
||||
@@ -55,7 +56,14 @@ const VaultProviderSchema = z
|
||||
message:
|
||||
"Only letters, numbers, dashes and underscores (used in ${{vault.<name>.<secret>}})",
|
||||
}),
|
||||
providerType: z.enum(["hashicorp", "infisical", "aws", "doppler", "azure"]),
|
||||
providerType: z.enum([
|
||||
"hashicorp",
|
||||
"infisical",
|
||||
"aws",
|
||||
"doppler",
|
||||
"azure",
|
||||
"scaleway",
|
||||
]),
|
||||
url: z.string(),
|
||||
token: z.string(),
|
||||
namespace: z.string(),
|
||||
@@ -77,6 +85,10 @@ const VaultProviderSchema = z
|
||||
azureClientSecret: z.string(),
|
||||
dopplerProject: z.string(),
|
||||
dopplerConfig: z.string(),
|
||||
scalewayRegion: z.string(),
|
||||
scalewayProjectId: z.string(),
|
||||
scalewaySecretKey: z.string(),
|
||||
scalewayApiUrl: z.string(),
|
||||
assignments: z.array(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
@@ -127,6 +139,17 @@ const VaultProviderSchema = z
|
||||
path: ["awsEndpoint"],
|
||||
});
|
||||
}
|
||||
if (
|
||||
data.providerType === "scaleway" &&
|
||||
data.scalewayApiUrl &&
|
||||
!isValidUrl(data.scalewayApiUrl)
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Enter a valid URL (e.g. https://api.scaleway.com)",
|
||||
path: ["scalewayApiUrl"],
|
||||
});
|
||||
}
|
||||
if (
|
||||
data.providerType === "infisical" &&
|
||||
data.siteUrl &&
|
||||
@@ -166,6 +189,11 @@ const VaultProviderSchema = z
|
||||
["azureClientId", "Client ID is required"],
|
||||
["azureClientSecret", "Client Secret is required"],
|
||||
],
|
||||
scaleway: [
|
||||
["scalewayRegion", "Region is required"],
|
||||
["scalewayProjectId", "Project ID is required"],
|
||||
["scalewaySecretKey", "Secret Key is required"],
|
||||
],
|
||||
};
|
||||
|
||||
for (const [field, message] of required[data.providerType] ?? []) {
|
||||
@@ -222,6 +250,10 @@ const defaultValues: VaultProviderForm = {
|
||||
azureClientSecret: "",
|
||||
dopplerProject: "",
|
||||
dopplerConfig: "",
|
||||
scalewayRegion: "fr-par",
|
||||
scalewayProjectId: "",
|
||||
scalewaySecretKey: "",
|
||||
scalewayApiUrl: "https://api.scaleway.com",
|
||||
assignments: [],
|
||||
};
|
||||
|
||||
@@ -268,6 +300,14 @@ const buildConfig = (data: VaultProviderForm) => {
|
||||
clientId: data.azureClientId,
|
||||
clientSecret: data.azureClientSecret,
|
||||
};
|
||||
case "scaleway":
|
||||
return {
|
||||
providerType: "scaleway" as const,
|
||||
region: data.scalewayRegion || "fr-par",
|
||||
projectId: data.scalewayProjectId,
|
||||
secretKey: data.scalewaySecretKey,
|
||||
apiUrl: data.scalewayApiUrl || "https://api.scaleway.com",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -383,6 +423,12 @@ export const HandleVaultProvider = ({ vaultProviderId }: Props) => {
|
||||
azureClientId: provider.config.clientId,
|
||||
azureClientSecret: provider.config.clientSecret,
|
||||
}),
|
||||
...(provider.config.providerType === "scaleway" && {
|
||||
scalewayRegion: provider.config.region,
|
||||
scalewayProjectId: provider.config.projectId,
|
||||
scalewaySecretKey: provider.config.secretKey,
|
||||
scalewayApiUrl: provider.config.apiUrl,
|
||||
}),
|
||||
});
|
||||
} else if (!vaultProviderId) {
|
||||
form.reset(defaultValues);
|
||||
@@ -859,6 +905,97 @@ export const HandleVaultProvider = ({ vaultProviderId }: Props) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{providerType === "scaleway" && (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="scalewayRegion"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Region</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="fr-par" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{["fr-par", "nl-ams", "pl-waw"].map((region) => (
|
||||
<SelectItem key={region} value={region}>
|
||||
{region}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="scalewayProjectId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Project ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="00000000-0000-0000-0000-000000000000"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="scalewaySecretKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Secret Key</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="password" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The secret key of an API key with the{" "}
|
||||
<code>SecretManagerReadOnly</code> permission set
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="scalewayApiUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>API URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="https://api.scaleway.com"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormDescription>
|
||||
Reference format:{" "}
|
||||
<code>{"${{vault.<name>.secret-name}}"}</code>,{" "}
|
||||
<code>{"${{vault.<name>.folder/secret-name}}"}</code> for
|
||||
secrets in a path, or{" "}
|
||||
<code>{"${{vault.<name>.secret-name:field}}"}</code> for JSON
|
||||
secrets
|
||||
</FormDescription>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-2 rounded-lg border p-3">
|
||||
<FormLabel>Access</FormLabel>
|
||||
<FormDescription>
|
||||
|
||||
@@ -20,6 +20,7 @@ const providerLabels: Record<string, string> = {
|
||||
aws: "AWS Secrets Manager",
|
||||
doppler: "Doppler",
|
||||
azure: "Azure Key Vault",
|
||||
scaleway: "Scaleway Secret Manager",
|
||||
};
|
||||
|
||||
export const ShowVaultProviders = () => {
|
||||
|
||||
@@ -571,10 +571,22 @@ export const AzureIcon = ({ className }: Props) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ScalewayIcon = ({ className }: Props) => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path d="M16.605 11.11v5.72a1.77 1.77 0 01-1.54 1.69h-4a1.43 1.43 0 01-1.31-1.22 1.09 1.09 0 010-.18 1.37 1.37 0 011.37-1.36h1.74a1 1 0 001-1v-3.62a1.4 1.4 0 011.18-1.39h.17a1.37 1.37 0 011.39 1.36zm-6.46 1.74V9.26a1 1 0 011-1h1.85a1.37 1.37 0 001.37-1.37 1 1 0 000-.17 1.45 1.45 0 00-1.41-1.2h-3.96a1.81 1.81 0 00-1.58 1.66v5.7a1.37 1.37 0 001.37 1.37h.21a1.4 1.4 0 001.15-1.4zm12-4.29V20a4.53 4.53 0 01-4.15 4h-7.58a8.57 8.57 0 01-8.56-8.57V4.54A4.54 4.54 0 016.395 0h7.18a8.56 8.56 0 018.56 8.56zm-2.74 0a5.83 5.83 0 00-5.82-5.82h-7.19a1.79 1.79 0 00-1.8 1.8v10.89a5.83 5.83 0 005.82 5.8h7.44a1.79 1.79 0 001.54-1.48z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const vaultProviderIcons = {
|
||||
hashicorp: HashicorpVaultIcon,
|
||||
infisical: InfisicalIcon,
|
||||
aws: AwsIcon,
|
||||
doppler: DopplerIcon,
|
||||
azure: AzureIcon,
|
||||
scaleway: ScalewayIcon,
|
||||
} as const;
|
||||
|
||||
@@ -173,7 +173,7 @@ const RESOURCE_META: Record<string, { label: string; description: string }> = {
|
||||
vaultProvider: {
|
||||
label: "Secrets Providers",
|
||||
description:
|
||||
"Manage external secret managers (HashiCorp Vault, AWS, Azure, Infisical, Doppler) and where their secrets can be referenced",
|
||||
"Manage external secret managers (HashiCorp Vault, AWS, Azure, Infisical, Doppler, Scaleway) and where their secrets can be referenced",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user