github.com/yacovm/fabric@v2.0.0-alpha.0.20191128145320-c5d4087dc723+incompatible/core/chaincode/persistence/chaincode_package.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package persistence
     8  
     9  import (
    10  	"archive/tar"
    11  	"bytes"
    12  	"compress/gzip"
    13  	"encoding/json"
    14  	"io"
    15  	"io/ioutil"
    16  	"os"
    17  	"path/filepath"
    18  	"regexp"
    19  
    20  	pb "github.com/hyperledger/fabric-protos-go/peer"
    21  
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  // The chaincode package is simply a .tar.gz file.  For the time being, we
    26  // assume that the package contains a Chaincode-Package-Metadata.json file
    27  // which contains a 'Type', and optionally a 'Path'.  In the future, it would
    28  // be nice if we moved to a more buildpack type system, rather than the below
    29  // presented JAR+manifest type system, but for expediency and incremental changes,
    30  // moving to a tar format over the proto format for a user-inspectable artifact
    31  // seems like a good step.
    32  
    33  const (
    34  	// MetadataFile is the expected location of the metadata json document
    35  	// in the top level of the chaincode package.
    36  	MetadataFile = "metadata.json"
    37  
    38  	// CodePackageFile is the expected location of the code package in the
    39  	// top level of the chaincode package
    40  	CodePackageFile = "code.tar.gz"
    41  )
    42  
    43  //go:generate counterfeiter -o mock/legacy_cc_package_locator.go --fake-name LegacyCCPackageLocator . LegacyCCPackageLocator
    44  
    45  type LegacyCCPackageLocator interface {
    46  	GetChaincodeDepSpec(nameVersion string) (*pb.ChaincodeDeploymentSpec, error)
    47  }
    48  
    49  type FallbackPackageLocator struct {
    50  	ChaincodePackageLocator *ChaincodePackageLocator
    51  	LegacyCCPackageLocator  LegacyCCPackageLocator
    52  }
    53  
    54  func (fpl *FallbackPackageLocator) GetChaincodePackage(packageID string) (*ChaincodePackageMetadata, io.ReadCloser, error) {
    55  	streamer := fpl.ChaincodePackageLocator.ChaincodePackageStreamer(packageID)
    56  	if streamer.Exists() {
    57  		metadata, err := streamer.Metadata()
    58  		if err != nil {
    59  			return nil, nil, errors.WithMessagef(err, "error retrieving chaincode package metadata '%s'", packageID)
    60  		}
    61  
    62  		tarStream, err := streamer.Code()
    63  		if err != nil {
    64  			return nil, nil, errors.WithMessagef(err, "error retrieving chaincode package code '%s'", packageID)
    65  		}
    66  
    67  		return metadata, tarStream, nil
    68  	}
    69  
    70  	cds, err := fpl.LegacyCCPackageLocator.GetChaincodeDepSpec(string(packageID))
    71  	if err != nil {
    72  		return nil, nil, errors.WithMessagef(err, "could not get legacy chaincode package '%s'", packageID)
    73  	}
    74  
    75  	return &ChaincodePackageMetadata{
    76  			Path: cds.ChaincodeSpec.ChaincodeId.Path,
    77  			Type: cds.ChaincodeSpec.Type.String(),
    78  		},
    79  		ioutil.NopCloser(bytes.NewBuffer(cds.CodePackage)),
    80  		nil
    81  }
    82  
    83  type ChaincodePackageLocator struct {
    84  	ChaincodeDir string
    85  }
    86  
    87  func (cpl *ChaincodePackageLocator) ChaincodePackageStreamer(packageID string) *ChaincodePackageStreamer {
    88  	return &ChaincodePackageStreamer{
    89  		PackagePath: filepath.Join(cpl.ChaincodeDir, CCFileName(packageID)),
    90  	}
    91  }
    92  
    93  type ChaincodePackageStreamer struct {
    94  	PackagePath string
    95  }
    96  
    97  func (cps *ChaincodePackageStreamer) Exists() bool {
    98  	_, err := os.Stat(cps.PackagePath)
    99  	return err == nil
   100  }
   101  
   102  func (cps *ChaincodePackageStreamer) Metadata() (*ChaincodePackageMetadata, error) {
   103  	tarFileStream, err := cps.File(MetadataFile)
   104  	if err != nil {
   105  		return nil, errors.WithMessage(err, "could not get metadata file")
   106  	}
   107  
   108  	defer tarFileStream.Close()
   109  
   110  	metadata := &ChaincodePackageMetadata{}
   111  	err = json.NewDecoder(tarFileStream).Decode(metadata)
   112  	if err != nil {
   113  		return nil, errors.WithMessage(err, "could not parse metadata file")
   114  	}
   115  
   116  	return metadata, nil
   117  }
   118  
   119  func (cps *ChaincodePackageStreamer) Code() (*TarFileStream, error) {
   120  	tarFileStream, err := cps.File(CodePackageFile)
   121  	if err != nil {
   122  		return nil, errors.WithMessage(err, "could not get code package")
   123  	}
   124  
   125  	return tarFileStream, nil
   126  }
   127  
   128  func (cps *ChaincodePackageStreamer) File(name string) (tarFileStream *TarFileStream, err error) {
   129  	file, err := os.Open(cps.PackagePath)
   130  	if err != nil {
   131  		return nil, errors.WithMessagef(err, "could not open chaincode package at '%s'", cps.PackagePath)
   132  	}
   133  
   134  	defer func() {
   135  		if err != nil {
   136  			file.Close()
   137  		}
   138  	}()
   139  
   140  	gzReader, err := gzip.NewReader(file)
   141  	if err != nil {
   142  		return nil, errors.Wrapf(err, "error reading as gzip stream")
   143  	}
   144  
   145  	tarReader := tar.NewReader(gzReader)
   146  
   147  	for {
   148  		header, err := tarReader.Next()
   149  		if err == io.EOF {
   150  			break
   151  		}
   152  
   153  		if err != nil {
   154  			return nil, errors.Wrapf(err, "error inspecting next tar header")
   155  		}
   156  
   157  		if header.Name != name {
   158  			continue
   159  		}
   160  
   161  		if header.Typeflag != tar.TypeReg {
   162  			return nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag)
   163  		}
   164  
   165  		return &TarFileStream{
   166  			TarFile:    tarReader,
   167  			FileStream: file,
   168  		}, nil
   169  	}
   170  
   171  	return nil, errors.Errorf("did not find file '%s' in package", name)
   172  }
   173  
   174  type TarFileStream struct {
   175  	TarFile    io.Reader
   176  	FileStream io.Closer
   177  }
   178  
   179  func (tfs *TarFileStream) Read(p []byte) (int, error) {
   180  	return tfs.TarFile.Read(p)
   181  }
   182  
   183  func (tfs *TarFileStream) Close() error {
   184  	return tfs.FileStream.Close()
   185  }
   186  
   187  // ChaincodePackage represents the un-tar-ed format of the chaincode package.
   188  type ChaincodePackage struct {
   189  	Metadata    *ChaincodePackageMetadata
   190  	CodePackage []byte
   191  	DBArtifacts []byte
   192  }
   193  
   194  // ChaincodePackageMetadata contains the information necessary to understand
   195  // the embedded code package.
   196  type ChaincodePackageMetadata struct {
   197  	Type  string `json:"type"`
   198  	Path  string `json:"path"`
   199  	Label string `json:"label"`
   200  }
   201  
   202  // MetadataProvider provides the means to retrieve metadata
   203  // information (for instance the DB indexes) from a code package.
   204  type MetadataProvider interface {
   205  	GetDBArtifacts(codePackage []byte) ([]byte, error)
   206  }
   207  
   208  // ChaincodePackageParser provides the ability to parse chaincode packages.
   209  type ChaincodePackageParser struct {
   210  	MetadataProvider MetadataProvider
   211  }
   212  
   213  var (
   214  	// LabelRegexp is the regular expression controlling
   215  	// the allowed characters for the package label
   216  	LabelRegexp = regexp.MustCompile("^[a-zA-Z0-9]+([.+-_][a-zA-Z0-9]+)*$")
   217  )
   218  
   219  func validateLabel(label string) error {
   220  	if !LabelRegexp.MatchString(label) {
   221  		return errors.Errorf("invalid label '%s'. Label must be non-empty, can only consist of alphanumerics, symbols from '.+-_', and can only begin with alphanumerics", label)
   222  	}
   223  
   224  	return nil
   225  }
   226  
   227  // Parse parses a set of bytes as a chaincode package
   228  // and returns the parsed package as a struct
   229  func (ccpp ChaincodePackageParser) Parse(source []byte) (*ChaincodePackage, error) {
   230  	gzReader, err := gzip.NewReader(bytes.NewBuffer(source))
   231  	if err != nil {
   232  		return nil, errors.Wrapf(err, "error reading as gzip stream")
   233  	}
   234  
   235  	tarReader := tar.NewReader(gzReader)
   236  
   237  	var codePackage []byte
   238  	var ccPackageMetadata *ChaincodePackageMetadata
   239  	for {
   240  		header, err := tarReader.Next()
   241  		if err == io.EOF {
   242  			break
   243  		}
   244  
   245  		if err != nil {
   246  			return nil, errors.Wrapf(err, "error inspecting next tar header")
   247  		}
   248  
   249  		if header.Typeflag != tar.TypeReg {
   250  			return nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag)
   251  		}
   252  
   253  		fileBytes, err := ioutil.ReadAll(tarReader)
   254  		if err != nil {
   255  			return nil, errors.Wrapf(err, "could not read %s from tar", header.Name)
   256  		}
   257  
   258  		switch header.Name {
   259  
   260  		case MetadataFile:
   261  			ccPackageMetadata = &ChaincodePackageMetadata{}
   262  			err := json.Unmarshal(fileBytes, ccPackageMetadata)
   263  			if err != nil {
   264  				return nil, errors.Wrapf(err, "could not unmarshal %s as json", MetadataFile)
   265  			}
   266  
   267  		case CodePackageFile:
   268  			codePackage = fileBytes
   269  		default:
   270  			logger.Warningf("Encountered unexpected file '%s' in top level of chaincode package", header.Name)
   271  		}
   272  	}
   273  
   274  	if codePackage == nil {
   275  		return nil, errors.Errorf("did not find a code package inside the package")
   276  	}
   277  
   278  	if ccPackageMetadata == nil {
   279  		return nil, errors.Errorf("did not find any package metadata (missing %s)", MetadataFile)
   280  	}
   281  
   282  	if err := validateLabel(ccPackageMetadata.Label); err != nil {
   283  		return nil, err
   284  	}
   285  
   286  	dbArtifacts, err := ccpp.MetadataProvider.GetDBArtifacts(codePackage)
   287  	if err != nil {
   288  		return nil, errors.WithMessage(err, "error retrieving DB artifacts from code package")
   289  	}
   290  
   291  	return &ChaincodePackage{
   292  		Metadata:    ccPackageMetadata,
   293  		CodePackage: codePackage,
   294  		DBArtifacts: dbArtifacts,
   295  	}, nil
   296  }