github.com/renegr87/renegr87@v2.1.1+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 metadata.json file which contains a
    27  // 'type', a 'path', and a 'label'.  In the future, it would be nice if we
    28  // move to a more buildpack type system, rather than the below presented
    29  // JAR+manifest type system, but for expediency and incremental changes,
    30  // moving to a tar format over the proto format for a user-inspectable
    31  // artifact 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, []byte, io.ReadCloser, error) {
    55  	// XXX, this path has too many return parameters.  We could split it into two calls,
    56  	// or, we could deserialize the metadata where it's needed.  But, as written was the
    57  	// fastest path to fixing a bug around the mutation of metadata.
    58  	streamer := fpl.ChaincodePackageLocator.ChaincodePackageStreamer(packageID)
    59  	if streamer.Exists() {
    60  		metadata, err := streamer.Metadata()
    61  		if err != nil {
    62  			return nil, nil, nil, errors.WithMessagef(err, "error retrieving chaincode package metadata '%s'", packageID)
    63  		}
    64  
    65  		mdBytes, err := streamer.MetadataBytes()
    66  		if err != nil {
    67  			return nil, nil, nil, errors.WithMessagef(err, "error retrieving chaincode package metadata bytes '%s'", packageID)
    68  		}
    69  
    70  		tarStream, err := streamer.Code()
    71  		if err != nil {
    72  			return nil, nil, nil, errors.WithMessagef(err, "error retrieving chaincode package code '%s'", packageID)
    73  		}
    74  
    75  		return metadata, mdBytes, tarStream, nil
    76  	}
    77  
    78  	cds, err := fpl.LegacyCCPackageLocator.GetChaincodeDepSpec(string(packageID))
    79  	if err != nil {
    80  		return nil, nil, nil, errors.WithMessagef(err, "could not get legacy chaincode package '%s'", packageID)
    81  	}
    82  
    83  	md := &ChaincodePackageMetadata{
    84  		Path:  cds.ChaincodeSpec.ChaincodeId.Path,
    85  		Type:  cds.ChaincodeSpec.Type.String(),
    86  		Label: cds.ChaincodeSpec.ChaincodeId.Name,
    87  	}
    88  
    89  	mdBytes, err := json.Marshal(md)
    90  
    91  	return md,
    92  		mdBytes,
    93  		ioutil.NopCloser(bytes.NewBuffer(cds.CodePackage)),
    94  		nil
    95  }
    96  
    97  type ChaincodePackageLocator struct {
    98  	ChaincodeDir string
    99  }
   100  
   101  func (cpl *ChaincodePackageLocator) ChaincodePackageStreamer(packageID string) *ChaincodePackageStreamer {
   102  	return &ChaincodePackageStreamer{
   103  		PackagePath: filepath.Join(cpl.ChaincodeDir, CCFileName(packageID)),
   104  	}
   105  }
   106  
   107  type ChaincodePackageStreamer struct {
   108  	PackagePath string
   109  }
   110  
   111  func (cps *ChaincodePackageStreamer) Exists() bool {
   112  	_, err := os.Stat(cps.PackagePath)
   113  	return err == nil
   114  }
   115  
   116  func (cps *ChaincodePackageStreamer) Metadata() (*ChaincodePackageMetadata, error) {
   117  	tarFileStream, err := cps.File(MetadataFile)
   118  	if err != nil {
   119  		return nil, errors.WithMessage(err, "could not get metadata file")
   120  	}
   121  
   122  	defer tarFileStream.Close()
   123  
   124  	metadata := &ChaincodePackageMetadata{}
   125  	err = json.NewDecoder(tarFileStream).Decode(metadata)
   126  	if err != nil {
   127  		return nil, errors.WithMessage(err, "could not parse metadata file")
   128  	}
   129  
   130  	return metadata, nil
   131  }
   132  
   133  func (cps *ChaincodePackageStreamer) MetadataBytes() ([]byte, error) {
   134  	tarFileStream, err := cps.File(MetadataFile)
   135  	if err != nil {
   136  		return nil, errors.WithMessage(err, "could not get metadata file")
   137  	}
   138  
   139  	defer tarFileStream.Close()
   140  
   141  	md, err := ioutil.ReadAll(tarFileStream)
   142  	if err != nil {
   143  		return nil, errors.WithMessage(err, "could read metadata file")
   144  	}
   145  
   146  	return md, nil
   147  }
   148  
   149  func (cps *ChaincodePackageStreamer) Code() (*TarFileStream, error) {
   150  	tarFileStream, err := cps.File(CodePackageFile)
   151  	if err != nil {
   152  		return nil, errors.WithMessage(err, "could not get code package")
   153  	}
   154  
   155  	return tarFileStream, nil
   156  }
   157  
   158  func (cps *ChaincodePackageStreamer) File(name string) (tarFileStream *TarFileStream, err error) {
   159  	file, err := os.Open(cps.PackagePath)
   160  	if err != nil {
   161  		return nil, errors.WithMessagef(err, "could not open chaincode package at '%s'", cps.PackagePath)
   162  	}
   163  
   164  	defer func() {
   165  		if err != nil {
   166  			file.Close()
   167  		}
   168  	}()
   169  
   170  	gzReader, err := gzip.NewReader(file)
   171  	if err != nil {
   172  		return nil, errors.Wrapf(err, "error reading as gzip stream")
   173  	}
   174  
   175  	tarReader := tar.NewReader(gzReader)
   176  
   177  	for {
   178  		header, err := tarReader.Next()
   179  		if err == io.EOF {
   180  			break
   181  		}
   182  
   183  		if err != nil {
   184  			return nil, errors.Wrapf(err, "error inspecting next tar header")
   185  		}
   186  
   187  		if header.Name != name {
   188  			continue
   189  		}
   190  
   191  		if header.Typeflag != tar.TypeReg {
   192  			return nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag)
   193  		}
   194  
   195  		return &TarFileStream{
   196  			TarFile:    tarReader,
   197  			FileStream: file,
   198  		}, nil
   199  	}
   200  
   201  	return nil, errors.Errorf("did not find file '%s' in package", name)
   202  }
   203  
   204  type TarFileStream struct {
   205  	TarFile    io.Reader
   206  	FileStream io.Closer
   207  }
   208  
   209  func (tfs *TarFileStream) Read(p []byte) (int, error) {
   210  	return tfs.TarFile.Read(p)
   211  }
   212  
   213  func (tfs *TarFileStream) Close() error {
   214  	return tfs.FileStream.Close()
   215  }
   216  
   217  // ChaincodePackage represents the un-tar-ed format of the chaincode package.
   218  type ChaincodePackage struct {
   219  	Metadata    *ChaincodePackageMetadata
   220  	CodePackage []byte
   221  	DBArtifacts []byte
   222  }
   223  
   224  // ChaincodePackageMetadata contains the information necessary to understand
   225  // the embedded code package.
   226  type ChaincodePackageMetadata struct {
   227  	Type  string `json:"type"`
   228  	Path  string `json:"path"`
   229  	Label string `json:"label"`
   230  }
   231  
   232  // MetadataProvider provides the means to retrieve metadata
   233  // information (for instance the DB indexes) from a code package.
   234  type MetadataProvider interface {
   235  	GetDBArtifacts(codePackage []byte) ([]byte, error)
   236  }
   237  
   238  // ChaincodePackageParser provides the ability to parse chaincode packages.
   239  type ChaincodePackageParser struct {
   240  	MetadataProvider MetadataProvider
   241  }
   242  
   243  // LabelRegexp is the regular expression controlling the allowed characters
   244  // for the package label.
   245  var LabelRegexp = regexp.MustCompile(`^[[:alnum:]][[:alnum:]_.+-]*$`)
   246  
   247  // ValidateLabel return an error if the provided label contains any invalid
   248  // characters, as determined by LabelRegexp.
   249  func ValidateLabel(label string) error {
   250  	if !LabelRegexp.MatchString(label) {
   251  		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)
   252  	}
   253  
   254  	return nil
   255  }
   256  
   257  // Parse parses a set of bytes as a chaincode package
   258  // and returns the parsed package as a struct
   259  func (ccpp ChaincodePackageParser) Parse(source []byte) (*ChaincodePackage, error) {
   260  	gzReader, err := gzip.NewReader(bytes.NewBuffer(source))
   261  	if err != nil {
   262  		return nil, errors.Wrapf(err, "error reading as gzip stream")
   263  	}
   264  
   265  	tarReader := tar.NewReader(gzReader)
   266  
   267  	var codePackage []byte
   268  	var ccPackageMetadata *ChaincodePackageMetadata
   269  	for {
   270  		header, err := tarReader.Next()
   271  		if err == io.EOF {
   272  			break
   273  		}
   274  
   275  		if err != nil {
   276  			return nil, errors.Wrapf(err, "error inspecting next tar header")
   277  		}
   278  
   279  		if header.Typeflag != tar.TypeReg {
   280  			return nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag)
   281  		}
   282  
   283  		fileBytes, err := ioutil.ReadAll(tarReader)
   284  		if err != nil {
   285  			return nil, errors.Wrapf(err, "could not read %s from tar", header.Name)
   286  		}
   287  
   288  		switch header.Name {
   289  
   290  		case MetadataFile:
   291  			ccPackageMetadata = &ChaincodePackageMetadata{}
   292  			err := json.Unmarshal(fileBytes, ccPackageMetadata)
   293  			if err != nil {
   294  				return nil, errors.Wrapf(err, "could not unmarshal %s as json", MetadataFile)
   295  			}
   296  
   297  		case CodePackageFile:
   298  			codePackage = fileBytes
   299  		default:
   300  			logger.Warningf("Encountered unexpected file '%s' in top level of chaincode package", header.Name)
   301  		}
   302  	}
   303  
   304  	if codePackage == nil {
   305  		return nil, errors.Errorf("did not find a code package inside the package")
   306  	}
   307  
   308  	if ccPackageMetadata == nil {
   309  		return nil, errors.Errorf("did not find any package metadata (missing %s)", MetadataFile)
   310  	}
   311  
   312  	if err := ValidateLabel(ccPackageMetadata.Label); err != nil {
   313  		return nil, err
   314  	}
   315  
   316  	dbArtifacts, err := ccpp.MetadataProvider.GetDBArtifacts(codePackage)
   317  	if err != nil {
   318  		return nil, errors.WithMessage(err, "error retrieving DB artifacts from code package")
   319  	}
   320  
   321  	return &ChaincodePackage{
   322  		Metadata:    ccPackageMetadata,
   323  		CodePackage: codePackage,
   324  		DBArtifacts: dbArtifacts,
   325  	}, nil
   326  }