diff --git a/specs/swagger.yml b/specs/swagger.yml index 81591995f..6e75fb688 100644 --- a/specs/swagger.yml +++ b/specs/swagger.yml @@ -1867,7 +1867,38 @@ paths: '401': description: Not Authorized '404': - description: Invalid Node Id + description: Invalid Registry Id + '500': + description: Internal Server Error + patch: + tags: + - Registries + description: Updates a registry + operationId: updateRegistry + parameters: + - in: path + name: id + description: Registry id + required: true + type: string + - in: header + name: Authorization + description: User token + required: true + type: string + responses: + '204': + description: Updated + headers: + X-Timestamp: + type: number + description: FogController server timestamp + '400': + description: Bad Request + '401': + description: Not Authorized + '404': + description: Invalid Registry Id '500': description: Internal Server Error '/user/login': diff --git a/src/cli/registry.js b/src/cli/registry.js index f36a82ef5..31be587d4 100644 --- a/src/cli/registry.js +++ b/src/cli/registry.js @@ -16,43 +16,50 @@ const constants = require('../helpers/constants'); const logger = require('../logger'); const CliDecorator = require('../decorators/cli-decorator'); const RegistryService = require('../services/registry-service'); +const AppHelper = require('../helpers/app-helper'); class Registry extends BaseCLIHandler { constructor() { super(); - this.name = constants.CMD_REGISTRY + this.name = constants.CMD_REGISTRY; this.commandDefinitions = [ - { name: 'command', defaultOption: true, group: [constants.CMD] }, - { name: 'uri', alias: 'u', type: String, description: 'Registry URI', group: [constants.CMD_ADD] }, - { name: 'public', alias: 'b', type: Boolean, description: 'Set registry as public', group: [constants.CMD_ADD] }, - { name: 'private', alias: 'r', type: Boolean, description: 'Set registry as private', group: [constants.CMD_ADD] }, - { name: 'username', alias: 'l', type: String, description: 'Registry\'s user name', group: [constants.CMD_ADD] }, - { name: 'password', alias: 'p', type: String, description: 'Password', group: [constants.CMD_ADD] }, - { name: 'email', alias: 'e', type: String, description: 'Email address', group: [constants.CMD_ADD] }, - { name: 'user-id', alias: 'i', type: Number, description: 'User\'s id', group: [constants.CMD_ADD] }, - { name: 'item-id', alias: 'd', type: Number, description: 'Item\'s id', group: [constants.CMD_REMOVE] }, + {name: 'command', defaultOption: true, group: [constants.CMD]}, + {name: 'uri', alias: 'u', type: String, description: 'Registry URI', group: [constants.CMD_ADD, constants.CMD_UPDATE]}, + {name: 'public', alias: 'b', type: Boolean, description: 'Set registry as public', group: [constants.CMD_ADD, constants.CMD_UPDATE]}, + {name: 'private', alias: 'r', type: Boolean, description: 'Set registry as private', group: [constants.CMD_ADD, constants.CMD_UPDATE]}, + {name: 'username', alias: 'l', type: String, description: 'Registry\'s user name', group: [constants.CMD_ADD, constants.CMD_UPDATE]}, + {name: 'password', alias: 'p', type: String, description: 'Password', group: [constants.CMD_ADD, constants.CMD_UPDATE]}, + {name: 'requires-certificate', alias: 'c', type: Boolean, description: 'Requires certificate', group: [constants.CMD_ADD, constants.CMD_UPDATE]}, + {name: 'certificate', alias: 'C', type: String, description: 'Certificate', group: [constants.CMD_ADD, constants.CMD_UPDATE]}, + {name: 'email', alias: 'e', type: String, description: 'Email address', group: [constants.CMD_ADD, constants.CMD_UPDATE]}, + {name: 'user-id', alias: 'i', type: Number, description: 'User\'s id', group: [constants.CMD_ADD]}, + {name: 'item-id', alias: 'd', type: Number, description: 'Item\'s id', group: [constants.CMD_REMOVE, constants.CMD_UPDATE]} ]; this.commands = { [constants.CMD_ADD]: 'Add a new Registry.', [constants.CMD_REMOVE]: 'Delete a Registry.', + [constants.CMD_UPDATE]: 'Update a Registry', [constants.CMD_LIST]: 'List all Registries.', } } async run(args) { - const registryCommand = this.parseCommandLineArgs(this.commandDefinitions, { argv: args.argv }) + const registryCommand = this.parseCommandLineArgs(this.commandDefinitions, {argv: args.argv}) switch (registryCommand.command.command) { case constants.CMD_ADD: - await _executeCase(registryCommand, constants.CMD_ADD, _createRegistry, true); - break; + await _executeCase(registryCommand, constants.CMD_ADD, _createRegistry, true); + break; case constants.CMD_REMOVE: - await _executeCase(registryCommand, constants.CMD_REMOVE, _deleteRegistry, false); - break; + await _executeCase(registryCommand, constants.CMD_REMOVE, _deleteRegistry, false); + break; + case constants.CMD_UPDATE: + await _executeCase(registryCommand, constants.CMD_UPDATE, _updateRegistry, false); + break; case constants.CMD_LIST: - await _executeCase(registryCommand, constants.CMD_LIST, _getRegistries, false); - break; + await _executeCase(registryCommand, constants.CMD_LIST, _getRegistries, false); + break; case constants.CMD_HELP: default: return this.help([constants.CMD_LIST]) @@ -62,48 +69,56 @@ class Registry extends BaseCLIHandler { } async function _createRegistry(obj, user) { - const registry = _createRegistryObject(obj); - logger.info(JSON.stringify(registry)); - const response = await RegistryService.createRegistry(registry, user); - logger.info('Registry has been created successfully.'); - logger.info('Registry id: ' + response.id); + const registry = _createRegistryObject(obj); + logger.info(JSON.stringify(registry)); + const response = await RegistryService.createRegistry(registry, user); + logger.info('Registry has been created successfully.'); + logger.info('Registry id: ' + response.id); } async function _getRegistries(obj, user) { - const result = await RegistryService.findRegistries(user, true); - logger.info(JSON.stringify(result)); - logger.info('List of Registries has been received successfully.'); + const result = await RegistryService.findRegistries(user, true); + logger.info(JSON.stringify(result)); + logger.info('List of Registries has been received successfully.'); } async function _deleteRegistry(obj, user) { - await RegistryService.deleteRegistry({id: obj.itemId}, user, true); - logger.info('Registry has been removed successfully.'); + await RegistryService.deleteRegistry({id: obj.itemId}, user, true); + logger.info('Registry has been removed successfully.'); +} + +async function _updateRegistry (obj) { + const registry = _createRegistryObject(obj); + logger.info(JSON.stringify(registry)); + await RegistryService.updateRegistry(registry, obj.itemId, {}, true); + logger.info('Registry has been updated successfully.'); } async function _executeCase(commands, commandName, f, isUserRequired) { - try { - const obj = commands[commandName]; + try { + const obj = commands[commandName]; - if (isUserRequired) { - const decoratedFunction = CliDecorator.prepareUserById(f); - await decoratedFunction(obj); - } else { - await f(obj); - } - } catch (error) { - logger.error(error.message); + if (isUserRequired) { + const decoratedFunction = CliDecorator.prepareUserById(f); + await decoratedFunction(obj); + } else { + await f(obj); } + } catch (error) { + logger.error(error.message); + } } function _createRegistryObject(cliData) { - const registryObj = { - url: cliData.uri, - username: cliData.username, - password: cliData.password, - isPublic: cliData.public, - email: cliData.email - }; - return registryObj; + return { + url: cliData.uri, + username: cliData.username, + password: cliData.password, + isPublic: AppHelper.validateBooleanCliOptions(cliData.public, cliData.private), + email: cliData.email, + requiresCert: cliData.requiresCertificate, + certificate: cliData.certificate + }; } module.exports = new Registry(); \ No newline at end of file diff --git a/src/controllers/registry-controller.js b/src/controllers/registry-controller.js index ba94ed21b..3ff6c2971 100644 --- a/src/controllers/registry-controller.js +++ b/src/controllers/registry-controller.js @@ -27,13 +27,24 @@ const getRegistriesEndPoint = async function (req, user) { const deleteRegistryEndPoint = async function (req, user) { const deleteRegistry = { - id: parseInt(req.params.id) - } + id: parseInt(req.params.id) + }; return await RegistryService.deleteRegistry(deleteRegistry, user, false); }; +const updateRegistryEndPoint = async function (req, user) { + const registry = req.body; + const registryId = req.params.id; + + logger.info("Parameters:" + JSON.stringify(registry)); + logger.info("Registry id:" + JSON.stringify(registryId)); + + return await RegistryService.updateRegistry(registry, registryId, user, false) +}; + module.exports = { - createRegistryEndPoint: AuthDecorator.checkAuthToken(createRegistryEndPoint), - getRegistriesEndPoint: AuthDecorator.checkAuthToken(getRegistriesEndPoint), - deleteRegistryEndPoint: AuthDecorator.checkAuthToken(deleteRegistryEndPoint) + createRegistryEndPoint: AuthDecorator.checkAuthToken(createRegistryEndPoint), + getRegistriesEndPoint: AuthDecorator.checkAuthToken(getRegistriesEndPoint), + deleteRegistryEndPoint: AuthDecorator.checkAuthToken(deleteRegistryEndPoint), + updateRegistryEndPoint: AuthDecorator.checkAuthToken(updateRegistryEndPoint) }; \ No newline at end of file diff --git a/src/helpers/error-messages.js b/src/helpers/error-messages.js index 1e82c7de2..f72a4ca18 100644 --- a/src/helpers/error-messages.js +++ b/src/helpers/error-messages.js @@ -58,6 +58,7 @@ module.exports = { STRACE_NOT_FOUND: 'Strace not found', INVALID_CONTENT_TYPE: 'Invalid content type', UPLOADED_FILE_NOT_FOUND: 'Uploaded image snapshot file not found', + REGISTRY_NOT_FOUND: 'Registry not found', CLI: { INVALID_PORT_MAPPING: 'Port mapping parsing error. Please provide valid port mapping.', INVALID_VOLUME_MAPPING: 'Volume mapping parsing error. Please provide valid volume mapping.', diff --git a/src/main.js b/src/main.js index b9a9e0cfd..95cd37789 100644 --- a/src/main.js +++ b/src/main.js @@ -34,7 +34,16 @@ function main() { daemon .on('starting', () => { - logger.silly('Starting iofog-controller...') + logger.silly('Starting iofog-controller...'); + + db.sequelize + .sync() + .then(db.migrate) + .then(db.seed) + .catch((err) => { + logger.silly('Unable to initialize the database.', err); + process.exit(1) + }); }) .on('stopping', () => { logger.silly('Stopping iofog-controller...') @@ -55,12 +64,4 @@ function main() { cli.run(daemon) } -db.sequelize - .sync() - .then(db.migrate) - .then(db.seed) - .then(main) - .catch((err) => { - logger.silly('Unable to initialize the database.', err) - process.exit(1) - }); \ No newline at end of file +main(); \ No newline at end of file diff --git a/src/routes/registries.js b/src/routes/registries.js index 55e749dc9..51973ffe1 100644 --- a/src/routes/registries.js +++ b/src/routes/registries.js @@ -86,4 +86,30 @@ module.exports = [ .send(responseObject.body) } }, -] + { + method: 'patch', + path: '/api/v3/registries/:id', + middleware: async (req, res) => { + const successCode = constants.HTTP_CODE_NO_CONTENT; + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError] + }, + { + code: constants.HTTP_CODE_NOT_FOUND, + errors: [Errors.NotFoundError] + } + ]; + const updateRegistryEndPoint = ResponseDecorator.handleErrors(RegistryController.updateRegistryEndPoint, successCode, errorCodes); + const responseObject = await updateRegistryEndPoint(req); + res + .status(responseObject.code) + .send(responseObject.body) + } + } +]; diff --git a/src/schemas/registry.js b/src/schemas/registry.js index a69a2e982..de986a302 100644 --- a/src/schemas/registry.js +++ b/src/schemas/registry.js @@ -37,6 +37,21 @@ const registryDelete = { "additionalProperties": false }; +const registryUpdate = { + "id": "/registryUpdate", + "type": "object", + "properties": { + "url": {"type": "string", "minLength": 1}, + "isPublic": {"type": "boolean"}, + "username": {"type": "string", "minLength": 1}, + "password": {"type": "string"}, + "email": {"type": "string"}, + "requiresCert": {"type": "boolean"}, + "certificate": {"type": "string"} + }, + "additionalProperties": false +}; + module.exports = { - mainSchemas: [registryCreate, registryDelete] + mainSchemas: [registryCreate, registryDelete, registryUpdate] }; \ No newline at end of file diff --git a/src/services/registry-service.js b/src/services/registry-service.js index 074803f07..8d70ebd46 100644 --- a/src/services/registry-service.js +++ b/src/services/registry-service.js @@ -97,8 +97,50 @@ const deleteRegistry = async function (registryData, user, isCli, transaction) { await updateChangeTracking(user, transaction); }; +const updateRegistry = async function (registry, registryId, user, isCLI, transaction) { + await Validator.validate(registry, Validator.schemas.registryUpdate); + + if (registry.requiresCert && registry.certificate === undefined) { + throw new Errors.ValidationError(ErrorMessages.CERT_PROPERTY_REQUIRED); + } + + const existingRegistry = await RegistryManager.findOne({ + id: registryId + }, transaction); + if (!existingRegistry) { + throw new Errors.NotFoundError(ErrorMessages.REGISTRY_NOT_FOUND) + } + + let registryUpdate = { + url: registry.url, + username: registry.username, + password: registry.password, + isPublic: registry.isPublic, + userEmail: registry.email, + requiresCert: registry.requiresCert, + certificate: registry.certificate + }; + + registryUpdate = AppHelper.deleteUndefinedFields(registryUpdate); + + const where = isCLI ? + { + id: registryId + } + : + { + id: registryId, + userId: user.id + }; + + await RegistryManager.update(where, registryUpdate, transaction); + + await updateChangeTracking(user, transaction); +}; + module.exports = { findRegistries: TransactionDecorator.generateTransaction(findRegistries), createRegistry: TransactionDecorator.generateTransaction(createRegistry), - deleteRegistry: TransactionDecorator.generateTransaction(deleteRegistry) + deleteRegistry: TransactionDecorator.generateTransaction(deleteRegistry), + updateRegistry: TransactionDecorator.generateTransaction(updateRegistry) }; \ No newline at end of file diff --git a/tests/Controller Testing.postman_collection.json b/tests/Controller Testing.postman_collection.json index a86724e23..da25bb98c 100644 --- a/tests/Controller Testing.postman_collection.json +++ b/tests/Controller Testing.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "3f737064-1335-43ef-9b3c-f3f48558d42b", + "_postman_id": "0c412a3a-7328-4c9e-8919-d2836adccfa1", "name": "Controller Testing", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -856,6 +856,7 @@ "method": "POST", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" } @@ -905,6 +906,7 @@ "method": "GET", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -916,7 +918,7 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" }, "url": { "raw": "{{host}}/api/v3/agent/config", @@ -952,6 +954,7 @@ "method": "PATCH", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -1005,6 +1008,7 @@ "method": "GET", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -1016,7 +1020,7 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" }, "url": { "raw": "{{host}}/api/v3/agent/config/changes", @@ -1053,6 +1057,7 @@ "method": "PUT", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -1103,6 +1108,7 @@ "method": "GET", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -1114,7 +1120,7 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" }, "url": { "raw": "{{host}}/api/v3/agent/microservices", @@ -1153,6 +1159,7 @@ "method": "GET", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -1164,7 +1171,7 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" }, "url": { "raw": "{{host}}/api/v3/agent/microservices/abcedf", @@ -1204,6 +1211,7 @@ "method": "GET", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -1215,7 +1223,7 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" }, "url": { "raw": "{{host}}/api/v3/agent/registries", @@ -1254,6 +1262,7 @@ "method": "GET", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -1265,7 +1274,7 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" }, "url": { "raw": "{{host}}/api/v3/agent/tunnel", @@ -1301,6 +1310,7 @@ "method": "GET", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -1312,7 +1322,7 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" }, "url": { "raw": "{{host}}/api/v3/agent/strace", @@ -1351,6 +1361,7 @@ "method": "PUT", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -1401,6 +1412,7 @@ "method": "GET", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -1412,7 +1424,7 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" }, "url": { "raw": "{{host}}/api/v3/agent/version", @@ -1547,6 +1559,7 @@ "method": "GET", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -1558,7 +1571,7 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" }, "url": { "raw": "{{host}}/api/v3/agent/image-snapshot", @@ -1644,6 +1657,7 @@ "method": "DELETE", "header": [ { + "disabled": false, "key": "Content-Type", "value": "application/json" }, @@ -5416,7 +5430,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"url\": \"string\",\n \"isPublic\": true,\n \"username\": \"string\",\n \"password\": \"string\",\n \"email\": \"string\"\n}" + "raw": "{\n \"url\": \"string\",\n \"isPublic\": true,\n \"username\": \"string\",\n \"password\": \"string\",\n \"email\": \"string\",\n \"requiresCert\": false,\n \"certificate\": \"string\"\n}" }, "url": { "raw": "{{host}}/api/v3/registries", @@ -5432,6 +5446,52 @@ }, "response": [] }, + { + "name": "Update Registry", + "event": [ + { + "listen": "test", + "script": { + "id": "4f7a9f52-12cc-49d0-9e2f-147b6f5cb6fa", + "exec": [ + "tests[\"Status code is 204\"] = responseCode.code === 204;" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "type": "text", + "value": "{{user-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"url\": \"string2\",\n \"isPublic\": true,\n \"username\": \"string3\",\n \"password\": \"string4\",\n \"email\": \"string5\",\n \"requiresCert\": true,\n \"certificate\": \"string6\"\n}" + }, + "url": { + "raw": "{{host}}/api/v3/registries/{{reg-id}}", + "host": [ + "{{host}}" + ], + "path": [ + "api", + "v3", + "registries", + "{{reg-id}}" + ] + } + }, + "response": [] + }, { "name": "Get Registries", "event": [ @@ -5599,7 +5659,7 @@ ], "variable": [ { - "id": "291501bd-8b27-45a8-9a93-e39a9f4146fa", + "id": "2fbdbf4f-879c-40bb-858a-82cb33c8a6e9", "key": "host", "value": "localhost:51121", "type": "string"