Skip to content

Commit

Permalink
bucket notification - harden bad name scenario. Also, don't reply int…
Browse files Browse the repository at this point in the history
…ernal '_' field.

Signed-off-by: Amit Prinz Setter <[email protected]>
  • Loading branch information
alphaprinz committed Feb 14, 2025
1 parent 2cf48bc commit d9c632c
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 111 deletions.
69 changes: 39 additions & 30 deletions src/cmd/manage_nsfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -781,40 +781,49 @@ async function notification_management() {
async function connection_management(action, user_input) {
manage_nsfs_validations.validate_connection_args(user_input, action);

//don't reply the internal '_' field.
delete user_input._;

let response = {};
let data;

switch (action) {
case ACTIONS.ADD:
data = await notifications_util.add_connect_file(user_input, config_fs);
response = { code: ManageCLIResponse.ConnectionCreated, detail: data };
break;
case ACTIONS.DELETE:
await config_fs.delete_connection_config_file(user_input.name);
response = { code: ManageCLIResponse.ConnectionDeleted, detail: {name: user_input.name} };
break;
case ACTIONS.UPDATE:
await notifications_util.update_connect_file(user_input.name, user_input.key,
user_input.value, user_input.remove_key, config_fs);
response = { code: ManageCLIResponse.ConnectionUpdated, detail: {name: user_input.name} };
break;
case ACTIONS.STATUS:
data = await new notifications_util.Notificator({
fs_context: config_fs.fs_context,
connect_files_dir: config_fs.connections_dir_path,
nc_config_fs: config_fs,
}).parse_connect_file(user_input.name, user_input.decrypt);
response = { code: ManageCLIResponse.ConnectionStatus, detail: data };
break;
case ACTIONS.LIST:
data = await list_connections();
response = { code: ManageCLIResponse.ConnectionList, detail: data };
break;
default:
throw_cli_error(ManageCLIError.InvalidAction);
}
try {
switch (action) {
case ACTIONS.ADD:
data = await notifications_util.add_connect_file(user_input, config_fs);
response = { code: ManageCLIResponse.ConnectionCreated, detail: data };
break;
case ACTIONS.DELETE:
await config_fs.delete_connection_config_file(user_input.name);
response = { code: ManageCLIResponse.ConnectionDeleted, detail: {name: user_input.name} };
break;
case ACTIONS.UPDATE:
await notifications_util.update_connect_file(user_input.name, user_input.key,
user_input.value, user_input.remove_key, config_fs);
response = { code: ManageCLIResponse.ConnectionUpdated, detail: {name: user_input.name} };
break;
case ACTIONS.STATUS:
data = await new notifications_util.Notificator({
fs_context: config_fs.fs_context,
connect_files_dir: config_fs.connections_dir_path,
nc_config_fs: config_fs,
}).parse_connect_file(user_input.name, user_input.decrypt);
response = { code: ManageCLIResponse.ConnectionStatus, detail: data };
break;
case ACTIONS.LIST:
data = await list_connections();
response = { code: ManageCLIResponse.ConnectionList, detail: data };
break;
default:
throw_cli_error(ManageCLIError.InvalidAction);
}

write_stdout_response(response.code, response.detail, response.event_arg);
write_stdout_response(response.code, response.detail, response.event_arg);
} catch (err) {
if (err.code === 'EEXIST') throw_cli_error(ManageCLIError.ConnectionAlreadyExists, user_input.name);
if (err.code === 'ENOENT') throw_cli_error(ManageCLIError.NoSuchConnection, user_input.name);
throw err;
}
}

/**
Expand Down
12 changes: 12 additions & 0 deletions src/manage_nsfs/manage_nsfs_cli_errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,18 @@ ManageCLIError.MissingCliParam = Object.freeze({
http_code: 400,
});

ManageCLIError.ConnectionAlreadyExists = Object.freeze({
code: 'ConnectionAlreadyExists',
message: 'The requested connection name is not available. Please select a different name and try again.',
http_code: 409,
});

ManageCLIError.NoSuchConnection = Object.freeze({
code: 'NoSuchConnection',
message: 'Connection does not exist.',
http_code: 404,
});

///////////////////////////////
// ERRORS MAPPING //
///////////////////////////////
Expand Down
171 changes: 90 additions & 81 deletions src/test/unit_tests/jest_tests/test_nc_connection_cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,114 +7,120 @@
process.env.DISABLE_INIT_RANDOM_SEED = "true";

const fs = require('fs');
const _ = require('lodash');
const path = require('path');
const os_util = require('../../../util/os_utils');
const fs_utils = require('../../../util/fs_utils');
const { ConfigFS } = require('../../../sdk/config_fs');
const { TMP_PATH, set_nc_config_dir_in_config } = require('../../system_tests/test_utils');
const { TYPES, ACTIONS } = require('../../../manage_nsfs/manage_nsfs_constants');
const ManageCLIError = require('../../../manage_nsfs/manage_nsfs_cli_errors').ManageCLIError;
const ManageCLIResponse = require('../../../manage_nsfs/manage_nsfs_cli_responses').ManageCLIResponse;

const tmp_fs_path = path.join(TMP_PATH, 'test_nc_connection_cli.test');
const timeout = 5000;
const tmp_fs_path = path.join(TMP_PATH, 'test_nc_conn_cli');

// eslint-disable-next-line max-lines-per-function
describe('manage nsfs cli connection flow', () => {
describe('manage nsfs cli connection', () => {
describe('cli create connection', () => {
const config_root = path.join(tmp_fs_path, 'config_root_manage_nsfs');
const config_fs = new ConfigFS(config_root);
const root_path = path.join(tmp_fs_path, 'root_path_manage_nsfs/');
const options_file = path.join(TMP_PATH, "conn1.json");
const defaults = {
type: TYPES.CONNECTION,
name: 'conn1',
agent_request_object: {
host: "localhost",
"port": 9999,
"timeout": 100
},
request_options_object: {auth: "user:passw"},
notification_protocol: "http",

agent_request_object: {"host": "localhost", "port": 9999, "timeout": 100},
request_options_object: {"timeout": 200}
};
beforeEach(async () => {
await fs_utils.create_fresh_path(root_path);
set_nc_config_dir_in_config(config_root);

fs.writeFileSync(options_file, JSON.stringify(defaults));
const action = ACTIONS.ADD;
const conn_options = { config_root, from_file: options_file };
await exec_manage_cli(TYPES.CONNECTION, action, conn_options);
});

afterEach(async () => {
await fs_utils.folder_delete(`${config_root}`);
await fs_utils.folder_delete(`${root_path}`);
});

it('cli create connection from file', async () => {
const connection = await config_fs.get_connection_by_name(defaults.name);
assert_connection(connection, defaults, true);
}, timeout);

it('cli create connection from cli', async () => {
const conn_options = {...defaults, config_root};
conn_options.name = "fromcli";
await exec_manage_cli(TYPES.CONNECTION, ACTIONS.ADD, conn_options);
const connection = await config_fs.get_connection_by_name(conn_options.name);
assert_connection(connection, conn_options, true);
}, timeout);

it('cli delete connection ', async () => {
await exec_manage_cli(TYPES.CONNECTION, ACTIONS.DELETE, {config_root, name: defaults.name});
expect(fs.readdirSync(config_fs.connections_dir_path).filter(file => file.endsWith(".json")).length).toEqual(0);
}, timeout);

//update notification_protocol field in the connection file.
it('cli update connection ', async () => {
await exec_manage_cli(TYPES.CONNECTION, ACTIONS.UPDATE, {
it('cli create conn', async () => {
const action = ACTIONS.ADD;
const { type, name, agent_request_object, request_options_object, notification_protocol } = defaults;
const conn_options = { config_root, name, agent_request_object, request_options_object, notification_protocol };
const res = await exec_manage_cli(type, action, conn_options);
const res_json = JSON.parse(res.trim());
expect(_.isEqual(new Set(Object.keys(res_json.response)), new Set(['reply', 'code', 'message']))).toBe(true);
const actual = await config_fs.get_connection_by_name(name);
assert_conn(actual, defaults);
});


it('cli update conn', async () => {
let { type, name, agent_request_object, request_options_object, notification_protocol } = defaults;
let conn_options = { config_root, name, agent_request_object, request_options_object, notification_protocol };
await exec_manage_cli(type, ACTIONS.ADD, conn_options);
let actual = await config_fs.get_connection_by_name(name);
assert_conn(actual, defaults);

conn_options = {
config_root,
name: defaults.name,
name,
key: "notification_protocol",
value: "https"
});
const updated = await config_fs.get_connection_by_name(defaults.name);
const updated_content = {...defaults};
updated_content.notification_protocol = "https";
assert_connection(updated, updated_content, true);
}, timeout);

it('cli list connection ', async () => {
const res = JSON.parse(await exec_manage_cli(TYPES.CONNECTION, ACTIONS.LIST, {config_root}));
expect(res.response.reply[0]).toEqual(defaults.name);
}, timeout);

it('cli status connection ', async () => {
const res = JSON.parse(await exec_manage_cli(TYPES.CONNECTION, ACTIONS.STATUS, {
config_root,
name: defaults.name,
decrypt: true
}));
assert_connection(res.response.reply, defaults, false);
}, timeout);
value: "kafka"};
await exec_manage_cli(type, ACTIONS.UPDATE, conn_options);
actual = await config_fs.get_connection_by_name(name);
const updated = _.cloneDeep(defaults);
updated.notification_protocol = 'kafka';
assert_conn(actual, updated);
});


it('cli delete conn', async () => {
const { type, name, agent_request_object, request_options_object, notification_protocol } = defaults;
let conn_options = { config_root, name, agent_request_object, request_options_object, notification_protocol };
await exec_manage_cli(type, ACTIONS.ADD, conn_options);
const actual = await config_fs.get_connection_by_name(name);
assert_conn(actual, defaults);

conn_options = { config_root, name };
const res = await exec_manage_cli(type, ACTIONS.DELETE, conn_options);
const res_json = JSON.parse(res.trim());
expect(res_json.response.code).toBe(ManageCLIResponse.ConnectionDeleted.code);
const message = `Notification connection has been deleted successfully: ${name}`;
expect(res_json.response.message).toBe(message);
const path = config_fs.get_connection_path_by_name(name);
expect(fs.existsSync(path)).toBe(false);
});

it('conn already exists', async () => {
const action = ACTIONS.ADD;
const { type, name, agent_request_object, request_options_object, notification_protocol } = defaults;
const conn_options = { config_root, name, agent_request_object, request_options_object, notification_protocol };
await exec_manage_cli(type, action, conn_options);
const actual = await config_fs.get_connection_by_name(name);
assert_conn(actual, defaults);

const res = await exec_manage_cli(type, action, conn_options, true);
const res_json = JSON.parse(res.trim());
expect(res_json.error.code).toBe(ManageCLIError.ConnectionAlreadyExists.code);
});

it('conn does not exist', async () => {
const action = ACTIONS.DELETE;
const { type, name } = defaults;
const conn_options = { config_root, name };
const res = await exec_manage_cli(type, action, conn_options, true);
const res_json = JSON.parse(res.trim());
expect(res_json.error.code).toBe(ManageCLIError.NoSuchConnection.code);
});

});
});


/**
* assert_connection will verify the fields of the accounts
* @param {object} connection actual
* @param {object} connection_options expected
* @param {boolean} is_encrypted whether connection's auth field is encrypted
*/
function assert_connection(connection, connection_options, is_encrypted) {
expect(connection.name).toEqual(connection_options.name);
expect(connection.notification_protocol).toEqual(connection_options.notification_protocol);
expect(connection.agent_request_object).toStrictEqual(connection_options.agent_request_object);
if (is_encrypted) {
expect(connection.request_options_object).not.toStrictEqual(connection_options.request_options_object);
} else {
expect(connection.request_options_object).toStrictEqual(connection_options.request_options_object);
}
function assert_conn(actual, expected) {
expect(actual.name).toStrictEqual(expected.name);
expect(actual.agent_request_object).toStrictEqual(expected.agent_request_object);
expect(actual.request_options_object).toStrictEqual(expected.request_options_object);
expect(actual.notification_protocol).toStrictEqual(expected.notification_protocol);
}

/**
Expand All @@ -123,18 +129,22 @@ function assert_connection(connection, connection_options, is_encrypted) {
* @param {string} action
* @param {object} options
*/
async function exec_manage_cli(type, action, options) {
async function exec_manage_cli(type, action, options, expect_failure = false) {
const command = create_command(type, action, options);
let res;
try {
res = await os_util.exec(command, { return_stdout: true });
} catch (e) {
res = e;
if(expect_failure) {
res = e.stdout;
} else {
res = e;
}
}
return res;
}

/**
/**
* create_command would create the string needed to run the CLI command
* @param {string} type
* @param {string} action
Expand All @@ -147,15 +157,14 @@ function create_command(type, action, options) {
if (typeof options[key] === 'boolean') {
account_flags += `--${key} `;
} else if (typeof options[key] === 'object') {
const val = JSON.stringify(options[key]);
account_flags += `--${key} '${val}' `;
account_flags += `--${key} '${JSON.stringify(options[key])}' `
} else {
account_flags += `--${key} ${options[key]} `;
}
}
}
account_flags = account_flags.trim();

const command = `node src/cmd/manage_nsfs ${type} ${action} ${account_flags}`;
const command = `node src/cmd/manage_nsfs ${type} ${action} ${account_flags} 2>/dev/null`;
return command;
}

0 comments on commit d9c632c

Please sign in to comment.