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 }