github.com/anjalikarhana/fabric@v2.1.1+incompatible/internal/peer/lifecycle/chaincode/package.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package chaincode
     8  
     9  import (
    10  	"archive/tar"
    11  	"bytes"
    12  	"compress/gzip"
    13  	"encoding/json"
    14  	"path/filepath"
    15  	"strings"
    16  
    17  	"github.com/hyperledger/fabric/core/chaincode/persistence"
    18  	"github.com/hyperledger/fabric/internal/peer/packaging"
    19  	"github.com/pkg/errors"
    20  	"github.com/spf13/cobra"
    21  )
    22  
    23  // PlatformRegistry defines the interface to get the code bytes
    24  // for a chaincode given the type and path
    25  type PlatformRegistry interface {
    26  	GetDeploymentPayload(ccType, path string) ([]byte, error)
    27  	NormalizePath(ccType, path string) (string, error)
    28  }
    29  
    30  // Packager holds the dependencies needed to package
    31  // a chaincode and write it
    32  type Packager struct {
    33  	Command          *cobra.Command
    34  	Input            *PackageInput
    35  	PlatformRegistry PlatformRegistry
    36  	Writer           Writer
    37  }
    38  
    39  // PackageInput holds the input parameters for packaging a
    40  // ChaincodeInstallPackage
    41  type PackageInput struct {
    42  	OutputFile string
    43  	Path       string
    44  	Type       string
    45  	Label      string
    46  }
    47  
    48  // Validate checks for the required inputs
    49  func (p *PackageInput) Validate() error {
    50  	if p.Path == "" {
    51  		return errors.New("chaincode path must be specified")
    52  	}
    53  	if p.Type == "" {
    54  		return errors.New("chaincode language must be specified")
    55  	}
    56  	if p.OutputFile == "" {
    57  		return errors.New("output file must be specified")
    58  	}
    59  	if p.Label == "" {
    60  		return errors.New("package label must be specified")
    61  	}
    62  	if err := persistence.ValidateLabel(p.Label); err != nil {
    63  		return err
    64  	}
    65  
    66  	return nil
    67  }
    68  
    69  // PackageCmd returns the cobra command for packaging chaincode
    70  func PackageCmd(p *Packager) *cobra.Command {
    71  	chaincodePackageCmd := &cobra.Command{
    72  		Use:       "package [outputfile]",
    73  		Short:     "Package a chaincode",
    74  		Long:      "Package a chaincode and write the package to a file.",
    75  		ValidArgs: []string{"1"},
    76  		RunE: func(cmd *cobra.Command, args []string) error {
    77  			if p == nil {
    78  				pr := packaging.NewRegistry(packaging.SupportedPlatforms...)
    79  
    80  				p = &Packager{
    81  					PlatformRegistry: pr,
    82  					Writer:           &persistence.FilesystemIO{},
    83  				}
    84  			}
    85  			p.Command = cmd
    86  
    87  			return p.PackageChaincode(args)
    88  		},
    89  	}
    90  	flagList := []string{
    91  		"label",
    92  		"lang",
    93  		"path",
    94  		"peerAddresses",
    95  		"tlsRootCertFiles",
    96  		"connectionProfile",
    97  	}
    98  	attachFlags(chaincodePackageCmd, flagList)
    99  
   100  	return chaincodePackageCmd
   101  }
   102  
   103  // PackageChaincode packages a chaincode.
   104  func (p *Packager) PackageChaincode(args []string) error {
   105  	if p.Command != nil {
   106  		// Parsing of the command line is done so silence cmd usage
   107  		p.Command.SilenceUsage = true
   108  	}
   109  
   110  	if len(args) != 1 {
   111  		return errors.New("invalid number of args. expected only the output file")
   112  	}
   113  	p.setInput(args[0])
   114  
   115  	return p.Package()
   116  }
   117  
   118  func (p *Packager) setInput(outputFile string) {
   119  	p.Input = &PackageInput{
   120  		OutputFile: outputFile,
   121  		Path:       chaincodePath,
   122  		Type:       chaincodeLang,
   123  		Label:      packageLabel,
   124  	}
   125  }
   126  
   127  // Package packages chaincodes into the package type,
   128  // (.tar.gz) used by _lifecycle and writes it to disk
   129  func (p *Packager) Package() error {
   130  	err := p.Input.Validate()
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	pkgTarGzBytes, err := p.getTarGzBytes()
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	dir, name := filepath.Split(p.Input.OutputFile)
   141  	// if p.Input.OutputFile is only file name, dir becomes an empty string that creates problem
   142  	// while invoking 'WriteFile' function below. So, irrespective, translate dir into absolute path
   143  	if dir, err = filepath.Abs(dir); err != nil {
   144  		return err
   145  	}
   146  	err = p.Writer.WriteFile(dir, name, pkgTarGzBytes)
   147  	if err != nil {
   148  		err = errors.Wrapf(err, "error writing chaincode package to %s", p.Input.OutputFile)
   149  		logger.Error(err.Error())
   150  		return err
   151  	}
   152  
   153  	return nil
   154  }
   155  
   156  func (p *Packager) getTarGzBytes() ([]byte, error) {
   157  	payload := bytes.NewBuffer(nil)
   158  	gw := gzip.NewWriter(payload)
   159  	tw := tar.NewWriter(gw)
   160  
   161  	normalizedPath, err := p.PlatformRegistry.NormalizePath(strings.ToUpper(p.Input.Type), p.Input.Path)
   162  	if err != nil {
   163  		return nil, errors.WithMessage(err, "failed to normalize chaincode path")
   164  	}
   165  	metadataBytes, err := toJSON(normalizedPath, p.Input.Type, p.Input.Label)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	err = writeBytesToPackage(tw, "metadata.json", metadataBytes)
   170  	if err != nil {
   171  		return nil, errors.Wrap(err, "error writing package metadata to tar")
   172  	}
   173  
   174  	codeBytes, err := p.PlatformRegistry.GetDeploymentPayload(strings.ToUpper(p.Input.Type), p.Input.Path)
   175  	if err != nil {
   176  		return nil, errors.WithMessage(err, "error getting chaincode bytes")
   177  	}
   178  
   179  	codePackageName := "code.tar.gz"
   180  
   181  	err = writeBytesToPackage(tw, codePackageName, codeBytes)
   182  	if err != nil {
   183  		return nil, errors.Wrap(err, "error writing package code bytes to tar")
   184  	}
   185  
   186  	err = tw.Close()
   187  	if err == nil {
   188  		err = gw.Close()
   189  	}
   190  	if err != nil {
   191  		return nil, errors.Wrapf(err, "failed to create tar for chaincode")
   192  	}
   193  
   194  	return payload.Bytes(), nil
   195  }
   196  
   197  func writeBytesToPackage(tw *tar.Writer, name string, payload []byte) error {
   198  	err := tw.WriteHeader(&tar.Header{
   199  		Name: name,
   200  		Size: int64(len(payload)),
   201  		Mode: 0100644,
   202  	})
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	_, err = tw.Write(payload)
   208  	if err != nil {
   209  		return err
   210  	}
   211  
   212  	return nil
   213  }
   214  
   215  // PackageMetadata holds the path and type for a chaincode package
   216  type PackageMetadata struct {
   217  	Path  string `json:"path"`
   218  	Type  string `json:"type"`
   219  	Label string `json:"label"`
   220  }
   221  
   222  func toJSON(path, ccType, label string) ([]byte, error) {
   223  	metadata := &PackageMetadata{
   224  		Path:  path,
   225  		Type:  ccType,
   226  		Label: label,
   227  	}
   228  
   229  	metadataBytes, err := json.Marshal(metadata)
   230  	if err != nil {
   231  		return nil, errors.Wrap(err, "failed to marshal chaincode package metadata into JSON")
   232  	}
   233  
   234  	return metadataBytes, nil
   235  }