1- /* eslint-disable max-classes-per-file */
21import {
32 GetObjectCommand ,
4- ListBucketsCommand ,
5- HeadBucketCommand
63} from '@aws-sdk/client-s3'
74import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
85import { s3 } from './aws-clients.js'
96import logger from './logger.js'
7+ import { AssetBuckets , BucketOptionEnum } from './asset-buckets.js'
108
119const s3Client = s3 ( )
1210
1311const S3_URL_REGEX = / ^ s 3 : \/ \/ ( [ ^ / ] + ) \/ ( .+ ) $ /
1412
1513export const ALTERNATE_ASSETS_EXTENSION = 'https://stac-extensions.github.io/alternate-assets/v1.2.0/schema.json'
1614
17- export const BucketOption = Object . freeze ( {
18- NONE : 'NONE' ,
19- ALL : 'ALL' ,
20- ALL_BUCKETS_IN_ACCOUNT : 'ALL_BUCKETS_IN_ACCOUNT' ,
21- LIST : 'LIST'
22- } )
23-
2415/**
2516 * @param {string } url - S3 URL to parse
2617 * @returns {Object } {bucket, key} or {bucket: null, key: null} if not a valid S3 URL
2718 */
28- const parseS3Url = ( url ) => {
19+ export const parseS3Url = ( url ) => {
2920 const match = S3_URL_REGEX . exec ( url )
3021 if ( ! match ) return { bucket : null , key : null }
3122
3223 const [ , bucket , key ] = match
3324 return { bucket, key }
3425}
3526
36- class AssetBuckets {
37- /**
38- * @param {string } bucketOption - Bucket option (NONE, ALL, ALL_BUCKETS_IN_ACCOUNT, LIST)
39- * @param {string[]|null } bucketNames - Array of bucket names (required for LIST option)
40- */
41- constructor ( bucketOption , bucketNames ) {
42- this . bucketOption = bucketOption
43- this . bucketNames = bucketNames
44- this . buckets = { }
45- }
46-
47- /**
48- * @param {string } bucketOption - Bucket option (NONE, ALL, ALL_BUCKETS_IN_ACCOUNT, LIST)
49- * @param {string[]|null } bucketNames - Array of bucket names (required for LIST option)
50- * @returns {Promise<AssetBuckets> } Initialized AssetBuckets instance
51- */
52- static async create ( bucketOption , bucketNames ) {
53- const instance = new AssetBuckets ( bucketOption , bucketNames )
54- await instance . _initBuckets ( )
55- return instance
56- }
57-
58- /**
59- * @returns {Promise<void> }
60- */
61- async _initBuckets ( ) {
62- switch ( this . bucketOption ) {
63- case BucketOption . LIST : {
64- if ( this . bucketNames && this . bucketNames . length > 0 ) {
65- await Promise . all (
66- this . bucketNames . map ( async ( name ) => { await this . getBucket ( name ) } )
67- )
68-
69- const invalidBuckets = Object . keys ( this . buckets )
70- . filter ( ( bucketName ) => this . buckets [ bucketName ] . region === null )
71- if ( invalidBuckets . length > 0 ) {
72- throw new Error (
73- `Could not access or determine region for the following buckets: ${
74- invalidBuckets . join ( ', ' ) } `
75- )
76- }
77-
78- const count = Object . keys ( this . buckets ) . length
79- logger . info (
80- `Parsed ${ count } buckets from ASSET_PROXY_BUCKET_LIST for asset proxy`
81- )
82- } else {
83- throw new Error (
84- 'ASSET_PROXY_BUCKET_LIST must not be empty when ASSET_PROXY_BUCKET_OPTION is LIST'
85- )
86- }
87- break
88- }
89-
90- case BucketOption . ALL_BUCKETS_IN_ACCOUNT : {
91- const command = new ListBucketsCommand ( { } )
92- const response = await s3Client . send ( command )
93- const buckets = response . Buckets || [ ]
94-
95- await Promise . all (
96- buckets
97- . map ( ( bucket ) => bucket . Name )
98- . filter ( ( name ) => typeof name === 'string' )
99- . map ( async ( name ) => { await this . getBucket ( name ) } )
100- )
101-
102- const count = Object . keys ( this . buckets ) . length
103- logger . info (
104- `Fetched ${ count } buckets from AWS account for asset proxy`
105- )
106- break
107- }
108-
109- default :
110- break
111- }
112- }
113-
114- /**
115- * @param {string } bucketName - S3 bucket name
116- * @returns {Promise<Object> } Bucket info {name, region}
117- */
118- async getBucket ( bucketName ) {
119- if ( ! ( bucketName in this . buckets ) ) {
120- const command = new HeadBucketCommand ( { Bucket : bucketName } )
121- const response = await s3Client . send ( command )
122- const statusCode = response . $metadata . httpStatusCode
123- let name = null
124- let region = null
125-
126- switch ( statusCode ) {
127- case 200 :
128- name = bucketName
129- region = response . BucketRegion === 'EU'
130- ? 'eu-west-1'
131- : response . BucketRegion || 'us-east-1'
132- break
133- case 403 :
134- logger . warn ( `Access denied to bucket ${ bucketName } ` )
135- break
136- case 404 :
137- logger . warn ( `Bucket ${ bucketName } does not exist` )
138- break
139- case 400 :
140- logger . warn ( `Bad request for bucket ${ bucketName } ` )
141- break
142- default :
143- logger . warn ( `Unexpected status code ${ statusCode } for bucket ${ bucketName } ` )
144- }
145-
146- this . buckets [ bucketName ] = { name, region }
147- }
148- return this . buckets [ bucketName ]
149- }
150-
151- /**
152- * @param {string } bucketName - S3 bucket name
153- * @returns {boolean } True if bucket should be proxied, False otherwise
154- */
155- shouldProxyBucket ( bucketName ) {
156- if ( this . bucketOption === BucketOption . ALL
157- || bucketName in this . buckets ) {
158- return true
159- }
160- return false
161- }
162- }
163-
16427export class AssetProxy {
16528 /**
16629 * @param {AssetBuckets } buckets - AssetBuckets instance
@@ -170,7 +33,7 @@ export class AssetProxy {
17033 constructor ( buckets , urlExpiry , bucketOption ) {
17134 this . buckets = buckets
17235 this . urlExpiry = urlExpiry
173- this . isEnabled = bucketOption !== BucketOption . NONE
36+ this . isEnabled = bucketOption !== BucketOptionEnum . NONE
17437 }
17538
17639 /**
@@ -182,7 +45,7 @@ export class AssetProxy {
18245 const bucketList = process . env [ 'ASSET_PROXY_BUCKET_LIST' ]
18346
18447 let bucketNames = null
185- if ( bucketOption === BucketOption . LIST ) {
48+ if ( bucketOption === BucketOptionEnum . LIST ) {
18649 if ( ! bucketList ) {
18750 throw new Error (
18851 'ASSET_PROXY_BUCKET_LIST must be set when ASSET_PROXY_BUCKET_OPTION is LIST'
0 commit comments