diff --git a/package.json b/package.json index 190c9255..572b498a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/aio-cli-plugin-api-mesh", - "version": "5.2.4-alpha.0", + "version": "5.2.4-alpha.1", "description": "Adobe I/O CLI plugin to develop and manage API mesh sources", "keywords": [ "oclif-plugin" diff --git a/src/commands/api-mesh/__tests__/delete-log-forwarding.test.js b/src/commands/api-mesh/__tests__/delete-log-forwarding.test.js new file mode 100644 index 00000000..fd4c05d9 --- /dev/null +++ b/src/commands/api-mesh/__tests__/delete-log-forwarding.test.js @@ -0,0 +1,106 @@ +/* +Copyright 2021 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const DeleteLogForwardingCommand = require('../config/delete/log-forwarding'); +const { initSdk, promptConfirm } = require('../../../helpers'); +const { getMeshId, deleteLogForwarding } = require('../../../lib/smsClient'); + +jest.mock('../../../helpers', () => ({ + initSdk: jest.fn().mockResolvedValue({}), + initRequestId: jest.fn().mockResolvedValue({}), + promptConfirm: jest.fn().mockResolvedValue(true), +})); +jest.mock('../../../lib/smsClient'); + +let logSpy, errorLogSpy, parseSpy; + +describe('delete log forwarding command tests', () => { + beforeEach(() => { + initSdk.mockResolvedValue({ + imsOrgCode: 'mockOrgCode', + projectId: 'mockProjectId', + workspaceId: 'mockWorkspaceId', + workspaceName: 'mockWorkspaceName', + }); + + getMeshId.mockResolvedValue('mockMeshId'); + deleteLogForwarding.mockResolvedValue(); + + global.requestId = 'dummy_request_id'; + + logSpy = jest.spyOn(DeleteLogForwardingCommand.prototype, 'log'); + errorLogSpy = jest.spyOn(DeleteLogForwardingCommand.prototype, 'error'); + parseSpy = jest.spyOn(DeleteLogForwardingCommand.prototype, 'parse'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should fail if mesh ID is not found', async () => { + getMeshId.mockResolvedValueOnce(null); + + await expect(DeleteLogForwardingCommand.run()).rejects.toThrow( + 'Unable to delete log forwarding details. No mesh found for Org(mockOrgCode) -> Project(mockProjectId) -> Workspace(mockWorkspaceId). Check the details and try again.', + ); + + expect(logSpy).not.toHaveBeenCalled(); + expect(errorLogSpy).toHaveBeenCalledWith( + 'Unable to delete log forwarding details. No mesh found for Org(mockOrgCode) -> Project(mockProjectId) -> Workspace(mockWorkspaceId). Check the details and try again.', + ); + }); + + test('should skip confirmation if autoConfirmAction is set', async () => { + parseSpy.mockResolvedValueOnce({ + flags: { + ignoreCache: false, + autoConfirmAction: true, + }, + }); + + await DeleteLogForwardingCommand.run(); + + expect(promptConfirm).not.toHaveBeenCalled(); + expect(deleteLogForwarding).toHaveBeenCalledWith( + 'mockOrgCode', + 'mockProjectId', + 'mockWorkspaceId', + 'mockMeshId', + ); + expect(logSpy).toHaveBeenCalledWith('Successfully deleted log forwarding details'); + }); + + test('should fail if deleteLogForwarding throws an error', async () => { + deleteLogForwarding.mockRejectedValueOnce(new Error('Deletion failed')); + + await expect(DeleteLogForwardingCommand.run()).rejects.toThrow( + 'Unable to delete log forwarding details. Try again. RequestId: dummy_request_id', + ); + + expect(logSpy).not.toHaveBeenCalledWith('Successfully deleted log forwarding details'); + expect(errorLogSpy).toHaveBeenCalledWith( + 'Unable to delete log forwarding details. Try again. RequestId: dummy_request_id', + ); + }); + + test('should delete log forwarding details successfully', async () => { + await DeleteLogForwardingCommand.run(); + + expect(deleteLogForwarding).toHaveBeenCalledWith( + 'mockOrgCode', + 'mockProjectId', + 'mockWorkspaceId', + 'mockMeshId', + ); + expect(logSpy).toHaveBeenCalledWith('Successfully deleted log forwarding details'); + }); +}); diff --git a/src/commands/api-mesh/config/delete/log-forwarding.js b/src/commands/api-mesh/config/delete/log-forwarding.js new file mode 100644 index 00000000..2bfbe43a --- /dev/null +++ b/src/commands/api-mesh/config/delete/log-forwarding.js @@ -0,0 +1,80 @@ +/* +Copyright 2021 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { Command } = require('@oclif/core'); +const logger = require('../../../../classes/logger'); +const { initSdk, promptConfirm } = require('../../../../helpers'); +const { ignoreCacheFlag, autoConfirmActionFlag } = require('../../../../utils'); +const { deleteLogForwarding, getMeshId } = require('../../../../lib/smsClient'); + +class DeleteLogForwardingCommand extends Command { + static flags = { + ignoreCache: ignoreCacheFlag, + autoConfirmAction: autoConfirmActionFlag, + }; + + async run() { + logger.info(`RequestId: ${global.requestId}`); + + const { flags } = await this.parse(DeleteLogForwardingCommand); + + const ignoreCache = await flags.ignoreCache; + const autoConfirmAction = await flags.autoConfirmAction; + + const { imsOrgCode, projectId, workspaceId, workspaceName } = await initSdk({ + ignoreCache, + }); + + let meshId = null; + + try { + meshId = await getMeshId(imsOrgCode, projectId, workspaceId, workspaceName); + } catch (err) { + this.error( + `Unable to get mesh ID. Please check the details and try again. RequestId: ${global.requestId}`, + ); + } + + // mesh could not be found + if (!meshId) { + this.error( + `Unable to delete log forwarding details. No mesh found for Org(${imsOrgCode}) -> Project(${projectId}) -> Workspace(${workspaceId}). Check the details and try again.`, + ); + } + + let shouldContinue = true; + + if (!autoConfirmAction) { + shouldContinue = await promptConfirm( + `Are you sure you want to delete the log forwarding details for mesh: ${meshId}?`, + ); + } + + if (shouldContinue) { + try { + await deleteLogForwarding(imsOrgCode, projectId, workspaceId, meshId); + this.log('Successfully deleted log forwarding details'); + } catch (error) { + this.log(error.message); + this.error( + `Unable to delete log forwarding details. Try again. RequestId: ${global.requestId}`, + ); + } + } else { + this.log('delete log-forwarding cancelled'); + return 'delete log-forwarding cancelled'; + } + } +} + +DeleteLogForwardingCommand.description = 'Delete log forwarding details for a given mesh'; + +module.exports = DeleteLogForwardingCommand; diff --git a/src/lib/smsClient.js b/src/lib/smsClient.js index 83429bd0..5ea90dbc 100644 --- a/src/lib/smsClient.js +++ b/src/lib/smsClient.js @@ -1397,6 +1397,106 @@ const getLogForwarding = async (organizationCode, projectId, workspaceId, meshId } }; +/** + * Deletes the log forwarding configuration for a given mesh. + * + * @param {string} organizationCode - The IMS org code + * @param {string} projectId - The project ID + * @param {string} workspaceId - The workspace ID + * @param {string} meshId - The mesh ID + */ +const deleteLogForwarding = async (organizationCode, projectId, workspaceId, meshId) => { + const { accessToken } = await getDevConsoleConfig(); + const config = { + method: 'DELETE', + url: `${SMS_BASE_URL}/organizations/${organizationCode}/projects/${projectId}/workspaces/${workspaceId}/meshes/${meshId}/log/forwarding`, + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'x-request-id': global.requestId, + 'x-api-key': SMS_API_KEY, + }, + }; + + logger.info( + 'Initiating DELETE %s', + `${SMS_BASE_URL}/organizations/${organizationCode}/projects/${projectId}/workspaces/${workspaceId}/meshes/${meshId}/log/forwarding`, + ); + + try { + const response = await axios(config); + + logger.info('Response from DELETE %s', response.status); + + if (response && response?.status === 204) { + return response; + } else { + logger.error( + `Unable to delete log forwarding config: ${objToString( + response, + ['data'], + 'Error', + )}. Received ${response.status}, expected 204`, + ); + throw new Error( + `something went wrong: ${objToString( + response, + ['data'], + 'Unable to delete log forwarding', + )}`, + ); + } + } catch (error) { + logger.info('Response from DELETE %s', error.response.status); + + if (error.response.status === 404) { + // The request was made and the server responded with a 404 status code + logger.error('log forwarding details not found'); + + throw new Error('log forwarding details not found'); + } else if (error.response && error.response.data) { + // The request was made and the server responded with an unsupported status code + logger.error( + 'Error while deleting log forwarding details. Response: %s', + objToString(error, ['response', 'data'], 'Unable to delete log forwarding details'), + ); + + if (error.response.data.messages) { + const message = objToString( + error, + ['response', 'data', 'messages', '0', 'message'], + 'Unable to delete log forwarding details', + ); + + throw new Error(message); + } else if (error.response.data.message) { + const message = objToString( + error, + ['response', 'data', 'message'], + 'Unable to delete log forwarding details', + ); + + throw new Error(message); + } else { + const message = objToString( + error, + ['response', 'data'], + 'Unable to delete log forwarding details', + ); + + throw new Error(message); + } + } else { + // The request was made but no response was received + logger.error( + 'Error while deleting log forwarding details. No response received from the server: %s', + objToString(error, [], 'Unable to delete log forwarding details'), + ); + + throw new Error('Unable to delete log forwarding details: %s', error.message); + } + } +}; + module.exports = { getApiKeyCredential, describeMesh, @@ -1420,4 +1520,5 @@ module.exports = { cachePurge, setLogForwarding, getLogForwarding, + deleteLogForwarding, };