mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat(repositories): azure blob storage
This commit is contained in:
@@ -738,6 +738,19 @@ export type ListRepositoriesResponses = {
|
|||||||
endpoint: string;
|
endpoint: string;
|
||||||
secretAccessKey: string;
|
secretAccessKey: string;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
accountKey: string;
|
||||||
|
accountName: string;
|
||||||
|
backend: "azure";
|
||||||
|
container: string;
|
||||||
|
endpointSuffix?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
backend: "gcs";
|
||||||
|
bucket: string;
|
||||||
|
credentialsJson: string;
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
backend: "local";
|
backend: "local";
|
||||||
name: string;
|
name: string;
|
||||||
@@ -748,7 +761,7 @@ export type ListRepositoriesResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
status: "error" | "healthy" | "unknown" | null;
|
status: "error" | "healthy" | "unknown" | null;
|
||||||
type: "local" | "s3";
|
type: "azure" | "gcs" | "local" | "s3";
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
@@ -765,6 +778,19 @@ export type CreateRepositoryData = {
|
|||||||
endpoint: string;
|
endpoint: string;
|
||||||
secretAccessKey: string;
|
secretAccessKey: string;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
accountKey: string;
|
||||||
|
accountName: string;
|
||||||
|
backend: "azure";
|
||||||
|
container: string;
|
||||||
|
endpointSuffix?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
backend: "gcs";
|
||||||
|
bucket: string;
|
||||||
|
credentialsJson: string;
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
backend: "local";
|
backend: "local";
|
||||||
name: string;
|
name: string;
|
||||||
@@ -835,6 +861,19 @@ export type GetRepositoryResponses = {
|
|||||||
endpoint: string;
|
endpoint: string;
|
||||||
secretAccessKey: string;
|
secretAccessKey: string;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
accountKey: string;
|
||||||
|
accountName: string;
|
||||||
|
backend: "azure";
|
||||||
|
container: string;
|
||||||
|
endpointSuffix?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
backend: "gcs";
|
||||||
|
bucket: string;
|
||||||
|
credentialsJson: string;
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
backend: "local";
|
backend: "local";
|
||||||
name: string;
|
name: string;
|
||||||
@@ -845,7 +884,7 @@ export type GetRepositoryResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
status: "error" | "healthy" | "unknown" | null;
|
status: "error" | "healthy" | "unknown" | null;
|
||||||
type: "local" | "s3";
|
type: "azure" | "gcs" | "local" | "s3";
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1030,6 +1069,19 @@ export type ListBackupSchedulesResponses = {
|
|||||||
endpoint: string;
|
endpoint: string;
|
||||||
secretAccessKey: string;
|
secretAccessKey: string;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
accountKey: string;
|
||||||
|
accountName: string;
|
||||||
|
backend: "azure";
|
||||||
|
container: string;
|
||||||
|
endpointSuffix?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
backend: "gcs";
|
||||||
|
bucket: string;
|
||||||
|
credentialsJson: string;
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
backend: "local";
|
backend: "local";
|
||||||
name: string;
|
name: string;
|
||||||
@@ -1040,7 +1092,7 @@ export type ListBackupSchedulesResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
status: "error" | "healthy" | "unknown" | null;
|
status: "error" | "healthy" | "unknown" | null;
|
||||||
type: "local" | "s3";
|
type: "azure" | "gcs" | "local" | "s3";
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
};
|
};
|
||||||
repositoryId: string;
|
repositoryId: string;
|
||||||
@@ -1216,6 +1268,19 @@ export type GetBackupScheduleResponses = {
|
|||||||
endpoint: string;
|
endpoint: string;
|
||||||
secretAccessKey: string;
|
secretAccessKey: string;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
accountKey: string;
|
||||||
|
accountName: string;
|
||||||
|
backend: "azure";
|
||||||
|
container: string;
|
||||||
|
endpointSuffix?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
backend: "gcs";
|
||||||
|
bucket: string;
|
||||||
|
credentialsJson: string;
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
backend: "local";
|
backend: "local";
|
||||||
name: string;
|
name: string;
|
||||||
@@ -1226,7 +1291,7 @@ export type GetBackupScheduleResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
status: "error" | "healthy" | "unknown" | null;
|
status: "error" | "healthy" | "unknown" | null;
|
||||||
type: "local" | "s3";
|
type: "azure" | "gcs" | "local" | "s3";
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
};
|
};
|
||||||
repositoryId: string;
|
repositoryId: string;
|
||||||
@@ -1383,6 +1448,19 @@ export type GetBackupScheduleForVolumeResponses = {
|
|||||||
endpoint: string;
|
endpoint: string;
|
||||||
secretAccessKey: string;
|
secretAccessKey: string;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
accountKey: string;
|
||||||
|
accountName: string;
|
||||||
|
backend: "azure";
|
||||||
|
container: string;
|
||||||
|
endpointSuffix?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
backend: "gcs";
|
||||||
|
bucket: string;
|
||||||
|
credentialsJson: string;
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
backend: "local";
|
backend: "local";
|
||||||
name: string;
|
name: string;
|
||||||
@@ -1393,7 +1471,7 @@ export type GetBackupScheduleForVolumeResponses = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
status: "error" | "healthy" | "unknown" | null;
|
status: "error" | "healthy" | "unknown" | null;
|
||||||
type: "local" | "s3";
|
type: "azure" | "gcs" | "local" | "s3";
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
};
|
};
|
||||||
repositoryId: string;
|
repositoryId: string;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const defaultValuesForType = {
|
|||||||
local: { backend: "local" as const, compressionMode: "auto" as const },
|
local: { backend: "local" as const, compressionMode: "auto" as const },
|
||||||
s3: { backend: "s3" as const, compressionMode: "auto" as const },
|
s3: { backend: "s3" as const, compressionMode: "auto" as const },
|
||||||
gcs: { backend: "gcs" as const, compressionMode: "auto" as const },
|
gcs: { backend: "gcs" as const, compressionMode: "auto" as const },
|
||||||
|
azure: { backend: "azure" as const, compressionMode: "auto" as const },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateRepositoryForm = ({
|
export const CreateRepositoryForm = ({
|
||||||
@@ -102,6 +103,7 @@ export const CreateRepositoryForm = ({
|
|||||||
<SelectItem value="local">Local</SelectItem>
|
<SelectItem value="local">Local</SelectItem>
|
||||||
<SelectItem value="s3">S3</SelectItem>
|
<SelectItem value="s3">S3</SelectItem>
|
||||||
<SelectItem value="gcs">Google Cloud Storage</SelectItem>
|
<SelectItem value="gcs">Google Cloud Storage</SelectItem>
|
||||||
|
<SelectItem value="azure">Azure Blob Storage</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormDescription>Choose the storage backend for this repository.</FormDescription>
|
<FormDescription>Choose the storage backend for this repository.</FormDescription>
|
||||||
@@ -244,6 +246,67 @@ export const CreateRepositoryForm = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{watchedBackend === "azure" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="container"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Container</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="my-backup-container" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Azure Blob Storage container name for storing backups.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="accountName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Account Name</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="mystorageaccount" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Azure Storage account name.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="accountKey"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Account Key</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="password" placeholder="••••••••" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Azure Storage account key for authentication.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="endpointSuffix"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Endpoint Suffix (Optional)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="core.windows.net" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Custom Azure endpoint suffix (defaults to core.windows.net).</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{mode === "update" && (
|
{mode === "update" && (
|
||||||
<Button type="submit" className="w-full" loading={loading}>
|
<Button type="submit" className="w-full" loading={loading}>
|
||||||
Save Changes
|
Save Changes
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ const encryptConfig = async (config: RepositoryConfig): Promise<RepositoryConfig
|
|||||||
case "gcs":
|
case "gcs":
|
||||||
encryptedConfig.credentialsJson = await cryptoUtils.encrypt(config.credentialsJson);
|
encryptedConfig.credentialsJson = await cryptoUtils.encrypt(config.credentialsJson);
|
||||||
break;
|
break;
|
||||||
|
case "azure":
|
||||||
|
encryptedConfig.accountKey = await cryptoUtils.encrypt(config.accountKey);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return encryptedConfig as RepositoryConfig;
|
return encryptedConfig as RepositoryConfig;
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ const buildRepoUrl = (config: RepositoryConfig): string => {
|
|||||||
return `s3:${config.endpoint}/${config.bucket}`;
|
return `s3:${config.endpoint}/${config.bucket}`;
|
||||||
case "gcs":
|
case "gcs":
|
||||||
return `gs:${config.bucket}:/`;
|
return `gs:${config.bucket}:/`;
|
||||||
|
case "azure":
|
||||||
|
return `azure:${config.container}:/`;
|
||||||
default: {
|
default: {
|
||||||
throw new Error(`Unsupported repository backend: ${JSON.stringify(config)}`);
|
throw new Error(`Unsupported repository backend: ${JSON.stringify(config)}`);
|
||||||
}
|
}
|
||||||
@@ -101,6 +103,14 @@ const buildEnv = async (config: RepositoryConfig) => {
|
|||||||
env.GOOGLE_APPLICATION_CREDENTIALS = credentialsPath;
|
env.GOOGLE_APPLICATION_CREDENTIALS = credentialsPath;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "azure": {
|
||||||
|
env.AZURE_ACCOUNT_NAME = config.accountName;
|
||||||
|
env.AZURE_ACCOUNT_KEY = await cryptoUtils.decrypt(config.accountKey);
|
||||||
|
if (config.endpointSuffix) {
|
||||||
|
env.AZURE_ENDPOINT_SUFFIX = config.endpointSuffix;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return env;
|
return env;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export const REPOSITORY_BACKENDS = {
|
|||||||
local: "local",
|
local: "local",
|
||||||
s3: "s3",
|
s3: "s3",
|
||||||
gcs: "gcs",
|
gcs: "gcs",
|
||||||
|
azure: "azure",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type RepositoryBackend = keyof typeof REPOSITORY_BACKENDS;
|
export type RepositoryBackend = keyof typeof REPOSITORY_BACKENDS;
|
||||||
@@ -28,7 +29,18 @@ export const gcsRepositoryConfigSchema = type({
|
|||||||
credentialsJson: "string",
|
credentialsJson: "string",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const repositoryConfigSchema = s3RepositoryConfigSchema.or(localRepositoryConfigSchema).or(gcsRepositoryConfigSchema);
|
export const azureRepositoryConfigSchema = type({
|
||||||
|
backend: "'azure'",
|
||||||
|
container: "string",
|
||||||
|
accountName: "string",
|
||||||
|
accountKey: "string",
|
||||||
|
endpointSuffix: "string?",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const repositoryConfigSchema = s3RepositoryConfigSchema
|
||||||
|
.or(localRepositoryConfigSchema)
|
||||||
|
.or(gcsRepositoryConfigSchema)
|
||||||
|
.or(azureRepositoryConfigSchema);
|
||||||
|
|
||||||
export type RepositoryConfig = typeof repositoryConfigSchema.infer;
|
export type RepositoryConfig = typeof repositoryConfigSchema.infer;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user