Created
April 22, 2025 08:09
-
-
Save sam-goodwin/b3307fcfe3a878c00b064224e5f9259a to your computer and use it in GitHub Desktop.
Huawei Bucket Alchemy Resource
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Options for Huawei Cloud API requests | |
*/ | |
export interface HuaweiApiOptions { | |
/** | |
* API key or token to use (overrides environment variable) | |
*/ | |
apiKey?: string; | |
/** | |
* Account or project ID (overrides environment variable) | |
*/ | |
accountId?: string; | |
/** | |
* Region for the API endpoint | |
*/ | |
region?: string; | |
} | |
/** | |
* Minimal API client using raw fetch | |
*/ | |
export class HuaweiApi { | |
/** Base URL for API */ | |
readonly baseUrl: string; | |
/** API key or token */ | |
readonly apiKey: string; | |
/** Account ID */ | |
readonly accountId: string; | |
/** Region */ | |
readonly region: string; | |
/** | |
* Create a new API client | |
* | |
* @param options API options | |
*/ | |
constructor(options: HuaweiApiOptions = {}) { | |
this.region = options.region || process.env.HUAWEI_REGION || ""; | |
this.baseUrl = `https://obs.${this.region}.myhuaweicloud.com`; | |
this.apiKey = options.apiKey || process.env.HUAWEI_ACCESS_KEY || ""; | |
this.accountId = options.accountId || process.env.HUAWEI_ACCOUNT_ID || ""; | |
// Validate required configuration | |
if (!this.apiKey) { | |
throw new Error("HUAWEI_ACCESS_KEY environment variable is required"); | |
} | |
if (!this.region) { | |
throw new Error("HUAWEI_REGION environment variable is required"); | |
} | |
} | |
/** | |
* Make a request to the API | |
* | |
* @param path API path (without base URL) | |
* @param init Fetch init options | |
* @returns Raw Response object from fetch | |
*/ | |
async fetch(path: string, init: RequestInit = {}): Promise<Response> { | |
// Set up authentication headers | |
const headers: Record<string, string> = { | |
"Content-Type": "application/json", | |
Authorization: `Bearer ${this.apiKey}`, | |
}; | |
// Add headers from init if provided | |
if (init.headers) { | |
const initHeaders = init.headers as Record<string, string>; | |
Object.keys(initHeaders).forEach((key) => { | |
headers[key] = initHeaders[key]; | |
}); | |
} | |
// For FormData, remove Content-Type | |
if (init.body instanceof FormData) { | |
delete headers["Content-Type"]; | |
} | |
// Make the request | |
return fetch(`${this.baseUrl}${path}`, { | |
...init, | |
headers, | |
}); | |
} | |
/** | |
* Helper for GET requests | |
*/ | |
async get(path: string, init: RequestInit = {}): Promise<Response> { | |
return this.fetch(path, { ...init, method: "GET" }); | |
} | |
/** | |
* Helper for POST requests | |
*/ | |
async post( | |
path: string, | |
body: any, | |
init: RequestInit = {} | |
): Promise<Response> { | |
const requestBody = body instanceof FormData ? body : JSON.stringify(body); | |
return this.fetch(path, { ...init, method: "POST", body: requestBody }); | |
} | |
/** | |
* Helper for PUT requests | |
*/ | |
async put( | |
path: string, | |
body: any, | |
init: RequestInit = {} | |
): Promise<Response> { | |
const requestBody = body instanceof FormData ? body : JSON.stringify(body); | |
return this.fetch(path, { ...init, method: "PUT", body: requestBody }); | |
} | |
/** | |
* Helper for DELETE requests | |
*/ | |
async delete(path: string, init: RequestInit = {}): Promise<Response> { | |
return this.fetch(path, { ...init, method: "DELETE" }); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { Context } from "../context"; | |
import { Resource } from "../resource"; | |
import { HuaweiApi } from "./api"; | |
/** | |
* Website configuration for OBS bucket | |
*/ | |
export interface BucketWebsiteConfig { | |
/** | |
* The name of the index document for the website | |
*/ | |
indexDocument?: string; | |
/** | |
* The name of the error document for the website | |
*/ | |
errorDocument?: string; | |
/** | |
* A hostname to redirect all website requests to | |
*/ | |
redirectAllRequestsTo?: string; | |
/** | |
* JSON routing rules configuration for the website | |
*/ | |
routingRules?: string; | |
} | |
/** | |
* CORS rule configuration for OBS bucket | |
*/ | |
export interface BucketCorsRule { | |
/** | |
* List of origins allowed to make cross-origin requests | |
*/ | |
allowedOrigins: string[]; | |
/** | |
* List of HTTP methods allowed for cross-origin requests | |
*/ | |
allowedMethods: string[]; | |
/** | |
* List of headers allowed in cross-origin requests | |
*/ | |
allowedHeaders?: string[]; | |
/** | |
* List of headers exposed to the browser | |
*/ | |
exposeHeaders?: string[]; | |
/** | |
* Time in seconds the browser can cache preflight results | |
*/ | |
maxAgeSeconds?: number; | |
} | |
/** | |
* Lifecycle rule transitions | |
*/ | |
export interface BucketLifecycleTransition { | |
/** | |
* Number of days after object creation the rule applies | |
*/ | |
days: number; | |
/** | |
* Storage class to transition to (STANDARD, WARM, COLD) | |
*/ | |
storageClass: string; | |
} | |
/** | |
* Lifecycle rule expiration configuration | |
*/ | |
export interface BucketLifecycleExpiration { | |
/** | |
* Number of days after object creation when the rule takes effect | |
*/ | |
days: number; | |
} | |
/** | |
* Lifecycle rule configuration for OBS bucket | |
*/ | |
export interface BucketLifecycleRule { | |
/** | |
* Unique name for the lifecycle rule | |
*/ | |
name: string; | |
/** | |
* Whether the rule is enabled | |
*/ | |
enabled: boolean; | |
/** | |
* Prefix filter for objects the rule applies to | |
*/ | |
prefix?: string; | |
/** | |
* Expiration configuration for objects | |
*/ | |
expiration?: BucketLifecycleExpiration; | |
/** | |
* Transition configuration for objects | |
*/ | |
transition?: BucketLifecycleTransition[]; | |
/** | |
* Expiration configuration for non-current versions | |
*/ | |
noncurrentVersionExpiration?: BucketLifecycleExpiration; | |
/** | |
* Transition configuration for non-current versions | |
*/ | |
noncurrentVersionTransition?: BucketLifecycleTransition[]; | |
/** | |
* Configuration for aborting incomplete multipart uploads | |
*/ | |
abortIncompleteMultipartUpload?: BucketLifecycleExpiration; | |
} | |
/** | |
* Logging configuration for OBS bucket | |
*/ | |
export interface BucketLoggingConfig { | |
/** | |
* Target bucket to store access logs | |
*/ | |
targetBucket: string; | |
/** | |
* Prefix to prepend to all log object keys | |
*/ | |
targetPrefix?: string; | |
/** | |
* Agency with appropriate permissions for logging | |
*/ | |
agency?: string; | |
} | |
/** | |
* Properties for creating or updating a Huawei Cloud OBS Bucket | |
*/ | |
export interface BucketProps { | |
/** | |
* Name of the bucket | |
*/ | |
bucket: string; | |
/** | |
* Storage class for the bucket (STANDARD, WARM, COLD) | |
* Default: STANDARD | |
*/ | |
storageClass?: string; | |
/** | |
* ACL permissions for the bucket (private, public-read, public-read-write) | |
* Default: private | |
*/ | |
acl?: string; | |
/** | |
* Bucket policy document in JSON format | |
*/ | |
policy?: string; | |
/** | |
* Policy format (obs or s3) | |
* Default: obs | |
*/ | |
policyFormat?: string; | |
/** | |
* Whether versioning is enabled | |
* Default: false | |
*/ | |
versioning?: boolean; | |
/** | |
* Logging configuration | |
*/ | |
logging?: BucketLoggingConfig; | |
/** | |
* Storage quota in bytes (0 for unlimited) | |
* Default: 0 | |
*/ | |
quota?: number; | |
/** | |
* Lifecycle rules for managing objects | |
*/ | |
lifecycleRules?: BucketLifecycleRule[]; | |
/** | |
* Website configuration | |
*/ | |
website?: BucketWebsiteConfig; | |
/** | |
* CORS rules | |
*/ | |
corsRules?: BucketCorsRule[]; | |
/** | |
* Tags to apply to the bucket | |
*/ | |
tags?: Record<string, string>; | |
/** | |
* Region where the bucket is created | |
*/ | |
region?: string; | |
/** | |
* Whether to enable multi-AZ for the bucket | |
*/ | |
multiAz?: boolean; | |
/** | |
* Whether to enable parallel file system | |
*/ | |
parallelFs?: boolean; | |
/** | |
* Whether to enable server-side encryption | |
* Default: false | |
*/ | |
encryption?: boolean; | |
/** | |
* Server-side encryption algorithm (usually AES256) | |
*/ | |
sseAlgorithm?: string; | |
/** | |
* KMS key ID for SSE-KMS encryption | |
*/ | |
kmsKeyId?: string; | |
/** | |
* KMS key project ID | |
*/ | |
kmsKeyProjectId?: string; | |
/** | |
* Enterprise project ID for the bucket | |
*/ | |
enterpriseProjectId?: string; | |
/** | |
* Custom domain names for the bucket | |
*/ | |
userDomainNames?: string[]; | |
/** | |
* Whether all objects should be deleted when the bucket is destroyed | |
* Default: false | |
*/ | |
forceDestroy?: boolean; | |
} | |
/** | |
* Storage info for OBS bucket | |
*/ | |
export interface BucketStorageInfo { | |
/** | |
* Storage size in bytes | |
*/ | |
size: number; | |
/** | |
* Number of objects in the bucket | |
*/ | |
objectNumber: number; | |
} | |
/** | |
* Output returned after Bucket creation/update | |
*/ | |
export interface Bucket extends Resource<"huawei::Bucket">, BucketProps { | |
/** | |
* The ID of the bucket (same as the bucket name) | |
*/ | |
id: string; | |
/** | |
* Storage information | |
*/ | |
storageInfo?: BucketStorageInfo; | |
/** | |
* Bucket domain name | |
*/ | |
bucketDomainName: string; | |
/** | |
* Bucket version | |
*/ | |
bucketVersion?: string; | |
/** | |
* Time at which the bucket was created | |
*/ | |
createdAt: number; | |
} | |
/** | |
* Creates a Huawei Cloud OBS Bucket. | |
* | |
* This resource creates and manages Object Storage Service (OBS) buckets on Huawei Cloud. | |
* | |
* @example | |
* // Create a basic private bucket | |
* const basicBucket = await Bucket("my-bucket", { | |
* bucket: "my-bucket", | |
* acl: "private", | |
* region: "ap-southeast-1" | |
* }); | |
* | |
* @example | |
* // Create a bucket with website hosting | |
* const websiteBucket = await Bucket("website-bucket", { | |
* bucket: "website-bucket", | |
* acl: "public-read", | |
* website: { | |
* indexDocument: "index.html", | |
* errorDocument: "error.html" | |
* } | |
* }); | |
* | |
* @example | |
* // Create a bucket with lifecycle rules | |
* const lifecycleBucket = await Bucket("lifecycle-bucket", { | |
* bucket: "lifecycle-bucket", | |
* lifecycleRules: [{ | |
* name: "archive-rule", | |
* enabled: true, | |
* prefix: "logs/", | |
* transition: [{ | |
* days: 30, | |
* storageClass: "WARM" | |
* }, { | |
* days: 90, | |
* storageClass: "COLD" | |
* }] | |
* }] | |
* }); | |
* | |
* @example | |
* // Create a versioned bucket with encryption | |
* const securedBucket = await Bucket("secured-bucket", { | |
* bucket: "secured-bucket", | |
* versioning: true, | |
* encryption: true, | |
* sseAlgorithm: "AES256" | |
* }); | |
*/ | |
export const Bucket = Resource( | |
"huawei::Bucket", | |
async function ( | |
this: Context<Bucket>, | |
id: string, | |
props: BucketProps | |
): Promise<Bucket> { | |
// Initialize API client | |
const region = props.region || process.env.HUAWEI_REGION || ""; | |
const api = new HuaweiApi({ region }); | |
// The bucket name to use for operations | |
const bucketName = props.bucket; | |
if (this.phase === "delete") { | |
try { | |
if (this.output?.bucket) { | |
// Check if bucket should be force destroyed | |
if (props.forceDestroy) { | |
console.log( | |
`Deleting all objects from bucket ${this.output.bucket} before deletion` | |
); | |
try { | |
// List all objects | |
const listResult = await listObjects(api, this.output.bucket); | |
if (listResult.Contents.length > 0) { | |
// Delete all objects | |
const keys = listResult.Contents.map((obj) => obj.Key); | |
const deleteResult = await deleteObjects( | |
api, | |
this.output.bucket, | |
keys | |
); | |
if (deleteResult.Errors && deleteResult.Errors.length > 0) { | |
console.error( | |
`Failed to delete some objects in bucket ${this.output.bucket}:`, | |
deleteResult.Errors | |
); | |
} | |
} | |
} catch (error) { | |
console.error( | |
`Error while emptying bucket ${this.output.bucket}:`, | |
error | |
); | |
} | |
} | |
// Delete bucket | |
const deletePath = `/${this.output.bucket}`; | |
const deleteResponse = await api.delete(deletePath); | |
// Check response status directly | |
if (!deleteResponse.ok && deleteResponse.status !== 404) { | |
console.error( | |
`Error deleting bucket ${this.output.bucket}:`, | |
deleteResponse.statusText | |
); | |
} | |
} | |
} catch (error) { | |
console.error(`Error deleting bucket ${bucketName}:`, error); | |
} | |
// Return destroyed state | |
return this.destroy(); | |
} else { | |
try { | |
let isUpdate = this.phase === "update" && this.output?.bucket; | |
// Create or verify bucket exists | |
if (!isUpdate) { | |
// Create new bucket | |
console.log(`Creating bucket ${bucketName}`); | |
const bucketPath = `/${bucketName}`; | |
const storageClass = props.storageClass || "STANDARD"; | |
const createParams: any = {}; | |
// Add storage class if not standard | |
if (storageClass !== "STANDARD") { | |
createParams.StorageClass = storageClass; | |
} | |
// Set parallel file system flag if specified | |
if (props.parallelFs) { | |
createParams.ParallelFileSystem = true; | |
} | |
// Set multi-AZ flag if specified | |
if (props.multiAz) { | |
createParams.MultiAZ = true; | |
} | |
const createResponse = await api.put(bucketPath, createParams); | |
if (!createResponse.ok) { | |
throw new Error( | |
`Failed to create bucket ${bucketName}: ${createResponse.statusText}` | |
); | |
} | |
} else { | |
// For updates, verify bucket exists | |
console.log(`Updating bucket ${bucketName}`); | |
const headResponse = await api.get(`/${bucketName}`); | |
if (!headResponse.ok) { | |
throw new Error(`Bucket ${bucketName} does not exist`); | |
} | |
} | |
// Apply bucket configurations | |
// Set ACL | |
if (props.acl) { | |
const aclResponse = await setBucketAcl(api, bucketName, props.acl); | |
if (!aclResponse.ok) { | |
console.error( | |
`Failed to set ACL on bucket ${bucketName}: ${aclResponse.statusText}` | |
); | |
} | |
} | |
// Set policy | |
if (props.policy) { | |
const policyResponse = await setBucketPolicy( | |
api, | |
bucketName, | |
props.policy | |
); | |
if (!policyResponse.ok) { | |
console.error( | |
`Failed to set policy on bucket ${bucketName}: ${policyResponse.statusText}` | |
); | |
} | |
} | |
// Set versioning | |
if (props.versioning !== undefined) { | |
const versioningResponse = await setBucketVersioning( | |
api, | |
bucketName, | |
props.versioning | |
); | |
if (!versioningResponse.ok) { | |
console.error( | |
`Failed to set versioning on bucket ${bucketName}: ${versioningResponse.statusText}` | |
); | |
} | |
} | |
// Set website configuration | |
if (props.website) { | |
const websiteResponse = await setBucketWebsite( | |
api, | |
bucketName, | |
props.website | |
); | |
if (!websiteResponse.ok) { | |
console.error( | |
`Failed to set website configuration on bucket ${bucketName}: ${websiteResponse.statusText}` | |
); | |
} | |
} else if (isUpdate && this.output?.website) { | |
// Remove website configuration if it was previously set but now removed | |
await deleteBucketWebsite(api, bucketName); | |
} | |
// Set CORS rules | |
if (props.corsRules && props.corsRules.length > 0) { | |
const corsResponse = await setBucketCors( | |
api, | |
bucketName, | |
props.corsRules | |
); | |
if (!corsResponse.ok) { | |
console.error( | |
`Failed to set CORS rules on bucket ${bucketName}: ${corsResponse.statusText}` | |
); | |
} | |
} else if ( | |
isUpdate && | |
this.output?.corsRules && | |
this.output.corsRules.length > 0 | |
) { | |
// Remove CORS rules if they were previously set but now removed | |
await deleteBucketCors(api, bucketName); | |
} | |
// Set lifecycle rules | |
if (props.lifecycleRules && props.lifecycleRules.length > 0) { | |
const lifecycleResponse = await setBucketLifecycle( | |
api, | |
bucketName, | |
props.lifecycleRules | |
); | |
if (!lifecycleResponse.ok) { | |
console.error( | |
`Failed to set lifecycle rules on bucket ${bucketName}: ${lifecycleResponse.statusText}` | |
); | |
} | |
} else if ( | |
isUpdate && | |
this.output?.lifecycleRules && | |
this.output.lifecycleRules.length > 0 | |
) { | |
// Remove lifecycle rules if they were previously set but now removed | |
await deleteBucketLifecycle(api, bucketName); | |
} | |
// Set logging configuration | |
if (props.logging) { | |
const loggingResponse = await setBucketLogging( | |
api, | |
bucketName, | |
props.logging | |
); | |
if (!loggingResponse.ok) { | |
console.error( | |
`Failed to set logging configuration on bucket ${bucketName}: ${loggingResponse.statusText}` | |
); | |
} | |
} else if (isUpdate && this.output?.logging) { | |
// Remove logging configuration if it was previously set but now removed | |
await deleteBucketLogging(api, bucketName); | |
} | |
// Set tags | |
if (props.tags && Object.keys(props.tags).length > 0) { | |
const tagsResponse = await setBucketTags(api, bucketName, props.tags); | |
if (!tagsResponse.ok) { | |
console.error( | |
`Failed to set tags on bucket ${bucketName}: ${tagsResponse.statusText}` | |
); | |
} | |
} else if ( | |
isUpdate && | |
this.output?.tags && | |
Object.keys(this.output.tags || {}).length > 0 | |
) { | |
// Remove tags if they were previously set but now removed | |
await deleteBucketTags(api, bucketName); | |
} | |
// Set encryption | |
if (props.encryption) { | |
const algorithm = props.sseAlgorithm || "AES256"; | |
const encryptionResponse = await setBucketEncryption( | |
api, | |
bucketName, | |
algorithm, | |
props.kmsKeyId | |
); | |
if (!encryptionResponse.ok) { | |
console.error( | |
`Failed to set encryption on bucket ${bucketName}: ${encryptionResponse.statusText}` | |
); | |
} | |
} else if (isUpdate && this.output?.encryption) { | |
// Remove encryption if it was previously set but now removed | |
await deleteBucketEncryption(api, bucketName); | |
} | |
// Quota would be set here | |
// Enterprise project ID would be set here | |
// User domain names would be set here | |
// Construct the output | |
const createdAt = | |
isUpdate && this.output?.createdAt | |
? this.output.createdAt | |
: Date.now(); | |
return this({ | |
id: bucketName, | |
bucket: bucketName, | |
storageClass: props.storageClass || "STANDARD", | |
acl: props.acl || "private", | |
policy: props.policy, | |
policyFormat: props.policyFormat || "obs", | |
versioning: props.versioning || false, | |
logging: props.logging, | |
quota: props.quota || 0, | |
lifecycleRules: props.lifecycleRules, | |
website: props.website, | |
corsRules: props.corsRules, | |
tags: props.tags, | |
region: region, | |
multiAz: props.multiAz, | |
parallelFs: props.parallelFs, | |
encryption: props.encryption || false, | |
sseAlgorithm: props.sseAlgorithm, | |
kmsKeyId: props.kmsKeyId, | |
kmsKeyProjectId: props.kmsKeyProjectId, | |
enterpriseProjectId: props.enterpriseProjectId, | |
userDomainNames: props.userDomainNames, | |
forceDestroy: props.forceDestroy || false, | |
bucketDomainName: `${bucketName}.obs.${region}.myhuaweicloud.com`, | |
storageInfo: | |
isUpdate && this.output?.storageInfo | |
? this.output.storageInfo | |
: undefined, | |
bucketVersion: | |
isUpdate && this.output?.bucketVersion | |
? this.output.bucketVersion | |
: undefined, | |
createdAt: createdAt, | |
}); | |
} catch (error) { | |
console.error(`Error creating/updating bucket ${bucketName}:`, error); | |
throw error; | |
} | |
} | |
} | |
); | |
/** | |
* Set bucket ACL | |
*/ | |
export async function setBucketAcl( | |
api: HuaweiApi, | |
bucket: string, | |
acl: string | |
): Promise<Response> { | |
return api.put(`/${bucket}?acl`, { acl }); | |
} | |
/** | |
* Set bucket policy | |
*/ | |
export async function setBucketPolicy( | |
api: HuaweiApi, | |
bucket: string, | |
policy: string | |
): Promise<Response> { | |
return api.put(`/${bucket}?policy`, policy); | |
} | |
/** | |
* Set bucket versioning | |
*/ | |
export async function setBucketVersioning( | |
api: HuaweiApi, | |
bucket: string, | |
enabled: boolean | |
): Promise<Response> { | |
const status = enabled ? "Enabled" : "Suspended"; | |
return api.put(`/${bucket}?versioning`, { | |
VersioningConfiguration: { | |
Status: status, | |
}, | |
}); | |
} | |
/** | |
* Set bucket website configuration | |
*/ | |
export async function setBucketWebsite( | |
api: HuaweiApi, | |
bucket: string, | |
config: BucketWebsiteConfig | |
): Promise<Response> { | |
const websiteConfig: any = { | |
WebsiteConfiguration: {}, | |
}; | |
if (config.redirectAllRequestsTo) { | |
websiteConfig.WebsiteConfiguration.RedirectAllRequestsTo = { | |
HostName: config.redirectAllRequestsTo, | |
}; | |
} else { | |
if (config.indexDocument) { | |
websiteConfig.WebsiteConfiguration.IndexDocument = { | |
Suffix: config.indexDocument, | |
}; | |
} | |
if (config.errorDocument) { | |
websiteConfig.WebsiteConfiguration.ErrorDocument = { | |
Key: config.errorDocument, | |
}; | |
} | |
if (config.routingRules) { | |
try { | |
const rules = JSON.parse(config.routingRules); | |
websiteConfig.WebsiteConfiguration.RoutingRules = { | |
RoutingRule: rules, | |
}; | |
} catch (e) { | |
throw new Error(`Invalid routing rules JSON: ${e}`); | |
} | |
} | |
} | |
return api.put(`/${bucket}?website`, websiteConfig); | |
} | |
/** | |
* Delete bucket website configuration | |
*/ | |
export async function deleteBucketWebsite( | |
api: HuaweiApi, | |
bucket: string | |
): Promise<Response> { | |
return api.delete(`/${bucket}?website`); | |
} | |
/** | |
* Set bucket CORS rules | |
*/ | |
export async function setBucketCors( | |
api: HuaweiApi, | |
bucket: string, | |
rules: BucketCorsRule[] | |
): Promise<Response> { | |
const corsConfig: any = { | |
CORSConfiguration: { | |
CORSRule: rules.map((rule) => ({ | |
AllowedOrigin: rule.allowedOrigins, | |
AllowedMethod: rule.allowedMethods, | |
AllowedHeader: rule.allowedHeaders, | |
ExposeHeader: rule.exposeHeaders, | |
MaxAgeSeconds: rule.maxAgeSeconds || 100, | |
})), | |
}, | |
}; | |
return api.put(`/${bucket}?cors`, corsConfig); | |
} | |
/** | |
* Delete bucket CORS configuration | |
*/ | |
export async function deleteBucketCors( | |
api: HuaweiApi, | |
bucket: string | |
): Promise<Response> { | |
return api.delete(`/${bucket}?cors`); | |
} | |
/** | |
* Set bucket lifecycle rules | |
*/ | |
export async function setBucketLifecycle( | |
api: HuaweiApi, | |
bucket: string, | |
rules: BucketLifecycleRule[] | |
): Promise<Response> { | |
const lifecycleConfig: any = { | |
LifecycleConfiguration: { | |
Rule: rules.map((rule) => { | |
const ruleConfig: any = { | |
ID: rule.name, | |
Status: rule.enabled ? "Enabled" : "Disabled", | |
Prefix: rule.prefix || "", | |
}; | |
if (rule.expiration) { | |
ruleConfig.Expiration = { | |
Days: rule.expiration.days, | |
}; | |
} | |
if (rule.transition && rule.transition.length > 0) { | |
ruleConfig.Transition = rule.transition.map((t) => ({ | |
Days: t.days, | |
StorageClass: t.storageClass, | |
})); | |
} | |
if (rule.noncurrentVersionExpiration) { | |
ruleConfig.NoncurrentVersionExpiration = { | |
NoncurrentDays: rule.noncurrentVersionExpiration.days, | |
}; | |
} | |
if ( | |
rule.noncurrentVersionTransition && | |
rule.noncurrentVersionTransition.length > 0 | |
) { | |
ruleConfig.NoncurrentVersionTransition = | |
rule.noncurrentVersionTransition.map((t) => ({ | |
NoncurrentDays: t.days, | |
StorageClass: t.storageClass, | |
})); | |
} | |
if (rule.abortIncompleteMultipartUpload) { | |
ruleConfig.AbortIncompleteMultipartUpload = { | |
DaysAfterInitiation: rule.abortIncompleteMultipartUpload.days, | |
}; | |
} | |
return ruleConfig; | |
}), | |
}, | |
}; | |
return api.put(`/${bucket}?lifecycle`, lifecycleConfig); | |
} | |
/** | |
* Delete bucket lifecycle configuration | |
*/ | |
export async function deleteBucketLifecycle( | |
api: HuaweiApi, | |
bucket: string | |
): Promise<Response> { | |
return api.delete(`/${bucket}?lifecycle`); | |
} | |
/** | |
* Set bucket logging configuration | |
*/ | |
export async function setBucketLogging( | |
api: HuaweiApi, | |
bucket: string, | |
config: BucketLoggingConfig | |
): Promise<Response> { | |
const loggingConfig: any = { | |
LoggingEnabled: { | |
TargetBucket: config.targetBucket, | |
TargetPrefix: config.targetPrefix || "logs/", | |
Agency: config.agency || "", | |
}, | |
}; | |
return api.put(`/${bucket}?logging`, { | |
BucketLoggingStatus: loggingConfig, | |
}); | |
} | |
/** | |
* Delete bucket logging configuration | |
*/ | |
export async function deleteBucketLogging( | |
api: HuaweiApi, | |
bucket: string | |
): Promise<Response> { | |
return api.put(`/${bucket}?logging`, { BucketLoggingStatus: {} }); | |
} | |
/** | |
* Set bucket tags | |
*/ | |
export async function setBucketTags( | |
api: HuaweiApi, | |
bucket: string, | |
tags: Record<string, string> | |
): Promise<Response> { | |
const tagsConfig: any = { | |
Tagging: { | |
TagSet: { | |
Tag: Object.entries(tags).map(([key, value]) => ({ | |
Key: key, | |
Value: value, | |
})), | |
}, | |
}, | |
}; | |
return api.put(`/${bucket}?tagging`, tagsConfig); | |
} | |
/** | |
* Delete bucket tags | |
*/ | |
export async function deleteBucketTags( | |
api: HuaweiApi, | |
bucket: string | |
): Promise<Response> { | |
return api.delete(`/${bucket}?tagging`); | |
} | |
/** | |
* Set bucket encryption | |
*/ | |
export async function setBucketEncryption( | |
api: HuaweiApi, | |
bucket: string, | |
algorithm: string, | |
kmsKeyId?: string | |
): Promise<Response> { | |
const encryptionConfig: any = { | |
ServerSideEncryptionConfiguration: { | |
Rule: [ | |
{ | |
ApplyServerSideEncryptionByDefault: { | |
SSEAlgorithm: algorithm, | |
}, | |
}, | |
], | |
}, | |
}; | |
if (kmsKeyId && algorithm === "kms") { | |
encryptionConfig.ServerSideEncryptionConfiguration.Rule[0].ApplyServerSideEncryptionByDefault.KMSMasterKeyID = | |
kmsKeyId; | |
} | |
return api.put(`/${bucket}?encryption`, encryptionConfig); | |
} | |
/** | |
* Delete bucket encryption | |
*/ | |
export async function deleteBucketEncryption( | |
api: HuaweiApi, | |
bucket: string | |
): Promise<Response> { | |
return api.delete(`/${bucket}?encryption`); | |
} | |
/** | |
* List objects in a bucket | |
*/ | |
export async function listObjects( | |
api: HuaweiApi, | |
bucket: string | |
): Promise<{ Contents: Array<{ Key: string }> }> { | |
const response = await api.get(`/${bucket}`); | |
if (!response.ok) { | |
throw new Error( | |
`Failed to list objects in bucket ${bucket}: ${response.statusText}` | |
); | |
} | |
const data = await response.text(); | |
// Parse XML response to extract object keys | |
// This is a simplified approach - in a real implementation you would use an XML parser | |
// Mock response for demonstration | |
const mockContents: Array<{ Key: string }> = []; | |
return { Contents: mockContents }; | |
} | |
/** | |
* Delete objects from a bucket | |
*/ | |
export async function deleteObjects( | |
api: HuaweiApi, | |
bucket: string, | |
keys: string[] | |
): Promise<{ Errors: any[] }> { | |
if (keys.length === 0) { | |
return { Errors: [] }; | |
} | |
const deleteConfig = { | |
Delete: { | |
Object: keys.map((key) => ({ Key: key })), | |
Quiet: true, | |
}, | |
}; | |
const response = await api.post(`/${bucket}?delete`, deleteConfig); | |
if (!response.ok) { | |
throw new Error( | |
`Failed to delete objects from bucket ${bucket}: ${response.statusText}` | |
); | |
} | |
// Mock response for demonstration | |
return { Errors: [] }; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment