diff --git a/src/cmd/manage_nsfs.js b/src/cmd/manage_nsfs.js index 1491ce1048..f1b2309444 100644 --- a/src/cmd/manage_nsfs.js +++ b/src/cmd/manage_nsfs.js @@ -14,6 +14,7 @@ const SensitiveString = require('../util/sensitive_string'); const ManageCLIError = require('../manage_nsfs/manage_nsfs_cli_errors').ManageCLIError; const ManageCLIResponse = require('../manage_nsfs/manage_nsfs_cli_responses').ManageCLIResponse; const bucket_policy_utils = require('../endpoint/s3/s3_bucket_policy_utils'); +const nsfs_schema_utils = require('../manage_nsfs/nsfs_schema_utils'); const TYPES = { ACCOUNT: 'account', @@ -199,6 +200,7 @@ async function main(argv = minimist(process.argv.slice(2))) { dbg.log1('NSFS Manage command: exit on error', err.stack || err); const manage_err = ((err instanceof ManageCLIError) && err) || new ManageCLIError(ManageCLIError.FS_ERRORS_TO_MANAGE[err.code] || + ManageCLIError.RPC_ERROR_TO_MANAGE[err.rpc_code] || ManageCLIError.InternalError); throw_cli_error(manage_err, err.stack || err); } @@ -254,7 +256,10 @@ async function fetch_bucket_data(argv, from_file) { bucket_owner: new SensitiveString(String(data.bucket_owner)), // update bucket identifier new_name: data.new_name && new SensitiveString(String(data.new_name)), - fs_backend: data.fs_backend || undefined + // fs_backend deletion specified with empty string '' (but it is not part of the schema) + fs_backend: data.fs_backend || undefined, + // s3_policy deletion specified with empty string '' (but it is not part of the schema) + s3_policy: data.s3_policy || undefined, }; return data; @@ -288,6 +293,10 @@ async function add_bucket(data) { if (exists) throw_cli_error(ManageCLIError.BucketAlreadyExists, data.name.unwrap()); const data_json = JSON.stringify(data); + // We take an object that was stringify + // (it unwraps ths sensitive strings, creation_date to string and removes undefined parameters) + // for validating against the schema we need an object, hence we parse it back to object + nsfs_schema_utils.validate_bucket_schema(JSON.parse(data_json)); await native_fs_utils.create_config_file(fs_context, buckets_dir_path, bucket_conf_path, data_json); write_stdout_response(ManageCLIResponse.BucketCreated, data_json); } @@ -316,6 +325,10 @@ async function update_bucket(data) { if (!update_name) { const bucket_config_path = get_config_file_path(buckets_dir_path, data.name); data = JSON.stringify(data); + // We take an object that was stringify + // (it unwraps ths sensitive strings, creation_date to string and removes undefined parameters) + // for validating against the schema we need an object, hence we parse it back to object + nsfs_schema_utils.validate_bucket_schema(JSON.parse(data)); await native_fs_utils.update_config_file(fs_context, buckets_dir_path, bucket_config_path, data); write_stdout_response(ManageCLIResponse.BucketUpdated, data); return; @@ -330,7 +343,10 @@ async function update_bucket(data) { if (exists) throw_cli_error(ManageCLIError.BucketAlreadyExists, data.name.unwrap()); data = JSON.stringify(_.omit(data, ['new_name'])); - + // We take an object that was stringify + // (it unwraps ths sensitive strings, creation_date to string and removes undefined parameters) + // for validating against the schema we need an object, hence we parse it back to object + nsfs_schema_utils.validate_bucket_schema(JSON.parse(data)); await native_fs_utils.create_config_file(fs_context, buckets_dir_path, new_bucket_config_path, data); await native_fs_utils.delete_config_file(fs_context, buckets_dir_path, cur_bucket_config_path); write_stdout_response(ManageCLIResponse.BucketUpdated, data); @@ -430,8 +446,8 @@ async function fetch_account_data(argv, from_file) { access_keys, nsfs_account_config: { distinguished_name: argv.user, - uid: !argv.user && argv.uid, - gid: !argv.user && argv.gid, + uid: argv.user ? undefined : argv.uid, + gid: argv.user ? undefined : argv.gid, new_buckets_path: argv.new_buckets_path, fs_backend: argv.fs_backend ? String(argv.fs_backend) : config.NSFS_NC_STORAGE_BACKEND } @@ -456,6 +472,7 @@ async function fetch_account_data(argv, from_file) { uid: data.nsfs_account_config.uid && Number(data.nsfs_account_config.uid), gid: data.nsfs_account_config.gid && Number(data.nsfs_account_config.gid), new_buckets_path: data.nsfs_account_config.new_buckets_path, + // fs_backend deletion specified with empty string '' (but it is not part of the schema) fs_backend: data.nsfs_account_config.fs_backend || undefined }, allow_bucket_creation: !is_undefined(data.nsfs_account_config.new_buckets_path), @@ -503,6 +520,10 @@ async function add_account(data) { } data = JSON.stringify(data); + // We take an object that was stringify + // (it unwraps ths sensitive strings, creation_date to string and removes undefined parameters) + // for validating against the schema we need an object, hence we parse it back to object + nsfs_schema_utils.validate_account_schema(JSON.parse(data)); await native_fs_utils.create_config_file(fs_context, accounts_dir_path, account_config_path, data); await native_fs_utils._create_path(access_keys_dir_path, fs_context, config.BASE_MODE_CONFIG_DIR); await nb_native().fs.symlink(fs_context, account_config_path, account_config_access_key_path); @@ -522,6 +543,10 @@ async function update_account(data) { if (!update_name && !update_access_key) { const account_config_path = get_config_file_path(accounts_dir_path, data.name); data = JSON.stringify(data); + // We take an object that was stringify + // (it unwraps ths sensitive strings, creation_date to string and removes undefined parameters) + // for validating against the schema we need an object, hence we parse it back to object + nsfs_schema_utils.validate_account_schema(JSON.parse(data)); await native_fs_utils.update_config_file(fs_context, accounts_dir_path, account_config_path, data); write_stdout_response(ManageCLIResponse.AccountUpdated, data); return; @@ -540,6 +565,10 @@ async function update_account(data) { throw_cli_error(err_code); } data = JSON.stringify(_.omit(data, ['new_name', 'new_access_key'])); + // We take an object that was stringify + // (it unwraps ths sensitive strings, creation_date to string and removes undefined parameters) + // for validating against the schema we need an object, hence we parse it back to object + nsfs_schema_utils.validate_account_schema(JSON.parse(data)); if (update_name) { await native_fs_utils.create_config_file(fs_context, accounts_dir_path, new_account_config_path, data); await native_fs_utils.delete_config_file(fs_context, accounts_dir_path, cur_account_config_path); diff --git a/src/manage_nsfs/manage_nsfs_cli_errors.js b/src/manage_nsfs/manage_nsfs_cli_errors.js index 25303c888e..d3a9e9d9eb 100644 --- a/src/manage_nsfs/manage_nsfs_cli_errors.js +++ b/src/manage_nsfs/manage_nsfs_cli_errors.js @@ -72,6 +72,11 @@ ManageCLIError.MissingConfigDirPath = Object.freeze({ message: 'Config dir path should not be empty', http_code: 400, }); +ManageCLIError.InvalidSchema = Object.freeze({ + code: 'InvalidSchema', + message: 'Schema invalid, please use required properties', + http_code: 400, +}); ////////////////////////////// //// IP WHITE LIST ERRORS //// @@ -301,4 +306,8 @@ ManageCLIError.FS_ERRORS_TO_MANAGE = Object.freeze({ // EEXIST: ManageCLIError.BucketAlreadyExists, }); +ManageCLIError.RPC_ERROR_TO_MANAGE = Object.freeze({ + INVALID_SCHEMA: ManageCLIError.InvalidSchema, +}); + exports.ManageCLIError = ManageCLIError; diff --git a/src/manage_nsfs/nsfs_schema_utils.js b/src/manage_nsfs/nsfs_schema_utils.js new file mode 100644 index 0000000000..87ef53e078 --- /dev/null +++ b/src/manage_nsfs/nsfs_schema_utils.js @@ -0,0 +1,43 @@ +/* Copyright (C) 2023 NooBaa */ +'use strict'; + +const RpcError = require('../rpc/rpc_error'); +const { default: Ajv } = require('ajv'); +const ajv = new Ajv({ verbose: true, allErrors: true }); +const { KEYWORDS } = require('../util/schema_keywords'); +const common_api = require('../api/common_api'); +const schema_utils = require('../util/schema_utils'); + +ajv.addKeyword(KEYWORDS.methods); +ajv.addKeyword(KEYWORDS.doc); +ajv.addKeyword(KEYWORDS.date); +ajv.addKeyword(KEYWORDS.idate); +ajv.addKeyword(KEYWORDS.objectid); +ajv.addKeyword(KEYWORDS.binary); +ajv.addKeyword(KEYWORDS.wrapper); +ajv.addSchema(common_api); + +const bucket_schema = require('../server/object_services/schemas/nsfs_bucket_schema'); +const account_schema = require('../server/object_services/schemas/nsfs_account_schema'); + +schema_utils.strictify(bucket_schema, { + additionalProperties: false +}); + +schema_utils.strictify(account_schema, { + additionalProperties: false +}); + +function validate_account_schema(account) { + const valid = ajv.validate(account_schema, account); + if (!valid) throw new RpcError('INVALID_SCHEMA', ajv.errors[0]?.message); +} + +function validate_bucket_schema(bucket) { + const valid = ajv.validate(bucket_schema, bucket); + if (!valid) throw new RpcError('INVALID_SCHEMA', ajv.errors[0]?.message); +} + +//EXPORTS +exports.validate_account_schema = validate_account_schema; +exports.validate_bucket_schema = validate_bucket_schema; diff --git a/src/sdk/bucketspace_fs.js b/src/sdk/bucketspace_fs.js index 9d8c797211..8ba1a7791a 100644 --- a/src/sdk/bucketspace_fs.js +++ b/src/sdk/bucketspace_fs.js @@ -13,11 +13,7 @@ const BucketSpaceSimpleFS = require('./bucketspace_simple_fs'); const _ = require('lodash'); const util = require('util'); const bucket_policy_utils = require('../endpoint/s3/s3_bucket_policy_utils'); -const { default: Ajv } = require('ajv'); -const bucket_schema = require('../server/object_services/schemas/nsfs_bucket_schema'); -const account_schema = require('../server/object_services/schemas/nsfs_account_schema'); -const { KEYWORDS } = require('../util/schema_keywords'); -const common_api = require('../api/common_api'); +const nsfs_schema_utils = require('../manage_nsfs/nsfs_schema_utils'); const KeysSemaphore = require('../util/keys_semaphore'); const native_fs_utils = require('../util/native_fs_utils'); @@ -27,15 +23,6 @@ const dbg = require('../util/debug_module')(__filename); const BUCKET_PATH = 'buckets'; const ACCOUNT_PATH = 'accounts'; const ACCESS_KEYS_PATH = 'access_keys'; -const ajv = new Ajv({ verbose: true, allErrors: true }); -ajv.addKeyword(KEYWORDS.methods); -ajv.addKeyword(KEYWORDS.doc); -ajv.addKeyword(KEYWORDS.date); -ajv.addKeyword(KEYWORDS.idate); -ajv.addKeyword(KEYWORDS.objectid); -ajv.addKeyword(KEYWORDS.binary); -ajv.addKeyword(KEYWORDS.wrapper); -ajv.addSchema(common_api); const bucket_semaphore = new KeysSemaphore(1); //TODO: dup from namespace_fs - need to handle and not dup code @@ -109,7 +96,7 @@ class BucketSpaceFS extends BucketSpaceSimpleFS { const iam_path = this._get_access_keys_config_path(access_key); const { data } = await nb_native().fs.readFile(this.fs_context, iam_path); const account = JSON.parse(data.toString()); - this.validate_account_schema(account); + nsfs_schema_utils.validate_account_schema(account); account.name = new SensitiveString(account.name); account.email = new SensitiveString(account.email); for (const k of account.access_keys) { @@ -137,23 +124,13 @@ class BucketSpaceFS extends BucketSpaceSimpleFS { } } - validate_account_schema(account) { - const valid = ajv.validate(account_schema, account); - if (!valid) throw new RpcError('INVALID_SCHEMA', ajv.errors[0]?.message); - } - - validate_bucket_schema(bucket) { - const valid = ajv.validate(bucket_schema, bucket); - if (!valid) throw new RpcError('INVALID_SCHEMA', ajv.errors[0]?.message); - } - async read_bucket_sdk_info({ name }) { try { const bucket_config_path = this._get_bucket_config_path(name); dbg.log0('BucketSpaceFS.read_bucket_sdk_info: bucket_config_path', bucket_config_path); const { data } = await nb_native().fs.readFile(this.fs_context, bucket_config_path); const bucket = JSON.parse(data.toString()); - this.validate_bucket_schema(bucket); + nsfs_schema_utils.validate_bucket_schema(bucket); const is_valid = await this.check_bucket_config(bucket); if (!is_valid) { dbg.warn('BucketSpaceFS: one or more bucket config check is failed for bucket : ', name); diff --git a/src/server/object_services/schemas/nsfs_bucket_schema.js b/src/server/object_services/schemas/nsfs_bucket_schema.js index 17d6560738..40b788f7cd 100644 --- a/src/server/object_services/schemas/nsfs_bucket_schema.js +++ b/src/server/object_services/schemas/nsfs_bucket_schema.js @@ -28,6 +28,9 @@ module.exports = { }, versioning: { type: 'string', + enum: ['DISABLED', 'SUSPENDED', 'ENABLED'] + // GAP would like to use $ref: 'bucket_api#/definitions/versioning' + // but currently it creates an error Error: reference "bucket_api" resolves to more than one schema }, path: { type: 'string', diff --git a/src/test/unit_tests/jest_tests/test_nc_nsfs_schema_validation.test.js b/src/test/unit_tests/jest_tests/test_nc_nsfs_schema_validation.test.js new file mode 100644 index 0000000000..27f7e7d91b --- /dev/null +++ b/src/test/unit_tests/jest_tests/test_nc_nsfs_schema_validation.test.js @@ -0,0 +1,1260 @@ +/* Copyright (C) 2024 NooBaa */ +/* eslint-disable no-undef */ +/*eslint max-lines-per-function: ["error", 800]*/ + +'use strict'; + +const nsfs_schema_utils = require('../../../manage_nsfs/nsfs_schema_utils'); +const RpcError = require('../../../rpc/rpc_error'); +const test_utils = require('../../system_tests/test_utils'); + +// function that we use to make sure that we are in the catch clause +// it would fail the test +function throw_error_in_case_error_was_not_thrown() { + throw new Error('Test passed instead of failed'); +} + +describe('schema validation NC NSFS account', () => { + const account_name = 'account1'; + const account_email = 'account1@noobaa.io'; + const access_key = 'GIGiFAnjaaE7OKD5N7hA'; + const secret_key = 'U2AYaMpU3zRDcRFWmvzgQr9MoHIAsD+3oEXAMPLE'; + const creation_date = new Date('December 17, 2023 09:00:00').toISOString(); + const nsfs_account_config_uid_gid = { + uid: 1001, + gid: 1001, + }; + const nsfs_account_config_distinguished_name = { + distinguished_name: 'moti-1003', + }; + const new_bucket_path = '/tmp/nsfs_root1'; + + describe('account with all needed properties', () => { + + it('nsfs_account_config with uid, gid', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + nsfs_schema_utils.validate_account_schema(account_data); + }); + + it('nsfs_account_config with distinguished_name', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_distinguished_name + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + nsfs_schema_utils.validate_account_schema(account_data); + }); + + it('nsfs_account_config with fs_backend', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + fs_backend: 'GPFS' // added + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + nsfs_schema_utils.validate_account_schema(account_data); + }); + + it('nsfs_account_config with new_bucket_path', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + new_buckets_path: new_bucket_path //added + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + nsfs_schema_utils.validate_account_schema(account_data); + }); + + }); + + describe('account with additional properties', () => { + + it('nsfs_account_config with distinguished_name AND uid gid', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + // here we use both uid gid and distinguished_name + ...nsfs_account_config_uid_gid, + ...nsfs_account_config_distinguished_name + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must NOT have additional properties'); + } + }); + + it('account with new_name', () => { + const account_data = { + name: account_name, + new_name: 'account2', // this is not part of the schema + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must NOT have additional properties'); + } + }); + + // note: it is new_access_key (and not new_access_keys in the code) + it('account with new_access_key', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + // this is not part of the schema + new_access_key: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must NOT have additional properties'); + } + }); + + it('account with duration_seconds inside access_keys', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key, + duration_seconds: 3600, // this is not part of the schema + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must NOT have additional properties'); + } + }); + + it('account with my_id inside nsfs_account_config', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key, + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + my_id: 123, // this is not part of the schema + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must NOT have additional properties'); + } + }); + + }); + + // note: had to use " " (double quotes) instead of ' ' (single quotes) to match the message + describe('account without required properties', () => { + + it('account without name', () => { + const account_data = { + // name: account_name, // hide it on purpose + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'name'"); + } + }); + + it('account with undefined name', () => { + const account_data = { + name: undefined, // on purpose + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'name'"); + } + }); + + it('account without email', () => { + const account_data = { + name: account_name, + // email: account_email, // hide it on purpose + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'email'"); + } + }); + + it('account with undefined email', () => { + const account_data = { + name: account_name, + email: undefined, // on purpose + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'email'"); + } + }); + + it('account without access_keys', () => { + const account_data = { + name: account_name, + email: account_email, + // access_keys: [ // hide it on purpose + // { + // access_key: access_key, + // secret_key: secret_key + // }, + // ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'access_keys'"); + } + }); + + it('account with undefined access_keys', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: undefined, // on purpose + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'access_keys'"); + } + }); + + it('account without access_keys details (access_key and secret_key)', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + // hide it on purpose + // access_key: access_key, + // secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'access_key'"); + } + }); + + it('account without access_key', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + // access_key: access_key, // hide it on purpose + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'access_key'"); + } + }); + + it('account with undefined access_key', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: undefined, // on purpose + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'access_key'"); + } + }); + + it('account without secret_key', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + // secret_key: secret_key // hide it on purpose + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'secret_key'"); + } + }); + + it('account with undefined secret_key', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: undefined // on purpose + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'secret_key'"); + } + }); + + it('account without nsfs_account_config', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + // nsfs_account_config: { // hide it on purpose + // ...nsfs_account_config_uid_gid, + // }, + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', + "must have required property 'nsfs_account_config'"); + } + }); + + it('account with undefined nsfs_account_config', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: undefined, // on purpose + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', + "must have required property 'nsfs_account_config'"); + } + }); + + it('account without nsfs_account_config details (uid gid or distinguished_name)', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + // ...nsfs_account_config_uid_gid, // hide it on purpose + }, + creation_date: creation_date, + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'uid'"); + } + }); + + it('account without creation_date', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + // creation_date: creation_date, // hide it on purpose + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', + "must have required property 'creation_date'"); + } + }); + + it('account with undefined creation_date', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: undefined, // on purpose + allow_bucket_creation: false, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', + "must have required property 'creation_date'"); + } + }); + + it('account without allow_bucket_creation', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + // allow_bucket_creation: false, // hide it on purpose + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', + "must have required property 'allow_bucket_creation'"); + } + }); + + it('account with undefined allow_bucket_creation', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: creation_date, + allow_bucket_creation: undefined, // on purpose + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', + "must have required property 'allow_bucket_creation'"); + } + }); + + }); + + describe('account with wrong types', () => { + + it('nsfs_account_config with uid, gid as boolean (instead of number)', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + // boolean instead of number + uid: false, + gid: false, + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must be number'); + } + }); + + it('nsfs_account_config with creation_date as Date (instead of string)', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + }, + creation_date: Date.now(), // Date instead of string + allow_bucket_creation: true, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must be string'); + } + }); + + it('nsfs_account_config with fs_backend not part of enum', () => { + const account_data = { + name: account_name, + email: account_email, + access_keys: [ + { + access_key: access_key, + secret_key: secret_key + }, + ], + nsfs_account_config: { + ...nsfs_account_config_uid_gid, + fs_backend: '' // not part of definition of fs_backend + }, + creation_date: creation_date, + allow_bucket_creation: true, + }; + try { + nsfs_schema_utils.validate_account_schema(account_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must be equal to one of the allowed values'); + } + }); + + }); + +}); + +describe('schema validation NC NSFS bucket', () => { + const bucket_name = 'bucket1'; + const system_owner = 'account1@noobaa.io'; + const bucket_owner = 'account1@noobaa.io'; + const versioning_disabled = 'DISABLED'; + const versioning_enabled = 'ENABLED'; + const creation_date = new Date('December 17, 2023 09:00:00').toISOString(); + const path = '/tmp/nsfs_root1'; + const bucket_policy = test_utils.generate_s3_policy('*', bucket_name, ['s3:*']).policy; + + describe('account with all needed properties', () => { + + it('nsfs_bucket', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: true, + }; + nsfs_schema_utils.validate_bucket_schema(bucket_data); + }); + + it('nsfs_bucket with tag', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_enabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: true, + tag: 'myTag', // added + }; + nsfs_schema_utils.validate_bucket_schema(bucket_data); + }); + + it('nsfs_bucket with fs_backend', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: true, + fs_backend: 'CEPH_FS', // added + }; + nsfs_schema_utils.validate_bucket_schema(bucket_data); + }); + + it('nsfs_bucket with s3_policy (bucket policy)', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + s3_policy: bucket_policy, // added + }; + nsfs_schema_utils.validate_bucket_schema(bucket_data); + }); + + }); + + describe('bucket with additional properties', () => { + + it('bucket with new_name', () => { + const bucket_data = { + name: bucket_name, + new_name: 'bucket', // this is not part of the schema + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must NOT have additional properties'); + } + }); + + }); + + // note: had to use " " (double quotes) instead of ' ' (single quotes) to match the message + describe('bucket without required properties', () => { + + it('bucket without name', () => { + const bucket_data = { + // name: bucket_name, // hide it on purpose + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'name'"); + } + }); + + it('bucket with undefined name', () => { + const bucket_data = { + name: undefined, // on purpose + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'name'"); + } + }); + + it('bucket without system_owner', () => { + const bucket_data = { + name: bucket_name, + // system_owner: system_owner, // hide it on purpose + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'system_owner'"); + } + }); + + it('bucket with undefined system_owner', () => { + const bucket_data = { + name: bucket_name, + system_owner: undefined, // on purpose + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'system_owner'"); + } + }); + + it('bucket without bucket_owner', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + // bucket_owner: bucket_owner, // hide it on purpose + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'bucket_owner'"); + } + }); + + it('bucket with undefined bucket_owner', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: undefined, // on purpose + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'bucket_owner'"); + } + }); + + it('bucket without versioning', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + // versioning: versioning, // hide it on purpose + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'versioning'"); + } + }); + + it('bucket with undefined versioning', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: undefined, // on purpose + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'versioning'"); + } + }); + + it('bucket without creation_date', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + // creation_date: creation_date, // hide it on purpose + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'creation_date'"); + } + }); + + it('bucket with undefined creation_date', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: undefined, // on purpose + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'creation_date'"); + } + }); + + it('bucket without path', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + // path: path, // hide it on purpose + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'path'"); + } + }); + + it('bucket with undefined path', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: undefined, // on purpose + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'path'"); + } + }); + + it('bucket without should_create_underlying_storage', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + // should_create_underlying_storage: false, // hide it on purpose + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'should_create_underlying_storage'"); + } + }); + + it('bucket with undefined should_create_underlying_storage', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: undefined, // on purpose + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', "must have required property 'should_create_underlying_storage'"); + } + }); + + }); + + describe('bucket with wrong types', () => { + + it('bucket with creation_date as Date (instead of string)', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: Date.now(), // Date instead of string + path: path, + should_create_underlying_storage: true, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must be string'); + } + }); + + it('bucket with s3_policy as string (instead of object)', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_disabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: true, + s3_policy: '', // string instead of object + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must be object'); + } + }); + + it('bucket with should_create_underlying_storage as string (instead of boolean)', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_enabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: 'yes', // string instead of boolean + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must be boolean'); + } + }); + + it('bucket with versioning as string (instead of enum)', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: 'lala', // not part of definition of versioning + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must be equal to one of the allowed values'); + } + }); + + it('bucket with fs_backend as string (instead of enum)', () => { + const bucket_data = { + name: bucket_name, + system_owner: system_owner, + bucket_owner: bucket_owner, + versioning: versioning_enabled, + creation_date: creation_date, + path: path, + should_create_underlying_storage: false, + fs_backend: '' // not part of definition of fs_backend + }; + try { + nsfs_schema_utils.validate_bucket_schema(bucket_data); + throw_error_in_case_error_was_not_thrown(); + } catch (err) { + expect(err).toBeInstanceOf(RpcError); + expect(err).toHaveProperty('message', 'must be equal to one of the allowed values'); + } + }); + + }); + +}); diff --git a/src/test/unit_tests/test_bucketspace_fs.js b/src/test/unit_tests/test_bucketspace_fs.js index c538f8bc76..515b003593 100644 --- a/src/test/unit_tests/test_bucketspace_fs.js +++ b/src/test/unit_tests/test_bucketspace_fs.js @@ -48,10 +48,11 @@ const DEFAULT_FS_CONFIG = { warn_threshold_ms: 100, }; +// since the account in NS NSFS should be valid to the nsfs_account_schema +// had to remove additional properties: has_s3_access: 'true' and nsfs_only: 'true' const account_user1 = { name: 'user1', email: 'user1@noobaa.io', - has_s3_access: 'true', allow_bucket_creation: true, access_keys: [{ access_key: 'a-abcdefghijklmn123456', @@ -61,7 +62,6 @@ const account_user1 = { uid: 0, gid: 0, new_buckets_path: new_buckets_path_user1, - nsfs_only: 'true' }, creation_date: '2023-10-30T04:46:33.815Z', }; @@ -69,7 +69,6 @@ const account_user1 = { const account_user2 = { name: 'user2', email: 'user2@noobaa.io', - has_s3_access: 'true', allow_bucket_creation: true, access_keys: [{ access_key: 'a-abcdefghijklmn123457', @@ -78,7 +77,6 @@ const account_user2 = { nsfs_account_config: { distinguished_name: "root", new_buckets_path: new_buckets_path_user2, - nsfs_only: 'true' }, creation_date: '2023-10-30T04:46:33.815Z', }; @@ -86,7 +84,6 @@ const account_user2 = { const account_user3 = { name: 'user3', email: 'user3@noobaa.io', - has_s3_access: 'true', allow_bucket_creation: true, access_keys: [{ access_key: 'a-abcdefghijklmn123458', @@ -95,7 +92,6 @@ const account_user3 = { nsfs_account_config: { distinguished_name: os.userInfo().username, new_buckets_path: new_buckets_path_user2, - nsfs_only: 'true' }, creation_date: '2023-10-30T04:46:33.815Z', }; diff --git a/src/test/unit_tests/test_nc_nsfs_cli.js b/src/test/unit_tests/test_nc_nsfs_cli.js index d1046ae312..8690658300 100644 --- a/src/test/unit_tests/test_nc_nsfs_cli.js +++ b/src/test/unit_tests/test_nc_nsfs_cli.js @@ -201,6 +201,9 @@ mocha.describe('manage_nsfs cli', function() { const action = nc_nsfs_manage_actions.UPDATE; bucket_options = { ...bucket_options, bucket_policy: empty_bucket_policy }; await exec_manage_cli(type, action, bucket_options); + // in the CLI we use empty string to unset the s3_policy + // but as a parameter is it undefined property + bucket_options.bucket_policy = undefined; const bucket = await read_config_file(config_root, schema_dir, bucket_options.name); assert_bucket(bucket, bucket_options); await assert_config_file_permissions(config_root, schema_dir, bucket_options.name); @@ -264,6 +267,9 @@ mocha.describe('manage_nsfs cli', function() { const action = nc_nsfs_manage_actions.UPDATE; gpfs_bucket_options.fs_backend = ''; const bucket_status = await exec_manage_cli(type, action, gpfs_bucket_options); + // in the CLI we use empty string to unset the fs_backend + // but as a parameter is it undefined property + gpfs_bucket_options.fs_backend = undefined; assert_response(action, type, bucket_status, gpfs_bucket_options); const bucket = await read_config_file(config_root, schema_dir, gpfs_bucket_options.name); assert_bucket(bucket, gpfs_bucket_options); @@ -335,6 +341,7 @@ mocha.describe('manage_nsfs cli', function() { const accounts_schema_dir = 'accounts'; const access_keys_schema_dir = 'access_keys'; let updating_options = account_options; + let compare_details; // we will use it for update account and compare the results mocha.it('cli account create', async function() { const action = nc_nsfs_manage_actions.ADD; @@ -490,21 +497,41 @@ mocha.describe('manage_nsfs cli', function() { mocha.it('cli account update owner', async function() { const action = nc_nsfs_manage_actions.UPDATE; - gpfs_account_options.email = 'blalal'; - const account_status = await exec_manage_cli(type, action, gpfs_account_options); - assert_response(action, type, account_status, gpfs_account_options); + const account_options_for_update_owner = { + config_root: gpfs_account_options.config_root, // needed for exec_manage_cli function + name: gpfs_account_options.name, + fs_backend: gpfs_account_options.fs_backend, // added this not to mess up the comparison + email: 'blalal' //update the name + }; + const account_status = await exec_manage_cli(type, action, account_options_for_update_owner); + compare_details = { + ...gpfs_account_options, + ...account_options_for_update_owner, + }; + assert_response(action, type, account_status, compare_details); const account = await read_config_file(config_root, accounts_schema_dir, gpfs_account_options.name); - assert_account(account, gpfs_account_options); + assert_account(account, compare_details); await assert_config_file_permissions(config_root, accounts_schema_dir, gpfs_account_options.name); }); mocha.it('cli account update to non GPFS', async function() { const action = nc_nsfs_manage_actions.UPDATE; - gpfs_account_options.fs_backend = ''; - const account_status = await exec_manage_cli(type, action, gpfs_account_options); - assert_response(action, type, account_status, gpfs_account_options); + const account_options_for_update_fs_backend = { + config_root: gpfs_account_options.config_root, // needed for exec_manage_cli function + name: gpfs_account_options.name, + fs_backend: '', // remove the 'GPFS' + }; + const account_status = await exec_manage_cli(type, action, account_options_for_update_fs_backend); + compare_details = { + ...compare_details, + ...account_options_for_update_fs_backend, + }; + // in the CLI we use empty string to unset the fs_backend + // but as a parameter is it undefined property + compare_details.fs_backend = undefined; + assert_response(action, type, account_status, compare_details); const account = await read_config_file(config_root, accounts_schema_dir, gpfs_account_options.name); - assert_account(account, gpfs_account_options); + assert_account(account, compare_details); await assert_config_file_permissions(config_root, accounts_schema_dir, gpfs_account_options.name); }); @@ -804,7 +831,10 @@ async function exec_manage_cli(type, action, options) { const bucket_flags = (options.name ? `--name ${options.name}` : ``) + (options.owner_email ? ` --email ${options.owner_email}` : ``) + (options.fs_backend === undefined ? `` : ` --fs_backend '${options.fs_backend}'`) + - (options.bucket_policy === undefined ? `` : ` --bucket_policy '${JSON.stringify(options.bucket_policy)}'`) + + // eslint-disable-next-line no-nested-ternary + (options.bucket_policy === undefined ? `` : + options.bucket_policy === '' ? + ` --bucket_policy ''` : ` --bucket_policy '${JSON.stringify(options.bucket_policy)}'`) + (options.bucket_path ? ` --path ${options.bucket_path}` : ``); const account_flags = (options.name ? ` --name ${options.name}` : ``) +