44const _ = require ( 'lodash' ) ;
55const util = require ( 'util' ) ;
66const path = require ( 'path' ) ;
7+ const { default : Ajv } = require ( 'ajv' ) ;
78const P = require ( '../util/promise' ) ;
89const config = require ( '../../config' ) ;
910const RpcError = require ( '../rpc/rpc_error' ) ;
@@ -35,6 +36,7 @@ const NoobaaEvent = require('../manage_nsfs/manage_nsfs_events_utils').NoobaaEve
3536const dbg = require ( '../util/debug_module' ) ( __filename ) ;
3637const bucket_semaphore = new KeysSemaphore ( 1 ) ;
3738
39+ const ajv = new Ajv ( ) ;
3840
3941class BucketSpaceFS extends BucketSpaceSimpleFS {
4042 constructor ( { config_root } , stats ) {
@@ -339,7 +341,7 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
339341 return {
340342 _id : mongo_utils . mongoObjectId ( ) ,
341343 name,
342- tag : js_utils . default_value ( tag , undefined ) ,
344+ tag : js_utils . default_value ( tag , BucketSpaceFS . _default_bucket_tags ( ) ) ,
343345 owner_account : account . owner ? account . owner : account . _id , // The account is the owner of the buckets that were created by it or by its users.
344346 creator : account . _id ,
345347 versioning : config . NSFS_VERSIONING_ENABLED && lock_enabled ? 'ENABLED' : 'DISABLED' ,
@@ -485,12 +487,21 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
485487 // BUCKET TAGGING //
486488 ////////////////////
487489
488- async put_bucket_tagging ( params ) {
490+ /**
491+ * @param {* } params
492+ * @param {nb.ObjectSDK } object_sdk
493+ */
494+ async put_bucket_tagging ( params , object_sdk ) {
489495 try {
490496 const { name, tagging } = params ;
491497 dbg . log0 ( 'BucketSpaceFS.put_bucket_tagging: Bucket name, tagging' , name , tagging ) ;
492498 const bucket = await this . config_fs . get_bucket_by_name ( name ) ;
493- bucket . tag = tagging ;
499+ const { ns } = await object_sdk . read_bucket_full_info ( name ) ;
500+ const is_bucket_empty = await BucketSpaceFS . #_is_bucket_empty( name , null , ns , object_sdk ) ;
501+
502+ bucket . tag = BucketSpaceFS . _merge_reserved_tags (
503+ bucket . tag || BucketSpaceFS . _default_bucket_tags ( ) , tagging , is_bucket_empty
504+ ) ;
494505 await this . config_fs . update_bucket_config_file ( bucket ) ;
495506 } catch ( error ) {
496507 throw translate_error_codes ( error , entity_enum . BUCKET ) ;
@@ -502,7 +513,16 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
502513 const { name } = params ;
503514 dbg . log0 ( 'BucketSpaceFS.delete_bucket_tagging: Bucket name' , name ) ;
504515 const bucket = await this . config_fs . get_bucket_by_name ( name ) ;
505- delete bucket . tag ;
516+
517+ const preserved_tags = [ ] ;
518+ for ( const tag of bucket . tag ) {
519+ const tag_info = config . NSFS_GLACIER_RESERVED_BUCKET_TAGS [ tag . key ] ;
520+ if ( tag_info ?. immutable ) {
521+ preserved_tags . push ( tag ) ;
522+ }
523+ }
524+
525+ bucket . tag = preserved_tags ;
506526 await this . config_fs . update_bucket_config_file ( bucket ) ;
507527 } catch ( error ) {
508528 throw translate_error_codes ( error , entity_enum . BUCKET ) ;
@@ -520,6 +540,40 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
520540 }
521541 }
522542
543+ static _default_bucket_tags ( ) {
544+ return Object . keys (
545+ config . NSFS_GLACIER_RESERVED_BUCKET_TAGS
546+ ) . map ( key => ( { key, value : config . NSFS_GLACIER_RESERVED_BUCKET_TAGS [ key ] . default } ) ) ;
547+ }
548+
549+ static _merge_reserved_tags ( original_tags , new_tags , is_bucket_empty ) {
550+ const merged_tags = original_tags . reduce ( ( curr , tag ) => {
551+ if ( ! config . NSFS_GLACIER_RESERVED_BUCKET_TAGS [ tag . key ] ) return curr ;
552+ return Object . assign ( curr , { [ tag . key ] : tag . value } ) ;
553+ } , { } ) ;
554+
555+ for ( const tag of new_tags ) {
556+ const tag_info = config . NSFS_GLACIER_RESERVED_BUCKET_TAGS [ tag . key ] ;
557+ if ( ! tag_info ) {
558+ merged_tags [ tag . key ] = tag . value ;
559+ continue ;
560+ }
561+ if ( ! tag_info . immutable || ( tag_info . immutable === 'if-data' && is_bucket_empty ) ) {
562+ let validator = ajv . getSchema ( tag_info . schema . $id ) ;
563+ if ( ! validator ) {
564+ ajv . addSchema ( tag_info . schema ) ;
565+ validator = ajv . getSchema ( tag_info . schema . $id ) ;
566+ }
567+
568+ if ( validator ( tag . value ) ) {
569+ merged_tags [ tag . key ] = tag . value ;
570+ }
571+ }
572+ }
573+
574+ return Object . keys ( merged_tags ) . map ( key => ( { key, value : merged_tags [ key ] } ) ) ;
575+ }
576+
523577 ////////////////////
524578 // BUCKET LOGGING //
525579 ////////////////////
@@ -933,6 +987,25 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
933987
934988 return storage_classes ;
935989 }
990+
991+ static async #_is_bucket_empty( name , params , ns , object_sdk ) {
992+ params = params || { } ;
993+
994+ let list ;
995+ try {
996+ if ( ns . _is_versioning_disabled ( ) ) {
997+ list = await ns . list_objects ( { ...params , bucket : name , limit : 1 } , object_sdk ) ;
998+ } else {
999+ list = await ns . list_object_versions ( { ...params , bucket : name , limit : 1 } , object_sdk ) ;
1000+ }
1001+ } catch ( err ) {
1002+ dbg . warn ( '#_is_bucket_empty: bucket name' , name , 'got an error while trying to list_objects' , err ) ;
1003+ // in case the ULS was deleted - we will continue
1004+ if ( err . rpc_code !== 'NO_SUCH_BUCKET' ) throw err ;
1005+ }
1006+
1007+ return ! ( list && list . objects && list . objects . length > 0 ) ;
1008+ }
9361009}
9371010
9381011module . exports = BucketSpaceFS ;
0 commit comments