github.com/leonlxy/hyperledger@v1.0.0-alpha.0.20170427033203-34922035d248/core/chaincode/platforms/golang/package.go (about)

     1  /*
     2  Copyright IBM Corp. 2016 All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  		 http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package golang
    18  
    19  import (
    20  	"archive/tar"
    21  	"errors"
    22  	"fmt"
    23  
    24  	"bytes"
    25  	"encoding/hex"
    26  	"io/ioutil"
    27  	"os"
    28  	"os/exec"
    29  	"path/filepath"
    30  	"strings"
    31  	"time"
    32  
    33  	"github.com/golang/protobuf/proto"
    34  	"github.com/hyperledger/fabric/common/flogging"
    35  	"github.com/hyperledger/fabric/common/util"
    36  	ccutil "github.com/hyperledger/fabric/core/chaincode/platforms/util"
    37  	cutil "github.com/hyperledger/fabric/core/container/util"
    38  	pb "github.com/hyperledger/fabric/protos/peer"
    39  	"github.com/spf13/viper"
    40  )
    41  
    42  var includeFileTypes = map[string]bool{
    43  	".c":    true,
    44  	".h":    true,
    45  	".go":   true,
    46  	".yaml": true,
    47  	".json": true,
    48  }
    49  
    50  var logger = flogging.MustGetLogger("golang-platform")
    51  
    52  func getCodeFromHTTP(path string) (codegopath string, err error) {
    53  	codegopath = ""
    54  	err = nil
    55  	logger.Debugf("getCodeFromHTTP %s", path)
    56  
    57  	// The following could be done with os.Getenv("GOPATH") but we need to change it later so this prepares for that next step
    58  	env := os.Environ()
    59  	var origgopath string
    60  	var gopathenvIndex int
    61  	for i, v := range env {
    62  		if strings.Index(v, "GOPATH=") == 0 {
    63  			p := strings.SplitAfter(v, "GOPATH=")
    64  			origgopath = p[1]
    65  			gopathenvIndex = i
    66  			break
    67  		}
    68  	}
    69  	if origgopath == "" {
    70  		err = errors.New("GOPATH not defined")
    71  		return
    72  	}
    73  	// Only take the first element of GOPATH
    74  	gopath := filepath.SplitList(origgopath)[0]
    75  
    76  	// Define a new gopath in which to download the code
    77  	newgopath := filepath.Join(gopath, "_usercode_")
    78  
    79  	//ignore errors.. _usercode_ might exist. TempDir will catch any other errors
    80  	os.Mkdir(newgopath, 0755)
    81  
    82  	if codegopath, err = ioutil.TempDir(newgopath, ""); err != nil {
    83  		err = fmt.Errorf("could not create tmp dir under %s(%s)", newgopath, err)
    84  		return
    85  	}
    86  
    87  	//go paths can have multiple dirs. We create a GOPATH with two source tree's as follows
    88  	//
    89  	//    <temporary empty folder to download chaincode source> : <local go path with OBC source>
    90  	//
    91  	//This approach has several goodness:
    92  	// . Go will pick the first path to download user code (which we will delete after processing)
    93  	// . GO will not download OBC as it is in the second path. GO will use the local OBC for generating chaincode image
    94  	//     . network savings
    95  	//     . more secure
    96  	//     . as we are not downloading OBC, private, password-protected OBC repo's become non-issue
    97  
    98  	env[gopathenvIndex] = "GOPATH=" + codegopath + string(os.PathListSeparator) + origgopath
    99  
   100  	// Use a 'go get' command to pull the chaincode from the given repo
   101  	logger.Debugf("go get %s", path)
   102  	cmd := exec.Command("go", "get", path)
   103  	cmd.Env = env
   104  	var out bytes.Buffer
   105  	cmd.Stdout = &out
   106  	var errBuf bytes.Buffer
   107  	cmd.Stderr = &errBuf //capture Stderr and print it on error
   108  	err = cmd.Start()
   109  
   110  	// Create a go routine that will wait for the command to finish
   111  	done := make(chan error, 1)
   112  	go func() {
   113  		done <- cmd.Wait()
   114  	}()
   115  
   116  	select {
   117  	case <-time.After(time.Duration(viper.GetInt("chaincode.deploytimeout")) * time.Millisecond):
   118  		// If pulling repos takes too long, we should give up
   119  		// (This can happen if a repo is private and the git clone asks for credentials)
   120  		if err = cmd.Process.Kill(); err != nil {
   121  			err = fmt.Errorf("failed to kill: %s", err)
   122  		} else {
   123  			err = errors.New("Getting chaincode took too long")
   124  		}
   125  	case err = <-done:
   126  		// If we're here, the 'go get' command must have finished
   127  		if err != nil {
   128  			err = fmt.Errorf("'go get' failed with error: \"%s\"\n%s", err, string(errBuf.Bytes()))
   129  		}
   130  	}
   131  	return
   132  }
   133  
   134  func getCodeFromFS(path string) (codegopath string, err error) {
   135  	logger.Debugf("getCodeFromFS %s", path)
   136  	gopath := os.Getenv("GOPATH")
   137  	if gopath == "" {
   138  		err = errors.New("GOPATH not defined")
   139  		return
   140  	}
   141  	// Only take the first element of GOPATH
   142  	codegopath = filepath.SplitList(gopath)[0]
   143  
   144  	return
   145  }
   146  
   147  //collectChaincodeFiles collects chaincode files and generates hashcode for the
   148  //package. If path is a HTTP(s) url it downloads the code first.
   149  //NOTE: for dev mode, user builds and runs chaincode manually. The name provided
   150  //by the user is equivalent to the path. This method will treat the name
   151  //as codebytes and compute the hash from it. ie, user cannot run the chaincode
   152  //with the same (name, input, args)
   153  func collectChaincodeFiles(spec *pb.ChaincodeSpec, tw *tar.Writer) (string, error) {
   154  	if spec == nil {
   155  		return "", errors.New("Cannot collect files from nil spec")
   156  	}
   157  
   158  	chaincodeID := spec.ChaincodeId
   159  	if chaincodeID == nil || chaincodeID.Path == "" {
   160  		return "", errors.New("Cannot collect files from empty chaincode path")
   161  	}
   162  
   163  	//install will not have inputs and we don't have to collect hash for it
   164  	var inputbytes []byte
   165  
   166  	var err error
   167  	if spec.Input == nil || len(spec.Input.Args) == 0 {
   168  		logger.Debugf("not using input for hash computation for %v ", chaincodeID)
   169  	} else {
   170  		inputbytes, err = proto.Marshal(spec.Input)
   171  		if err != nil {
   172  			return "", fmt.Errorf("Error marshalling constructor: %s", err)
   173  		}
   174  	}
   175  
   176  	//code root will point to the directory where the code exists
   177  	//in the case of http it will be a temporary dir that
   178  	//will have to be deleted
   179  	var codegopath string
   180  
   181  	var ishttp bool
   182  	defer func() {
   183  		if ishttp && codegopath != "" {
   184  			os.RemoveAll(codegopath)
   185  		}
   186  	}()
   187  
   188  	path := chaincodeID.Path
   189  
   190  	var actualcodepath string
   191  	if strings.HasPrefix(path, "http://") {
   192  		ishttp = true
   193  		actualcodepath = path[7:]
   194  		codegopath, err = getCodeFromHTTP(actualcodepath)
   195  	} else if strings.HasPrefix(path, "https://") {
   196  		ishttp = true
   197  		actualcodepath = path[8:]
   198  		codegopath, err = getCodeFromHTTP(actualcodepath)
   199  	} else {
   200  		actualcodepath = path
   201  		codegopath, err = getCodeFromFS(path)
   202  	}
   203  
   204  	if err != nil {
   205  		return "", fmt.Errorf("Error getting code %s", err)
   206  	}
   207  
   208  	tmppath := filepath.Join(codegopath, "src", actualcodepath)
   209  	if err = ccutil.IsCodeExist(tmppath); err != nil {
   210  		return "", fmt.Errorf("code does not exist %s", err)
   211  	}
   212  
   213  	hash := []byte{}
   214  	if inputbytes != nil {
   215  		hash = util.GenerateHashFromSignature(actualcodepath, inputbytes)
   216  	}
   217  
   218  	hash, err = ccutil.HashFilesInDir(filepath.Join(codegopath, "src"), actualcodepath, hash, tw)
   219  	if err != nil {
   220  		return "", fmt.Errorf("Could not get hashcode for %s - %s\n", path, err)
   221  	}
   222  
   223  	return hex.EncodeToString(hash[:]), nil
   224  }
   225  
   226  //WriteGopathSrc tars up files under gopath src
   227  func writeGopathSrc(tw *tar.Writer, excludeDir string) error {
   228  	gopath := os.Getenv("GOPATH")
   229  	// Only take the first element of GOPATH
   230  	gopath = filepath.SplitList(gopath)[0]
   231  
   232  	rootDirectory := filepath.Join(gopath, "src")
   233  	logger.Infof("rootDirectory = %s", rootDirectory)
   234  
   235  	if err := cutil.WriteFolderToTarPackage(tw, rootDirectory, excludeDir, includeFileTypes, nil); err != nil {
   236  		logger.Errorf("Error writing folder to tar package %s", err)
   237  		return err
   238  	}
   239  
   240  	// Write the tar file out
   241  	if err := tw.Close(); err != nil {
   242  		return err
   243  	}
   244  	//ioutil.WriteFile("/tmp/chaincode_deployment.tar", inputbuf.Bytes(), 0644)
   245  	return nil
   246  }
   247  
   248  //tw is expected to have the chaincode in it from GenerateHashcode. This method
   249  //will just package rest of the bytes
   250  func writeChaincodePackage(spec *pb.ChaincodeSpec, tw *tar.Writer) error {
   251  
   252  	urlLocation, err := decodeUrl(spec)
   253  	if err != nil {
   254  		return fmt.Errorf("could not decode url: %s", err)
   255  	}
   256  
   257  	err = writeGopathSrc(tw, urlLocation)
   258  	if err != nil {
   259  		return fmt.Errorf("Error writing Chaincode package contents: %s", err)
   260  	}
   261  	return nil
   262  }