github.com/anjalikarhana/fabric@v2.1.1+incompatible/core/chaincode/platforms/platforms.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package platforms
     8  
     9  import (
    10  	"archive/tar"
    11  	"bytes"
    12  	"compress/gzip"
    13  	"fmt"
    14  	"io"
    15  	"strings"
    16  
    17  	docker "github.com/fsouza/go-dockerclient"
    18  	"github.com/hyperledger/fabric/common/flogging"
    19  	"github.com/hyperledger/fabric/common/metadata"
    20  	"github.com/hyperledger/fabric/core/chaincode/platforms/golang"
    21  	"github.com/hyperledger/fabric/core/chaincode/platforms/java"
    22  	"github.com/hyperledger/fabric/core/chaincode/platforms/node"
    23  	"github.com/hyperledger/fabric/core/chaincode/platforms/util"
    24  	"github.com/pkg/errors"
    25  )
    26  
    27  // SupportedPlatforms is the canonical list of platforms Fabric supports
    28  var SupportedPlatforms = []Platform{
    29  	&java.Platform{},
    30  	&golang.Platform{},
    31  	&node.Platform{},
    32  }
    33  
    34  // Interface for validating the specification and writing the package for
    35  // the given platform
    36  type Platform interface {
    37  	Name() string
    38  	GenerateDockerfile() (string, error)
    39  	DockerBuildOptions(path string) (util.DockerBuildOptions, error)
    40  }
    41  
    42  type PackageWriter interface {
    43  	Write(name string, payload []byte, tw *tar.Writer) error
    44  }
    45  
    46  type PackageWriterWrapper func(name string, payload []byte, tw *tar.Writer) error
    47  
    48  func (pw PackageWriterWrapper) Write(name string, payload []byte, tw *tar.Writer) error {
    49  	return pw(name, payload, tw)
    50  }
    51  
    52  type Registry struct {
    53  	Platforms     map[string]Platform
    54  	PackageWriter PackageWriter
    55  }
    56  
    57  var logger = flogging.MustGetLogger("chaincode.platform")
    58  
    59  func NewRegistry(platformTypes ...Platform) *Registry {
    60  	platforms := make(map[string]Platform)
    61  	for _, platform := range platformTypes {
    62  		if _, ok := platforms[platform.Name()]; ok {
    63  			logger.Panicf("Multiple platforms of the same name specified: %s", platform.Name())
    64  		}
    65  		platforms[platform.Name()] = platform
    66  	}
    67  	return &Registry{
    68  		Platforms:     platforms,
    69  		PackageWriter: PackageWriterWrapper(writeBytesToPackage),
    70  	}
    71  }
    72  
    73  func (r *Registry) GenerateDockerfile(ccType string) (string, error) {
    74  	platform, ok := r.Platforms[ccType]
    75  	if !ok {
    76  		return "", fmt.Errorf("Unknown chaincodeType: %s", ccType)
    77  	}
    78  
    79  	var buf []string
    80  
    81  	// ----------------------------------------------------------------------------------------------------
    82  	// Let the platform define the base Dockerfile
    83  	// ----------------------------------------------------------------------------------------------------
    84  	base, err := platform.GenerateDockerfile()
    85  	if err != nil {
    86  		return "", fmt.Errorf("Failed to generate platform-specific Dockerfile: %s", err)
    87  	}
    88  	buf = append(buf, base)
    89  	buf = append(buf, fmt.Sprintf(`LABEL %s.chaincode.type="%s" \`, metadata.BaseDockerLabel, ccType))
    90  	buf = append(buf, fmt.Sprintf(`      %s.version="%s"`, metadata.BaseDockerLabel, metadata.Version))
    91  	// ----------------------------------------------------------------------------------------------------
    92  	// Then augment it with any general options
    93  	// ----------------------------------------------------------------------------------------------------
    94  	//append version so chaincode build version can be compared against peer build version
    95  	buf = append(buf, fmt.Sprintf("ENV CORE_CHAINCODE_BUILDLEVEL=%s", metadata.Version))
    96  
    97  	// ----------------------------------------------------------------------------------------------------
    98  	// Finalize it
    99  	// ----------------------------------------------------------------------------------------------------
   100  	contents := strings.Join(buf, "\n")
   101  	logger.Debugf("\n%s", contents)
   102  
   103  	return contents, nil
   104  }
   105  
   106  func (r *Registry) StreamDockerBuild(ccType, path string, codePackage io.Reader, inputFiles map[string][]byte, tw *tar.Writer, client *docker.Client) error {
   107  	var err error
   108  
   109  	// ----------------------------------------------------------------------------------------------------
   110  	// Determine our platform driver from the spec
   111  	// ----------------------------------------------------------------------------------------------------
   112  	platform, ok := r.Platforms[ccType]
   113  	if !ok {
   114  		return fmt.Errorf("could not find platform of type: %s", ccType)
   115  	}
   116  
   117  	// ----------------------------------------------------------------------------------------------------
   118  	// First stream out our static inputFiles
   119  	// ----------------------------------------------------------------------------------------------------
   120  	for name, data := range inputFiles {
   121  		err = r.PackageWriter.Write(name, data, tw)
   122  		if err != nil {
   123  			return fmt.Errorf(`Failed to inject "%s": %s`, name, err)
   124  		}
   125  	}
   126  
   127  	buildOptions, err := platform.DockerBuildOptions(path)
   128  	if err != nil {
   129  		return errors.Wrap(err, "platform failed to create docker build options")
   130  	}
   131  
   132  	output := &bytes.Buffer{}
   133  	buildOptions.InputStream = codePackage
   134  	buildOptions.OutputStream = output
   135  
   136  	err = util.DockerBuild(buildOptions, client)
   137  	if err != nil {
   138  		return errors.Wrap(err, "docker build failed")
   139  	}
   140  
   141  	return writeBytesToPackage("binpackage.tar", output.Bytes(), tw)
   142  }
   143  
   144  func writeBytesToPackage(name string, payload []byte, tw *tar.Writer) error {
   145  	err := tw.WriteHeader(&tar.Header{
   146  		Name: name,
   147  		Size: int64(len(payload)),
   148  		Mode: 0100644,
   149  	})
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	_, err = tw.Write(payload)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	return nil
   160  }
   161  
   162  func (r *Registry) GenerateDockerBuild(ccType, path string, codePackage io.Reader, client *docker.Client) (io.Reader, error) {
   163  	inputFiles := make(map[string][]byte)
   164  
   165  	// ----------------------------------------------------------------------------------------------------
   166  	// Generate the Dockerfile specific to our context
   167  	// ----------------------------------------------------------------------------------------------------
   168  	dockerFile, err := r.GenerateDockerfile(ccType)
   169  	if err != nil {
   170  		return nil, fmt.Errorf("Failed to generate a Dockerfile: %s", err)
   171  	}
   172  
   173  	inputFiles["Dockerfile"] = []byte(dockerFile)
   174  
   175  	// ----------------------------------------------------------------------------------------------------
   176  	// Finally, launch an asynchronous process to stream all of the above into a docker build context
   177  	// ----------------------------------------------------------------------------------------------------
   178  	input, output := io.Pipe()
   179  
   180  	go func() {
   181  		gw := gzip.NewWriter(output)
   182  		tw := tar.NewWriter(gw)
   183  		err := r.StreamDockerBuild(ccType, path, codePackage, inputFiles, tw, client)
   184  		if err != nil {
   185  			logger.Error(err)
   186  		}
   187  
   188  		tw.Close()
   189  		gw.Close()
   190  		output.CloseWithError(err)
   191  	}()
   192  
   193  	return input, nil
   194  }