From 1a37371029f4f9255ec8eb1beb5ff41f1b988f6b Mon Sep 17 00:00:00 2001 From: Gari Singh Date: Sun, 25 Feb 2018 12:11:20 -0500 Subject: [PATCH] [FAB-8345] Package chaincode metadata descriptors Adds an optional metadataPath to the chaincode package function for Golang and Node.js chaincode. Change-Id: I90e72a79e9f5511a042ae281cb91820e0b965ed8 Signed-off-by: Gari Singh --- fabric-client/lib/Packager.js | 18 +++-- fabric-client/lib/packager/BasePackager.js | 52 +++++++++++++- fabric-client/lib/packager/Golang.js | 34 ++++----- fabric-client/lib/packager/Node.js | 25 ++++--- .../statedb/couchdb/indexes/index.json | 1 + .../statedb/couchdb/indexes/index.notjson | 1 + test/unit/packager.js | 71 +++++++++++++++++++ test/unit/util.js | 2 +- 8 files changed, 167 insertions(+), 37 deletions(-) create mode 100644 test/fixtures/metadata/statedb/couchdb/indexes/index.json create mode 100644 test/fixtures/metadata/statedb/couchdb/indexes/index.notjson diff --git a/fabric-client/lib/Packager.js b/fabric-client/lib/Packager.js index 3ad19cfdaf..57bdd86a93 100644 --- a/fabric-client/lib/Packager.js +++ b/fabric-client/lib/Packager.js @@ -24,14 +24,15 @@ var logger = utils.getLogger('packager'); /** * Utility function to package a chaincode. The contents will be returned as a byte array. * - * @param {Object} chaincodePath required - String of the path to location of + * @param {string} chaincodePath required - String of the path to location of * the source code of the chaincode - * @param {Object} chaincodeType optional - String of the type of chaincode + * @param {string} [chaincodeType] String of the type of chaincode * ['golang', 'node', 'car', 'java'] (default 'golang') - * @param {boolean} devmode optional - True if using dev mode + * @param {boolean} [devmode] Set to true to use chaincode development mode + * @param {string} [metadataPath] The path to the top-level directory containing metadata descriptors * @returns {Promise} A promise for the data as a byte array */ -module.exports.package = function(chaincodePath, chaincodeType, devmode) { +module.exports.package = function(chaincodePath, chaincodeType, devmode, metadataPath) { logger.debug('packager: chaincodePath: %s, chaincodeType: %s, devmode: %s',chaincodePath,chaincodeType,devmode); return new Promise(function(resolve, reject) { if (devmode) { @@ -57,9 +58,14 @@ module.exports.package = function(chaincodePath, chaincodeType, devmode) { handler = new Node(); break; default: - handler = new Golang(); + let keep = [ + '.go', + '.c', + '.h' + ]; + handler = new Golang(keep); } - return resolve(handler.package(chaincodePath)); + return resolve(handler.package(chaincodePath, metadataPath)); }); }; diff --git a/fabric-client/lib/packager/BasePackager.js b/fabric-client/lib/packager/BasePackager.js index 064d9cca43..abbd91c34a 100644 --- a/fabric-client/lib/packager/BasePackager.js +++ b/fabric-client/lib/packager/BasePackager.js @@ -15,11 +15,18 @@ 'use strict'; var fs = require('fs-extra'); +var klaw = require('klaw'); var tar = require('tar-stream'); var path = require('path'); var zlib = require('zlib'); var BasePackager = class { + + /** + * Constructor + * + * @param {*} [keep] Array of valid source file extensions + */ constructor (keep) { if (this.constructor === BasePackager) { // BasePackager can not be constructed. @@ -37,8 +44,9 @@ var BasePackager = class { * included in an archive file. * * @param chaincodePath + * @param metadataPath */ - package (chaincodePath) { + package (chaincodePath, metadataPath) { throw new TypeError('Please implement method package from child class'); } @@ -52,6 +60,48 @@ var BasePackager = class { throw new Error('abstract function called'); } + /** + * Find the metadata descriptor files. + * + * @param filePath The top-level directory containing the metadata descriptors. + * Only files with a ".json" extension will be included in the results. + * @returns {Promise} + */ + findMetadataDescriptors (filePath) { + return new Promise((resolve, reject) => { + var descriptors = []; + klaw(filePath).on('data', (entry) => { + if (entry.stats.isFile() && this.isMetadata(entry.path)) { + + var desc = { + name: path.join('META-INF', path.relative(filePath, entry.path).split('\\').join('/')), // for windows style paths + fqp: entry.path + }; + descriptors.push(desc); + } + }) + .on('error', (error, item) => { + logger.error(`error while packaging ${item.path}`); + reject(error); + }) + .on('end', () => { + resolve(descriptors); + }); + }); + } + + /** + * Predicate function for determining whether a given path should be + * considered a valid metadata descriptor based entirely on the + * file extension. + * @param filePath The top-level directory containing the metadata descriptors. + * @returns {boolean} Returns true for valid metadata descriptors. + */ + isMetadata (filePath) { + var extensions = ['.json']; + return (extensions.indexOf(path.extname(filePath)) != -1); + } + /** * Predicate function for determining whether a given path should be * considered valid source code, based entirely on the extension. It is diff --git a/fabric-client/lib/packager/Golang.js b/fabric-client/lib/packager/Golang.js index e6fe1c669f..e57f9f0834 100644 --- a/fabric-client/lib/packager/Golang.js +++ b/fabric-client/lib/packager/Golang.js @@ -25,29 +25,16 @@ var BasePackager = require('./BasePackager'); var logger = utils.getLogger('packager/Golang.js'); -// A list of file extensions that should be packaged into the .tar.gz. -// Files with all other file extenstions will be excluded to minimize the size -// of the install payload. -var keep = [ - '.go', - '.c', - '.h' -]; - class GolangPackager extends BasePackager { - constructor () { - super(keep); - } - /** - * All of the files in the directory of the environment variable - * GOPATH joined to the request.chaincodePath will be included - * in an archive file. - * @param chaincodePath + * Package chaincode source and metadata for deployment. + * @param {string} chaincodePath The Go package name. The GOPATH environment variable must be set + * and the package must be located under GOPATH/src. + * @param {string} [metadataPath] The path to the top-level directory containing metadata descriptors. * @returns {Promise.} */ - package (chaincodePath) { + package (chaincodePath, metadataPath) { logger.info('packaging GOLANG from %s', chaincodePath); // Determine the user's $GOPATH @@ -63,7 +50,16 @@ class GolangPackager extends BasePackager { var buffer = new sbuf.WritableStreamBuffer(); - return this.findSource(goPath, projDir).then((descriptors) => { + return this.findSource(goPath, projDir).then((srcDescriptors) => { + if (metadataPath){ + return super.findMetadataDescriptors(metadataPath) + .then((metaDescriptors) => { + return srcDescriptors.concat(metaDescriptors); + }); + } else { + return srcDescriptors; + } + }).then((descriptors) => { return super.generateTarGz(descriptors, buffer); }).then(() => { return buffer.getContents(); diff --git a/fabric-client/lib/packager/Node.js b/fabric-client/lib/packager/Node.js index 80549ad299..2dbd453618 100644 --- a/fabric-client/lib/packager/Node.js +++ b/fabric-client/lib/packager/Node.js @@ -25,17 +25,14 @@ let BasePackager = require('./BasePackager'); class NodePackager extends BasePackager { - constructor () { - super([]); - } - /** - * All of the files in the directory of request.chaincodePath will be - * included in an archive file. - * @param chaincodePath + * Package chaincode source and metadata for deployment. + * @param {string} chaincodePath The path to the top-level directory containing the source code + * and package.json. + * @param {string} [metadataPath] The path to the top-level directory containing metadata descriptors * @returns {Promise.} */ - package (chaincodePath) { + package (chaincodePath, metadataPath) { logger.info('packaging Node from %s', chaincodePath); // Compose the path to the chaincode project directory @@ -47,8 +44,16 @@ class NodePackager extends BasePackager { // will need to assemble sources from multiple packages let buffer = new sbuf.WritableStreamBuffer(); - - return this.findSource(projDir).then((descriptors) => { + return this.findSource(projDir).then((srcDescriptors) => { + if (metadataPath){ + return super.findMetadataDescriptors(metadataPath) + .then((metaDescriptors) => { + return srcDescriptors.concat(metaDescriptors); + }); + } else { + return srcDescriptors; + } + }).then((descriptors) => { return super.generateTarGz(descriptors, buffer); }).then(() => { return buffer.getContents(); diff --git a/test/fixtures/metadata/statedb/couchdb/indexes/index.json b/test/fixtures/metadata/statedb/couchdb/indexes/index.json new file mode 100644 index 0000000000..a1b9e22b6c --- /dev/null +++ b/test/fixtures/metadata/statedb/couchdb/indexes/index.json @@ -0,0 +1 @@ +{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} \ No newline at end of file diff --git a/test/fixtures/metadata/statedb/couchdb/indexes/index.notjson b/test/fixtures/metadata/statedb/couchdb/indexes/index.notjson new file mode 100644 index 0000000000..a696bd254e --- /dev/null +++ b/test/fixtures/metadata/statedb/couchdb/indexes/index.notjson @@ -0,0 +1 @@ +not json \ No newline at end of file diff --git a/test/unit/packager.js b/test/unit/packager.js index 13b357df4f..796c3bccbd 100644 --- a/test/unit/packager.js +++ b/test/unit/packager.js @@ -15,6 +15,51 @@ const fs = require('fs-extra'); const targz = require('targz'); const Packager = require('fabric-client/lib/Packager.js'); +var Node = require('fabric-client/lib/packager/Node.js'); +var Golang = require('fabric-client/lib/packager/Golang.js'); + + + +test('\n\n** BasePackager tests **\n\n', function(t) { + var keep = [ + '.keep', + '.keep2' + ]; + // test with concrete implementations + var node = new Node(keep); + t.equal(node.isSource('path/src.keep'), true, 'Node.isSource() should return true for valid extension \".keep\"'); + t.equal(node.isSource('path/src.keep2'), true, 'Node.isSource() should return true for valid extension \".keep2\"'); + t.equal(node.isSource('path/src.keep3'), false, 'Node.isSource() should return false for invalid extension \".keep3\"'); + t.equal(node.isMetadata('path/metadata.json'), true, 'Node.isMetadata() should return true for valid extension \".json\"'); + t.equal(node.isMetadata('path/metadata.notjson'), false, 'Node.isMetadata() should return false for invalid extension \".notjson\"'); + node.findMetadataDescriptors(testutil.METADATA_PATH) + .then((descriptors) => { + let expected = 'META-INF/statedb/couchdb/indexes/index.json'; + t.equal(descriptors.length, 1, 'Expected Node.findMetadataDescriptors() to return one valid descriptor'); + t.equal(descriptors[0].name, expected, 'Node.findMetadataDescriptors() should return valid descriptor name'); + }).catch((err) => { + t.fail('Node.findMetadataDescriptors() failed with unexpected error'); + t.comment(err.stack ? err.stack : err); + }); + + var golang = new Golang(keep); + t.equal(golang.isSource('path/src.keep'), true, 'Golang.isSource() should return true for valid extension \".keep\"'); + t.equal(golang.isSource('path/src.keep2'), true, 'Golang.isSource() should return true for valid extension \".keep2\"'); + t.equal(golang.isSource('path/src.keep3'), false, 'Golang.isSource() should return false for invalid extension \".keep3\"'); + t.equal(golang.isMetadata('path/metadata.json'), true, 'Golang.isMetadata() should return true for valid extension \".json\"'); + t.equal(golang.isMetadata('path/metadata.notjson'), false, 'Golang.isMetadata() should return false for invalid extension \".notjson\"'); + golang.findMetadataDescriptors(testutil.METADATA_PATH) + .then((descriptors) => { + let expected = 'META-INF/statedb/couchdb/indexes/index.json'; + t.equal(descriptors.length, 1, 'Expected Golang.findMetadataDescriptors() to return one valid descriptor'); + t.equal(descriptors[0].name, expected, 'Golang.findMetadataDescriptors() should return valid descriptor name'); + }).catch((err) => { + t.fail('Golang.findMetadataDescriptors() failed with unexpected error'); + t.comment(err.stack ? err.stack : err); + }); + + t.end(); +}); test('\n\n** Golang Packager tests **\n\n', function(t) { Packager.package('blah','',true) @@ -70,6 +115,24 @@ test('\n\n** Golang Packager tests **\n\n', function(t) { t.end(); }); + return Packager.package(testutil.CHAINCODE_PATH,'', false, testutil.METADATA_PATH); + }).then((data) => { + let tmpFile = path.join(testutil.getTempDir(), 'test-deploy-copy.tar.gz'); + let destDir = path.join(testutil.getTempDir(), 'test-deploy-copy-tar-gz'); + fs.writeFileSync(tmpFile, data); + fs.removeSync(destDir); + targz.decompress({ + src: tmpFile, + dest: destDir + }, (err) => { + if (err) { + t.fail('Failed to extract generated chaincode package. ' + err); + let checkPath = path.join(destDir, 'META-INF', 'statedb', 'couchdb', 'indexes', 'index.json'); + t.equal(fs.existsSync(checkPath), true, + 'The tar.gz file produced by Packager.package() has the "META-INF/statedb/couchdb/indexes/index.json" file'); + } + t.end(); + }); }).catch((err) => { t.fail('Caught error in Package.package tests'); t.comment(err.stack ? err.stack : err); @@ -149,6 +212,14 @@ test('\n\n** Node.js Packager tests **\n\n', function(t) { checkPath = path.join(targzDir, 'src', 'node_modules'); t.equal(fs.existsSync(checkPath), true, 'The tar.gz file produced by Packager.package() has the "node_modules" folder'); }); + }).then(()=>{ + return Packager.package(destDir, 'node', false, testutil.METADATA_PATH); + }).then((data) => { + return check(data, () => { + let checkPath = path.join(targzDir, 'META-INF', 'statedb', 'couchdb', 'indexes', 'index.json'); + t.equal(fs.existsSync(checkPath), true, + 'The tar.gz file produced by Packager.package() has the "META-INF/statedb/couchdb/indexes/index.json" file'); + }); }).then(() => { t.end(); }).catch((err) => { diff --git a/test/unit/util.js b/test/unit/util.js index 598aa03bf3..24985f85e8 100644 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -44,7 +44,7 @@ module.exports.END2END = { module.exports.NODE_CHAINCODE_PATH = path.resolve(__dirname, '../fixtures/src/node_cc/example_cc'); module.exports.NODE_CHAINCODE_UPGRADE_PATH = path.resolve(__dirname, '../fixtures/src/node_cc/example_cc1'); module.exports.NODE_CHAINCODE_UPGRADE_PATH_V2 = path.resolve(__dirname, '../fixtures/src/node_cc/example_cc2'); - +module.exports.METADATA_PATH = path.resolve(__dirname, '../fixtures/metadata'); module.exports.NODE_END2END = { channel: 'mychannel',