diff --git a/packages/composer-cli/lib/cmds/report.js b/packages/composer-cli/lib/cmds/report.js new file mode 100644 index 0000000000..56e409b256 --- /dev/null +++ b/packages/composer-cli/lib/cmds/report.js @@ -0,0 +1,23 @@ +/* + * Licensed 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 CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const Report = require('./report/lib/report.js'); + +exports.command = 'report'; +exports.desc = 'Command for creating a report of the current Composer environment'; +exports.handler = function (argv) { + return argv.thePromise = Report.handler(argv); +}; \ No newline at end of file diff --git a/packages/composer-cli/lib/cmds/report/lib/report.js b/packages/composer-cli/lib/cmds/report/lib/report.js new file mode 100644 index 0000000000..44f05fc38d --- /dev/null +++ b/packages/composer-cli/lib/cmds/report/lib/report.js @@ -0,0 +1,93 @@ +/* + * Licensed 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 CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; +const fs = require('fs'); +const os = require('os'); +const { sep } = require('path'); +const cmdUtil = require('../../utils/cmdutils'); +const chalk = require('chalk'); +const moment = require('moment'); +const nodereport = require('node-report'); +const tar = require('tar'); + +/** + * Composer "capture" command + * @private + */ +class Report { + /** + * Command implementation. + * @param {Object} args argument list from composer command + * @return {Promise} promise when command complete + */ + static handler(args) { + return this.report(args.file); + } + + /** + * Get the current environment data + * @return {Promise} resolved/rejected promise when the command is complete + */ + static report() { + cmdUtil.log(chalk.blue.bold('Creating Composer report\n')); + let tmpDirectory = this._setupReportDir(); + this._createNodeReport(tmpDirectory); + return this._archiveReportDir(tmpDirectory); + } + + /** + * Sets up the temp directory for the report + * @return {String} the Path to the temporary directory + */ + static _setupReportDir() { + const tmpDir = os.tmpdir(); + return fs.mkdtempSync(`${tmpDir}${sep}`); + } + + /** + * Trigger node-report to write report in the temp directory + * @param {String} tmpDirectory the temporary directory for collecting report output + */ + static _createNodeReport(tmpDirectory) { + cmdUtil.log(chalk.blue('Triggering node report...')); + nodereport.setDirectory(tmpDirectory); + nodereport.triggerReport(); + } + + /** + * Creates an archive of the temp directory for the report + * @param {String} tmpDirectory the temporary directory for collecting report output + * @param {String} outputFilename the name of the file that was optionally passed in on the command line + * @return {Promise} resolved/rejected promise when the archive has been created + */ + static _archiveReportDir(tmpDirectory) { + let timestamp = moment().format('YYYYMMDD[T]HHmmss'); + let prefix = 'composer-report-' + timestamp; + let filename = prefix + '.tgz'; + return tar.c( + { + cwd: tmpDirectory+'/', + prefix: prefix, + gzip: true, + file: filename + }, + ['.'] + ).then(() => { + cmdUtil.log(chalk.blue.bold('\nSuccessfully created Composer report file to ')); + cmdUtil.log(chalk.blue('\tOutput file: ')+filename); + }); + } +} +module.exports = Report; diff --git a/packages/composer-cli/lib/cmds/report/reportCommand.js b/packages/composer-cli/lib/cmds/report/reportCommand.js new file mode 100644 index 0000000000..bf5b60a6cb --- /dev/null +++ b/packages/composer-cli/lib/cmds/report/reportCommand.js @@ -0,0 +1,24 @@ +/* + * Licensed 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 CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const Report = require ('./lib/report.js'); + +module.exports.command = 'report'; +module.exports.describe = 'Create a report of the current execution environment'; + +module.exports.handler = (argv) => { + return argv.thePromise = Report.handler(argv); +}; diff --git a/packages/composer-cli/package.json b/packages/composer-cli/package.json index f9c6125758..5bacd3144f 100644 --- a/packages/composer-cli/package.json +++ b/packages/composer-cli/package.json @@ -49,12 +49,15 @@ "homedir": "0.6.0", "js-yaml": "3.10.0", "mkdirp": "0.5.1", + "moment":"2.19.3", + "node-report":"2.2.1", "npm-paths": "0.1.3", "nunjucks": "3.0.0", "ora": "1.2.0", "prettyjson": "1.2.1", "prompt": "1.0.0", "sanitize-filename": "1.6.1", + "tar":"4.3.0", "yargs": "10.0.3" }, "license-check-config": { diff --git a/packages/composer-cli/test/report/report.js b/packages/composer-cli/test/report/report.js new file mode 100644 index 0000000000..e2e0bd6992 --- /dev/null +++ b/packages/composer-cli/test/report/report.js @@ -0,0 +1,91 @@ +/* + * Licensed 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 CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const ReportCmd = require('../../lib/cmds/report/reportCommand.js'); +const chai = require('chai'); +const sinon = require('sinon'); +chai.should(); +chai.use(require('chai-as-promised')); +const fs = require('fs'); +const os = require('os'); +const nodereport = require('node-report'); +const tar = require('tar'); + + + +describe('composer report CLI', function() { + const sandbox = sinon.sandbox.create(); + let consoleLogSpy; + let mkdtempSyncStub; + let triggerReportStub; + let setDirectoryStub; + let cStub; + + beforeEach(function() { + consoleLogSpy = sandbox.spy(console, 'log'); + sandbox.stub(process, 'exit'); + mkdtempSyncStub = sandbox.stub(fs,'mkdtempSync').returns('COMPOSER_REPORT_TEMPDIR'); + sandbox.stub(os, 'tmpdir').returns('OS_TEMPDIR'); + triggerReportStub = sandbox.stub(nodereport, 'triggerReport'); + setDirectoryStub = sandbox.stub(nodereport, 'setDirectory'); + cStub = sandbox.stub(tar, 'c').returns(Promise.resolve()); + }); + + afterEach(function() { + sandbox.restore(); + }); + + it('should successfully run the composer report command with no arguments specified', function() { + const args = {}; + return ReportCmd.handler(args).then(() => { + sinon.assert.calledWith(consoleLogSpy, sinon.match('Creating Composer report')); + }); + }); + + it('should create a temporary directory to store files to create the report archive from', function() { + const args = {}; + return ReportCmd.handler(args).then(() => { + sinon.assert.calledOnce(mkdtempSyncStub); + sinon.assert.calledWith(mkdtempSyncStub, 'OS_TEMPDIR/'); + }); + }); + + it('should successfully write a node-report report to the temporary directory', function() { + const args = { }; + return ReportCmd.handler(args).then(() => { + sinon.assert.calledOnce(setDirectoryStub); + sinon.assert.calledWith(setDirectoryStub, 'COMPOSER_REPORT_TEMPDIR'); + sinon.assert.calledOnce(triggerReportStub); + }); + }); + + it('should successfully create a zipped tar archive of the COMPOSER_REPORT_TEMPDIR in the current directory and log the output filename in the console', function() { + const args = { }; + return ReportCmd.handler(args).then(() => { + sinon.assert.calledOnce(cStub); + sinon.assert.calledWith(cStub, { + cwd: 'COMPOSER_REPORT_TEMPDIR/', + prefix: sinon.match(/^composer-report-\d{8}T\d{6}$/), + gzip: true, + file: sinon.match(/^composer-report-\d{8}T\d{6}\.tgz$/) + }, ['.']); + sinon.assert.called(consoleLogSpy); + sinon.assert.calledWith(consoleLogSpy, sinon.match('Triggering node report...')); + sinon.assert.calledWith(consoleLogSpy, sinon.match('Successfully created Composer report file to')); + sinon.assert.calledWith(consoleLogSpy, sinon.match(/Output file: .*composer-report-\d{8}T\d{6}\.tgz$/)); + }); + }); +}); diff --git a/packages/composer-website/jekylldocs/reference/commands.md b/packages/composer-website/jekylldocs/reference/commands.md index cfe0f9a48d..52baa7d597 100644 --- a/packages/composer-website/jekylldocs/reference/commands.md +++ b/packages/composer-website/jekylldocs/reference/commands.md @@ -117,6 +117,12 @@ List all identities in a business network: [composer identity list](./composer.i Revoke an identity from a participant: [composer identity revoke](./composer.identity.revoke.html) +## Support diagnostics + +`composer report` + +Create a diagnostic report: [composer report](./composer.report) + ## Transaction execution `composer transaction submit` diff --git a/packages/composer-website/jekylldocs/reference/composer.report.md b/packages/composer-website/jekylldocs/reference/composer.report.md new file mode 100644 index 0000000000..a58ccc65f4 --- /dev/null +++ b/packages/composer-website/jekylldocs/reference/composer.report.md @@ -0,0 +1,23 @@ +--- +layout: default +title: Hyperledger Composer Report +section: reference-command +sidebar: sidebars/accordion-toc0.md +excerpt: Hyperledger Composer Report +--- + +# Composer Report + +Creates a report archive containing system and environment information to assist with problem determination. + +``` +composer report +``` + +## Syntax + +``` +Options: + --help Show help [boolean] + -v, --version Show version number [boolean] +```