-
Notifications
You must be signed in to change notification settings - Fork 515
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FABN-948] Add top-level Package class to SDK
Introduce a new top-level class called Package, for working with chaincode packages. A chaincode package is the same format as created by the "peer chaincode package" command. This commit allows a user of the SDK to: - Load a chaincode package from an existing buffer/file and get information about it - Create a new chaincode package from a directory - Save a chaincode package to a buffer/file See FABN-948 for the details. Change-Id: I499c5bf7dd03a6aedad9c28f06a6c850e1a8ce41 Signed-off-by: Simon Stone <[email protected]>
- Loading branch information
Simon Stone
committed
Oct 3, 2018
1 parent
cedc8cb
commit a50f68d
Showing
45 changed files
with
1,108 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/** | ||
* Copyright 2018 IBM All Rights Reserved. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const clientUtils = require('./client-utils.js'); | ||
const grpc = require('grpc'); | ||
const Packager = require('./Packager.js'); | ||
const tar = require('tar-stream'); | ||
const utils = require('./utils.js'); | ||
const zlib = require('zlib'); | ||
|
||
const _ccProto = grpc.load(__dirname + '/protos/peer/chaincode.proto').protos; | ||
const logger = utils.getLogger('package'); | ||
|
||
/** | ||
* A class representing a smart contract package. | ||
*/ | ||
class Package { | ||
|
||
/** | ||
* Find the list of file names in the specified chaincode deployment specification. | ||
* @private | ||
* @param {ChaincodeDeploymentSpec} chaincodeDeploymentSpec The chaincode deployment specification. | ||
* @returns {string[]} The list of file names. | ||
*/ | ||
static async _findFileNames(chaincodeDeploymentSpec) { | ||
const codePackage = chaincodeDeploymentSpec.getCodePackage().toBuffer(); | ||
const gunzip = zlib.createGunzip(); | ||
const extract = tar.extract(); | ||
return new Promise((resolve) => { | ||
const fileNames = []; | ||
extract.on('entry', (header, stream, next) => { | ||
logger.debug('Package._findFileNames - found entry %s', header.name); | ||
fileNames.push(header.name); | ||
stream.on('end', () => { | ||
next(); | ||
}); | ||
stream.resume(); | ||
}); | ||
extract.on('finish', () => { | ||
resolve(fileNames.sort()); | ||
}); | ||
gunzip.pipe(extract); | ||
gunzip.end(codePackage); | ||
}); | ||
} | ||
|
||
/** | ||
* Load a smart contract package from the specified buffer. | ||
* @param {Buffer} buffer A buffer containing the serialized smart contract package. | ||
* @returns {Package} The smart contract package. | ||
*/ | ||
static async fromBuffer(buffer) { | ||
const chaincodeDeploymentSpec = _ccProto.ChaincodeDeploymentSpec.decode(buffer); | ||
const fileNames = await Package._findFileNames(chaincodeDeploymentSpec); | ||
return new Package(chaincodeDeploymentSpec, fileNames); | ||
} | ||
|
||
/** | ||
* Create a new smart contract package from the specified directory. | ||
* @param {Object} options The options for the packager. | ||
* @param {string} options.name The name of the smart contract. | ||
* @param {string} options.version The version of the smart contract. | ||
* @param {string} options.path The directory containing the smart contract. | ||
* @param {string} options.type The type of the smart contract, one of 'golang', 'car', 'node' or 'java'. | ||
* @param {string} [options.metadataPath] The directory containing the metadata descriptors. | ||
* @returns {Package} The smart contract package. | ||
*/ | ||
static async fromDirectory({ name, version, path, type, metadataPath }) { | ||
logger.debug('Package.fromDirectory - entry - %s, %s, %s, %s', name, version, path, type); | ||
const codePackage = await Packager.package(path, type, false, metadataPath); | ||
logger.debug('Package.fromDirectory - code package is %s bytes', codePackage.length); | ||
const chaincodeSpec = { | ||
type: clientUtils.translateCCType(type), | ||
chaincode_id: { | ||
name, | ||
path, | ||
version | ||
} | ||
}; | ||
logger.debug('Package.fromDirectory - built chaincode specification %s', JSON.stringify(chaincodeSpec)); | ||
const chaincodeDeploymentSpec = new _ccProto.ChaincodeDeploymentSpec(); | ||
chaincodeDeploymentSpec.setChaincodeSpec(chaincodeSpec); | ||
chaincodeDeploymentSpec.setCodePackage(codePackage); | ||
const fileNames = await Package._findFileNames(chaincodeDeploymentSpec); | ||
return new Package(chaincodeDeploymentSpec, fileNames); | ||
} | ||
|
||
/** | ||
* Constructor. | ||
* @private | ||
* @param {ChaincodeDeploymentSpec} chaincodeDeploymentSpec The chaincode deployment specification. | ||
*/ | ||
constructor(chaincodeDeploymentSpec, fileNames) { | ||
this.chaincodeDeploymentSpec = chaincodeDeploymentSpec; | ||
this.fileNames = fileNames; | ||
} | ||
|
||
/** | ||
* Get the name of the smart contract package. | ||
* @returns {string} The name of the smart contract package. | ||
*/ | ||
getName() { | ||
return this.chaincodeDeploymentSpec.getChaincodeSpec().getChaincodeId().getName(); | ||
} | ||
|
||
/** | ||
* Get the version of the smart contract package. | ||
* @returns {string} The version of the smart contract package. | ||
*/ | ||
getVersion() { | ||
return this.chaincodeDeploymentSpec.getChaincodeSpec().getChaincodeId().getVersion(); | ||
} | ||
|
||
/** | ||
* Get the type of the smart contract package. | ||
* @returns {string} The type of the smart contract package, one of 'golang', 'car', 'node' or 'java'. | ||
*/ | ||
getType() { | ||
return clientUtils.ccTypeToString(this.chaincodeDeploymentSpec.getChaincodeSpec().getType()); | ||
} | ||
|
||
/** | ||
* Get the list of file names in this smart contract package. | ||
* @returns {string[]} The list of file names in this smart contract package. | ||
*/ | ||
getFileNames() { | ||
return this.fileNames; | ||
} | ||
|
||
/** | ||
* Save the smart contract package to a buffer. | ||
* @returns {Buffer} A buffer containing the serialized smart contract package. | ||
*/ | ||
async toBuffer() { | ||
return this.chaincodeDeploymentSpec.toBuffer(); | ||
} | ||
|
||
} | ||
|
||
module.exports = Package; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
/** | ||
* Copyright 2018 IBM All Rights Reserved. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const fs = require('fs'); | ||
const Package = require('..').Package; | ||
const path = require('path'); | ||
|
||
const chai = require('chai'); | ||
chai.should(); | ||
chai.use(require('chai-as-promised')); | ||
|
||
const languages = ['golang', 'javascript', 'typescript', 'java']; | ||
|
||
const languageToType = { | ||
golang: 'golang', | ||
javascript: 'node', | ||
typescript: 'node', | ||
java: 'java' | ||
}; | ||
|
||
const fileNames = { | ||
golang: [ | ||
'src/golang-contract/chaincode.go', | ||
'src/golang-contract/chaincode_test.go', | ||
'src/golang-contract/main.go' | ||
], | ||
javascript: [ | ||
'src/.editorconfig', | ||
'src/.eslintignore', | ||
'src/.eslintrc.js', | ||
'src/index.js', | ||
'src/lib/chaincode.js', | ||
'src/lib/start.js', | ||
'src/package.json', | ||
'src/test/chaincode.js', | ||
'src/test/start.js' | ||
], | ||
typescript: [ | ||
'src/.editorconfig', | ||
'src/package.json', | ||
'src/src/chaincode.spec.ts', | ||
'src/src/chaincode.ts', | ||
'src/src/index.ts', | ||
'src/src/start.spec.ts', | ||
'src/src/start.ts', | ||
'src/tsconfig.json', | ||
'src/tslint.json' | ||
], | ||
java: [ | ||
'src/build.gradle', | ||
'src/settings.gradle', | ||
'src/src/main/java/org/example/Chaincode.java', | ||
'src/src/main/java/org/example/Start.java', | ||
'src/src/test/java/org/example/ChaincodeTest.java' | ||
] | ||
}; | ||
|
||
const metadataFileNames = [ | ||
'META-INF/statedb/couchdb/indexes/indexOwner.json' | ||
]; | ||
|
||
describe('Package', () => { | ||
|
||
let GOPATH; | ||
|
||
beforeEach(() => { | ||
GOPATH = process.env.GOPATH; | ||
process.env.GOPATH = path.resolve(__dirname, 'data', 'go'); | ||
}); | ||
|
||
afterEach(() => { | ||
process.env.GOPATH = GOPATH; | ||
}); | ||
|
||
describe('#fromBuffer', () => { | ||
|
||
for (const language of languages) { | ||
|
||
const type = languageToType[language]; | ||
|
||
it(`should load a smart contract package from a buffer [${language}]`, async () => { | ||
const pkgFile = path.resolve(__dirname, 'data', `${language}-contract.cds`); | ||
const pkgBuffer = fs.readFileSync(pkgFile); | ||
const pkg = await Package.fromBuffer(pkgBuffer); | ||
pkg.getName().should.equal('my-contract'); | ||
pkg.getVersion().should.equal('1.2.3'); | ||
pkg.getType().should.equal(type); | ||
pkg.getFileNames().should.deep.equal(fileNames[language]); | ||
}); | ||
|
||
it(`should load a smart contract package from a buffer with metadata [${language}]`, async () => { | ||
const pkgFile = path.resolve(__dirname, 'data', `${language}-contract-metadata.cds`); | ||
const pkgBuffer = fs.readFileSync(pkgFile); | ||
const pkg = await Package.fromBuffer(pkgBuffer); | ||
pkg.getName().should.equal('my-contract'); | ||
pkg.getVersion().should.equal('1.2.3'); | ||
pkg.getType().should.equal(type); | ||
pkg.getFileNames().should.deep.equal(metadataFileNames.concat(fileNames[language])); | ||
}); | ||
|
||
} | ||
|
||
}); | ||
|
||
describe('#fromDirectory', () => { | ||
|
||
for (const language of languages) { | ||
|
||
const type = languageToType[language]; | ||
|
||
it(`should create a smart contract package from a directory [${language}]`, async () => { | ||
let pkgDirectory; | ||
if (language === 'golang') { | ||
pkgDirectory = `${language}-contract`; | ||
} else { | ||
pkgDirectory = path.resolve(__dirname, 'data', `${language}-contract`); | ||
} | ||
const pkg = await Package.fromDirectory({ name: 'my-contract', version: '1.2.3', path: pkgDirectory, type }); | ||
pkg.getName().should.equal('my-contract'); | ||
pkg.getVersion().should.equal('1.2.3'); | ||
pkg.getType().should.equal(type); | ||
pkg.getFileNames().should.deep.equal(fileNames[language]); | ||
}); | ||
|
||
it(`should create a smart contract package from a directory with metadata [${language}]`, async () => { | ||
let pkgDirectory; | ||
if (language === 'golang') { | ||
pkgDirectory = `${language}-contract`; | ||
} else { | ||
pkgDirectory = path.resolve(__dirname, 'data', `${language}-contract`); | ||
} | ||
const metaDirectory = path.resolve(__dirname, 'data', 'META-INF'); | ||
const pkg = await Package.fromDirectory({ name: 'my-contract', version: '1.2.3', path: pkgDirectory, type, metadataPath: metaDirectory }); | ||
pkg.getName().should.equal('my-contract'); | ||
pkg.getVersion().should.equal('1.2.3'); | ||
pkg.getType().should.equal(type); | ||
pkg.getFileNames().should.deep.equal(metadataFileNames.concat(fileNames[language])); | ||
}); | ||
|
||
} | ||
|
||
}); | ||
|
||
describe('#toBuffer', async () => { | ||
|
||
for (const language of languages) { | ||
|
||
it(`should save a smart contract package to a buffer [${language}]`, async () => { | ||
const pkgFile = path.resolve(__dirname, 'data', `${language}-contract.cds`); | ||
const pkgBuffer1 = fs.readFileSync(pkgFile); | ||
const pkg = await Package.fromBuffer(pkgBuffer1); | ||
const pkgBuffer2 = await pkg.toBuffer(); | ||
Buffer.compare(pkgBuffer1, pkgBuffer2).should.equal(0); | ||
}); | ||
|
||
} | ||
|
||
}); | ||
|
||
}); |
Oops, something went wrong.