Commit f4996fcf authored by Jonathan Levi (HACERA)'s avatar Jonathan Levi (HACERA) Committed by Gerrit Code Review
Browse files

Merge "FAB-11587 Add new tar gz chaincode package format"

parents 1b2d58c4 d54d863f
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package persistence
import (
"archive/tar"
"bytes"
"compress/gzip"
"encoding/json"
"io"
"io/ioutil"
"github.com/pkg/errors"
)
// The chaincode package is simply a .tar.gz file. For the time being, we
// assume that the package contains a Chaincode-Package-Metadata.json file
// which contains a 'Type', and optionally a 'Path'. In the future, it would
// be nice if we moved to a more buildpack type system, rather than the below
// presented JAR+manifest type system, but for expediency and incremental changes,
// moving to a tar format over the proto format for a user-inspectable artifact
// seems like a good step.
const (
ChaincodePackageMetadataFile = "Chaincode-Package-Metadata.json"
)
// ChaincodePackage represents the un-tar-ed format of the chaincode package.
type ChaincodePackage struct {
Metadata *ChaincodePackageMetadata
CodePackage []byte
}
// ChaincodePackageMetadata contains the information necessary to understand
// the embedded code package.
type ChaincodePackageMetadata struct {
Type string `json:"Type"`
Path string `json:"Path"`
}
// ChaincodePackageParser provides the ability to parse chaincode packages
type ChaincodePackageParser struct{}
// Parse parses a set of bytes as a chaincode package
// and returns the parsed package as a struct
func (ccpp ChaincodePackageParser) Parse(source []byte) (*ChaincodePackage, error) {
gzReader, err := gzip.NewReader(bytes.NewBuffer(source))
if err != nil {
return nil, errors.Wrapf(err, "error reading as gzip stream")
}
tarReader := tar.NewReader(gzReader)
var codePackage []byte
var ccPackageMetadata *ChaincodePackageMetadata
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, errors.Wrapf(err, "error inspecting next tar header")
}
if header.Typeflag != tar.TypeReg {
return nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag)
}
fileBytes, err := ioutil.ReadAll(tarReader)
if err != nil {
return nil, errors.Wrapf(err, "could not read %s from tar", header.Name)
}
if header.Name == ChaincodePackageMetadataFile {
ccPackageMetadata = &ChaincodePackageMetadata{}
err := json.Unmarshal(fileBytes, ccPackageMetadata)
if err != nil {
return nil, errors.Wrapf(err, "could not unmarshal %s as json", ChaincodePackageMetadataFile)
}
continue
}
if codePackage != nil {
return nil, errors.Errorf("found too many files in archive, cannot identify which file is the code-package")
}
codePackage = fileBytes
}
if codePackage == nil {
return nil, errors.Errorf("did not find a code package inside the package")
}
if ccPackageMetadata == nil {
return nil, errors.Errorf("did not find any package metadata (missing %s)", ChaincodePackageMetadataFile)
}
return &ChaincodePackage{
Metadata: ccPackageMetadata,
CodePackage: codePackage,
}, nil
}
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package persistence_test
import (
"io/ioutil"
"github.com/hyperledger/fabric/core/chaincode/persistence"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("ChaincodePackageParser", func() {
var (
ccpp persistence.ChaincodePackageParser
)
Describe("ParseChaincodePackage", func() {
It("parses a chaincode package", func() {
data, err := ioutil.ReadFile("testdata/good-package.tar.gz")
Expect(err).NotTo(HaveOccurred())
ccPackage, err := ccpp.Parse(data)
Expect(err).NotTo(HaveOccurred())
Expect(ccPackage.Metadata).To(Equal(&persistence.ChaincodePackageMetadata{
Type: "Fake-Type",
Path: "Fake-Path",
}))
})
Context("when the data is not gzipped", func() {
It("fails", func() {
_, err := ccpp.Parse([]byte("bad-data"))
Expect(err).To(MatchError("error reading as gzip stream: unexpected EOF"))
})
})
Context("when the chaincode package metadata is missing", func() {
It("fails", func() {
data, err := ioutil.ReadFile("testdata/missing-metadata.tar.gz")
Expect(err).NotTo(HaveOccurred())
_, err = ccpp.Parse(data)
Expect(err).To(MatchError("did not find any package metadata (missing Chaincode-Package-Metadata.json)"))
})
})
Context("when the chaincode package metadata is missing", func() {
It("fails", func() {
data, err := ioutil.ReadFile("testdata/bad-metadata.tar.gz")
Expect(err).NotTo(HaveOccurred())
_, err = ccpp.Parse(data)
Expect(err).To(MatchError("could not unmarshal Chaincode-Package-Metadata.json as json: invalid character '\\n' in string literal"))
})
})
Context("when the tar file is corrupted", func() {
It("fails", func() {
data, err := ioutil.ReadFile("testdata/corrupted-package.tar.gz")
Expect(err).NotTo(HaveOccurred())
_, err = ccpp.Parse(data)
Expect(err).To(MatchError("could not read Chaincode-Package-Metadata.json from tar: unexpected EOF"))
})
})
Context("when the tar has non-regular files", func() {
It("fails", func() {
data, err := ioutil.ReadFile("testdata/non-regular-file.tar.gz")
Expect(err).NotTo(HaveOccurred())
_, err = ccpp.Parse(data)
Expect(err).To(MatchError("tar entry fake-code-package.link is not a regular file, type 50"))
})
})
Context("when the tar has a corrupt header entry", func() {
It("fails", func() {
data, err := ioutil.ReadFile("testdata/corrupted-header.tar.gz")
Expect(err).NotTo(HaveOccurred())
_, err = ccpp.Parse(data)
Expect(err).To(MatchError("error inspecting next tar header: flate: corrupt input before offset 86"))
})
})
Context("when the tar has too many entries", func() {
It("fails", func() {
data, err := ioutil.ReadFile("testdata/too-many-files.tar.gz")
Expect(err).NotTo(HaveOccurred())
_, err = ccpp.Parse(data)
Expect(err).To(MatchError("found too many files in archive, cannot identify which file is the code-package"))
})
})
Context("when the tar is missing a code-package", func() {
It("fails", func() {
data, err := ioutil.ReadFile("testdata/missing-codepackage.tar.gz")
Expect(err).NotTo(HaveOccurred())
_, err = ccpp.Parse(data)
Expect(err).To(MatchError("did not find a code package inside the package"))
})
})
})
})
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment