236 lines
5.6 KiB
Go
236 lines
5.6 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package chaincode
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/json"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/hyperledger/fabric/core/chaincode/persistence"
|
|
"github.com/hyperledger/fabric/internal/peer/packaging"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// PlatformRegistry defines the interface to get the code bytes
|
|
// for a chaincode given the type and path
|
|
type PlatformRegistry interface {
|
|
GetDeploymentPayload(ccType, path string) ([]byte, error)
|
|
NormalizePath(ccType, path string) (string, error)
|
|
}
|
|
|
|
// Packager holds the dependencies needed to package
|
|
// a chaincode and write it
|
|
type Packager struct {
|
|
Command *cobra.Command
|
|
Input *PackageInput
|
|
PlatformRegistry PlatformRegistry
|
|
Writer Writer
|
|
}
|
|
|
|
// PackageInput holds the input parameters for packaging a
|
|
// ChaincodeInstallPackage
|
|
type PackageInput struct {
|
|
OutputFile string
|
|
Path string
|
|
Type string
|
|
Label string
|
|
}
|
|
|
|
// Validate checks for the required inputs
|
|
func (p *PackageInput) Validate() error {
|
|
if p.Path == "" {
|
|
return errors.New("chaincode path must be specified")
|
|
}
|
|
if p.Type == "" {
|
|
return errors.New("chaincode language must be specified")
|
|
}
|
|
if p.OutputFile == "" {
|
|
return errors.New("output file must be specified")
|
|
}
|
|
if p.Label == "" {
|
|
return errors.New("package label must be specified")
|
|
}
|
|
if err := persistence.ValidateLabel(p.Label); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PackageCmd returns the cobra command for packaging chaincode
|
|
func PackageCmd(p *Packager) *cobra.Command {
|
|
chaincodePackageCmd := &cobra.Command{
|
|
Use: "package [outputfile]",
|
|
Short: "Package a chaincode",
|
|
Long: "Package a chaincode and write the package to a file.",
|
|
ValidArgs: []string{"1"},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if p == nil {
|
|
pr := packaging.NewRegistry(packaging.SupportedPlatforms...)
|
|
|
|
p = &Packager{
|
|
PlatformRegistry: pr,
|
|
Writer: &persistence.FilesystemIO{},
|
|
}
|
|
}
|
|
p.Command = cmd
|
|
|
|
return p.PackageChaincode(args)
|
|
},
|
|
}
|
|
flagList := []string{
|
|
"label",
|
|
"lang",
|
|
"path",
|
|
"peerAddresses",
|
|
"tlsRootCertFiles",
|
|
"connectionProfile",
|
|
}
|
|
attachFlags(chaincodePackageCmd, flagList)
|
|
|
|
return chaincodePackageCmd
|
|
}
|
|
|
|
// PackageChaincode packages a chaincode.
|
|
func (p *Packager) PackageChaincode(args []string) error {
|
|
if p.Command != nil {
|
|
// Parsing of the command line is done so silence cmd usage
|
|
p.Command.SilenceUsage = true
|
|
}
|
|
|
|
if len(args) != 1 {
|
|
return errors.New("invalid number of args. expected only the output file")
|
|
}
|
|
p.setInput(args[0])
|
|
|
|
return p.Package()
|
|
}
|
|
|
|
func (p *Packager) setInput(outputFile string) {
|
|
p.Input = &PackageInput{
|
|
OutputFile: outputFile,
|
|
Path: chaincodePath,
|
|
Type: chaincodeLang,
|
|
Label: packageLabel,
|
|
}
|
|
}
|
|
|
|
// Package packages chaincodes into the package type,
|
|
// (.tar.gz) used by _lifecycle and writes it to disk
|
|
func (p *Packager) Package() error {
|
|
err := p.Input.Validate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pkgTarGzBytes, err := p.getTarGzBytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dir, name := filepath.Split(p.Input.OutputFile)
|
|
// if p.Input.OutputFile is only file name, dir becomes an empty string that creates problem
|
|
// while invoking 'WriteFile' function below. So, irrespective, translate dir into absolute path
|
|
if dir, err = filepath.Abs(dir); err != nil {
|
|
return err
|
|
}
|
|
err = p.Writer.WriteFile(dir, name, pkgTarGzBytes)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "error writing chaincode package to %s", p.Input.OutputFile)
|
|
logger.Error(err.Error())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Packager) getTarGzBytes() ([]byte, error) {
|
|
payload := bytes.NewBuffer(nil)
|
|
gw := gzip.NewWriter(payload)
|
|
tw := tar.NewWriter(gw)
|
|
|
|
normalizedPath, err := p.PlatformRegistry.NormalizePath(strings.ToUpper(p.Input.Type), p.Input.Path)
|
|
if err != nil {
|
|
return nil, errors.WithMessage(err, "failed to normalize chaincode path")
|
|
}
|
|
metadataBytes, err := toJSON(normalizedPath, p.Input.Type, p.Input.Label)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = writeBytesToPackage(tw, "metadata.json", metadataBytes)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error writing package metadata to tar")
|
|
}
|
|
|
|
codeBytes, err := p.PlatformRegistry.GetDeploymentPayload(strings.ToUpper(p.Input.Type), p.Input.Path)
|
|
if err != nil {
|
|
return nil, errors.WithMessage(err, "error getting chaincode bytes")
|
|
}
|
|
|
|
codePackageName := "code.tar.gz"
|
|
|
|
err = writeBytesToPackage(tw, codePackageName, codeBytes)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error writing package code bytes to tar")
|
|
}
|
|
|
|
err = tw.Close()
|
|
if err == nil {
|
|
err = gw.Close()
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to create tar for chaincode")
|
|
}
|
|
|
|
return payload.Bytes(), nil
|
|
}
|
|
|
|
func writeBytesToPackage(tw *tar.Writer, name string, payload []byte) error {
|
|
err := tw.WriteHeader(&tar.Header{
|
|
Name: name,
|
|
Size: int64(len(payload)),
|
|
Mode: 0o100644,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = tw.Write(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PackageMetadata holds the path and type for a chaincode package
|
|
type PackageMetadata struct {
|
|
Path string `json:"path"`
|
|
Type string `json:"type"`
|
|
Label string `json:"label"`
|
|
}
|
|
|
|
func toJSON(path, ccType, label string) ([]byte, error) {
|
|
metadata := &PackageMetadata{
|
|
Path: path,
|
|
Type: ccType,
|
|
Label: label,
|
|
}
|
|
|
|
metadataBytes, err := json.Marshal(metadata)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to marshal chaincode package metadata into JSON")
|
|
}
|
|
|
|
return metadataBytes, nil
|
|
}
|