diff --git a/core/chaincode/platforms/golang/env.go b/core/chaincode/platforms/golang/env.go new file mode 100644 index 00000000000..2c7f210c68a --- /dev/null +++ b/core/chaincode/platforms/golang/env.go @@ -0,0 +1,67 @@ +/* +Copyright 2017 - Greg Haskins + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package golang + +import ( + "os" + "path/filepath" + "strings" +) + +type Env map[string]string + +func getEnv() Env { + env := make(Env) + for _, entry := range os.Environ() { + tokens := strings.Split(entry, "=") + if len(tokens) > 1 { + env[tokens[0]] = tokens[1] + } + } + + return env +} + +func flattenEnv(env Env) []string { + result := make([]string, 0) + for k, v := range env { + result = append(result, k+"="+v) + } + + return result +} + +type Paths map[string]bool + +func splitEnvPaths(value string) Paths { + _paths := filepath.SplitList(value) + paths := make(Paths) + for _, path := range _paths { + paths[path] = true + } + return paths +} + +func flattenEnvPaths(paths Paths) string { + + _paths := make([]string, 0) + for path, _ := range paths { + _paths = append(_paths, path) + } + + return strings.Join(_paths, string(os.PathListSeparator)) +} diff --git a/core/chaincode/platforms/golang/env_test.go b/core/chaincode/platforms/golang/env_test.go new file mode 100644 index 00000000000..92e23185d23 --- /dev/null +++ b/core/chaincode/platforms/golang/env_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2017 - Greg Haskins + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package golang + +import ( + "os" + "testing" + + "github.com/docker/docker/pkg/testutil/assert" +) + +func Test_splitEnvPath(t *testing.T) { + paths := splitEnvPaths("foo" + string(os.PathListSeparator) + "bar" + string(os.PathListSeparator) + "baz") + assert.Equal(t, len(paths), 3) +} diff --git a/core/chaincode/platforms/golang/list.go b/core/chaincode/platforms/golang/list.go new file mode 100644 index 00000000000..9eb345f2102 --- /dev/null +++ b/core/chaincode/platforms/golang/list.go @@ -0,0 +1,72 @@ +/* +Copyright 2017 - Greg Haskins + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package golang + +import ( + "bytes" + "errors" + "fmt" + "os/exec" + "strings" + "time" +) + +// Logic inspired by: https://dave.cheney.net/2014/09/14/go-list-your-swiss-army-knife +func list(env Env, template, pkg string) ([]string, error) { + + if env == nil { + env = getEnv() + } + + var stdOut bytes.Buffer + var stdErr bytes.Buffer + + cmd := exec.Command("go", "list", "-f", template, pkg) + cmd.Env = flattenEnv(env) + cmd.Stdout = &stdOut + cmd.Stderr = &stdErr + err := cmd.Start() + + // Create a go routine that will wait for the command to finish + done := make(chan error, 1) + go func() { + done <- cmd.Wait() + }() + + select { + case <-time.After(60 * time.Second): + if err = cmd.Process.Kill(); err != nil { + return nil, fmt.Errorf("go list: failed to kill: %s", err) + } else { + return nil, errors.New("go list: timeout") + } + case err = <-done: + if err != nil { + return nil, fmt.Errorf("go list: failed with error: \"%s\"\n%s", err, string(stdErr.Bytes())) + } + + return strings.Split(string(stdOut.Bytes()), "\n"), nil + } +} + +func listDeps(env Env, pkg string) ([]string, error) { + return list(env, "{{ join .Deps \"\\n\"}}", pkg) +} + +func listImports(env Env, pkg string) ([]string, error) { + return list(env, "{{ join .Imports \"\\n\"}}", pkg) +} diff --git a/core/chaincode/platforms/golang/list_test.go b/core/chaincode/platforms/golang/list_test.go new file mode 100644 index 00000000000..22c0045bcd6 --- /dev/null +++ b/core/chaincode/platforms/golang/list_test.go @@ -0,0 +1,26 @@ +/* +Copyright 2017 - Greg Haskins + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package golang + +import "testing" + +func Test_listDeps(t *testing.T) { + _, err := listDeps(nil, "github.com/hyperledger/fabric/peer") + if err != nil { + t.Errorf("list failed: %s", err) + } +} diff --git a/core/chaincode/platforms/golang/package.go b/core/chaincode/platforms/golang/package.go index a99693d914d..0e4d541cff2 100644 --- a/core/chaincode/platforms/golang/package.go +++ b/core/chaincode/platforms/golang/package.go @@ -17,12 +17,10 @@ limitations under the License. package golang import ( - "archive/tar" "errors" "fmt" "bytes" - "encoding/hex" "io/ioutil" "os" "os/exec" @@ -30,11 +28,8 @@ import ( "strings" "time" - "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric/common/flogging" - "github.com/hyperledger/fabric/common/util" ccutil "github.com/hyperledger/fabric/core/chaincode/platforms/util" - cutil "github.com/hyperledger/fabric/core/container/util" pb "github.com/hyperledger/fabric/protos/peer" "github.com/spf13/viper" ) @@ -54,24 +49,11 @@ func getCodeFromHTTP(path string) (codegopath string, err error) { err = nil logger.Debugf("getCodeFromHTTP %s", path) - // The following could be done with os.Getenv("GOPATH") but we need to change it later so this prepares for that next step - env := os.Environ() - var origgopath string - var gopathenvIndex int - for i, v := range env { - if strings.Index(v, "GOPATH=") == 0 { - p := strings.SplitAfter(v, "GOPATH=") - origgopath = p[1] - gopathenvIndex = i - break - } - } - if origgopath == "" { - err = errors.New("GOPATH not defined") + env := getEnv() + gopath, err := getGopath() + if err != nil { return } - // Only take the first element of GOPATH - gopath := filepath.SplitList(origgopath)[0] // Define a new gopath in which to download the code newgopath := filepath.Join(gopath, "_usercode_") @@ -83,6 +65,12 @@ func getCodeFromHTTP(path string) (codegopath string, err error) { err = fmt.Errorf("could not create tmp dir under %s(%s)", newgopath, err) return } + defer func() { + // Clean up after ourselves IFF we return a failure in the future + if err != nil { + os.RemoveAll(codegopath) + } + }() //go paths can have multiple dirs. We create a GOPATH with two source tree's as follows // @@ -95,12 +83,13 @@ func getCodeFromHTTP(path string) (codegopath string, err error) { // . more secure // . as we are not downloading OBC, private, password-protected OBC repo's become non-issue - env[gopathenvIndex] = "GOPATH=" + codegopath + string(os.PathListSeparator) + origgopath + origgopath := env["GOPATH"] + env["GOPATH"] = codegopath + string(os.PathListSeparator) + origgopath // Use a 'go get' command to pull the chaincode from the given repo logger.Debugf("go get %s", path) cmd := exec.Command("go", "get", path) - cmd.Env = env + cmd.Env = flattenEnv(env) var out bytes.Buffer cmd.Stdout = &out var errBuf bytes.Buffer @@ -138,132 +127,137 @@ func getCodeFromHTTP(path string) (codegopath string, err error) { return } -func getCodeFromFS(path string) (codegopath string, err error) { +func getCodeFromFS(path string) (string, error) { logger.Debugf("getCodeFromFS %s", path) - gopath := os.Getenv("GOPATH") - if gopath == "" { - err = errors.New("GOPATH not defined") - return + gopath, err := getGopath() + if err != nil { + return "", err } - // Only take the first element of GOPATH - codegopath = filepath.SplitList(gopath)[0] - return + tmppath := filepath.Join(gopath, "src", path) + if err := ccutil.IsCodeExist(tmppath); err != nil { + return "", fmt.Errorf("code does not exist %s", err) + } + + return gopath, nil +} + +type CodeDescriptor struct { + Gopath, Pkg string + Cleanup func() } -//collectChaincodeFiles collects chaincode files and generates hashcode for the -//package. If path is a HTTP(s) url it downloads the code first. +// collectChaincodeFiles collects chaincode files. If path is a HTTP(s) url it +// downloads the code first. +// //NOTE: for dev mode, user builds and runs chaincode manually. The name provided -//by the user is equivalent to the path. This method will treat the name -//as codebytes and compute the hash from it. ie, user cannot run the chaincode -//with the same (name, input, args) -func collectChaincodeFiles(spec *pb.ChaincodeSpec, tw *tar.Writer) (string, error) { +//by the user is equivalent to the path. +func getCode(spec *pb.ChaincodeSpec) (*CodeDescriptor, error) { if spec == nil { - return "", errors.New("Cannot collect files from nil spec") + return nil, errors.New("Cannot collect files from nil spec") } chaincodeID := spec.ChaincodeId if chaincodeID == nil || chaincodeID.Path == "" { - return "", errors.New("Cannot collect files from empty chaincode path") + return nil, errors.New("Cannot collect files from empty chaincode path") } - //install will not have inputs and we don't have to collect hash for it - var inputbytes []byte - var err error - if spec.Input == nil || len(spec.Input.Args) == 0 { - logger.Debugf("not using input for hash computation for %v ", chaincodeID) - } else { - inputbytes, err = proto.Marshal(spec.Input) - if err != nil { - return "", fmt.Errorf("Error marshalling constructor: %s", err) - } - } //code root will point to the directory where the code exists //in the case of http it will be a temporary dir that //will have to be deleted - var codegopath string - + var gopath string var ishttp bool - defer func() { - if ishttp && codegopath != "" { - os.RemoveAll(codegopath) + cleanup := func() { + if ishttp && gopath != "" { + os.RemoveAll(gopath) } - }() + } path := chaincodeID.Path - var actualcodepath string + var pkg string if strings.HasPrefix(path, "http://") { ishttp = true - actualcodepath = path[7:] - codegopath, err = getCodeFromHTTP(actualcodepath) + pkg = path[7:] + gopath, err = getCodeFromHTTP(pkg) } else if strings.HasPrefix(path, "https://") { ishttp = true - actualcodepath = path[8:] - codegopath, err = getCodeFromHTTP(actualcodepath) + pkg = path[8:] + gopath, err = getCodeFromHTTP(pkg) } else { - actualcodepath = path - codegopath, err = getCodeFromFS(path) + pkg = path + gopath, err = getCodeFromFS(path) } if err != nil { - return "", fmt.Errorf("Error getting code %s", err) + return nil, fmt.Errorf("Error getting code %s", err) } - tmppath := filepath.Join(codegopath, "src", actualcodepath) - if err = ccutil.IsCodeExist(tmppath); err != nil { - return "", fmt.Errorf("code does not exist %s", err) - } + return &CodeDescriptor{Gopath: gopath, Pkg: pkg, Cleanup: cleanup}, nil +} - hash := []byte{} - if inputbytes != nil { - hash = util.GenerateHashFromSignature(actualcodepath, inputbytes) - } +type SourceDescriptor struct { + Name, Path string + Info os.FileInfo +} +type SourceMap map[string]SourceDescriptor - hash, err = ccutil.HashFilesInDir(filepath.Join(codegopath, "src"), actualcodepath, hash, tw) - if err != nil { - return "", fmt.Errorf("Could not get hashcode for %s - %s\n", path, err) - } +type Sources []SourceDescriptor - return hex.EncodeToString(hash[:]), nil +func (s Sources) Len() int { + return len(s) } -//WriteGopathSrc tars up files under gopath src -func writeGopathSrc(tw *tar.Writer, excludeDir string) error { - gopath := os.Getenv("GOPATH") - // Only take the first element of GOPATH - gopath = filepath.SplitList(gopath)[0] +func (s Sources) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} - rootDirectory := filepath.Join(gopath, "src") - logger.Infof("rootDirectory = %s", rootDirectory) +func (s Sources) Less(i, j int) bool { + return strings.Compare(s[i].Name, s[j].Name) < 0 +} - if err := cutil.WriteFolderToTarPackage(tw, rootDirectory, excludeDir, includeFileTypes, nil); err != nil { - logger.Errorf("Error writing folder to tar package %s", err) - return err - } +func findSource(gopath, pkg string) (SourceMap, error) { + sources := make(SourceMap) + tld := filepath.Join(gopath, "src", pkg) + walkFn := func(path string, info os.FileInfo, err error) error { - // Write the tar file out - if err := tw.Close(); err != nil { - return err - } - //ioutil.WriteFile("/tmp/chaincode_deployment.tar", inputbuf.Bytes(), 0644) - return nil -} + if err != nil { + return err + } -//tw is expected to have the chaincode in it from GenerateHashcode. This method -//will just package rest of the bytes -func writeChaincodePackage(spec *pb.ChaincodeSpec, tw *tar.Writer) error { + if info.IsDir() { + if path == tld { + // We dont want to import any directories, but we don't want to stop processing + // at the TLD either. + return nil + } - urlLocation, err := decodeUrl(spec) - if err != nil { - return fmt.Errorf("could not decode url: %s", err) + // Do not recurse + logger.Debugf("skipping dir: %s", path) + return filepath.SkipDir + } + + ext := filepath.Ext(path) + // we only want 'fileTypes' source files at this point + if _, ok := includeFileTypes[ext]; ok != true { + return nil + } + + name, err := filepath.Rel(gopath, path) + if err != nil { + return fmt.Errorf("error obtaining relative path for %s: %s", path, err) + } + + sources[name] = SourceDescriptor{Name: name, Path: path, Info: info} + + return nil } - err = writeGopathSrc(tw, urlLocation) - if err != nil { - return fmt.Errorf("Error writing Chaincode package contents: %s", err) + if err := filepath.Walk(tld, walkFn); err != nil { + return nil, fmt.Errorf("Error walking directory: %s", err) } - return nil + + return sources, nil } diff --git a/core/chaincode/platforms/golang/platform.go b/core/chaincode/platforms/golang/platform.go index 2a2e00a3de0..0a6d57e6957 100644 --- a/core/chaincode/platforms/golang/platform.go +++ b/core/chaincode/platforms/golang/platform.go @@ -28,6 +28,8 @@ import ( "regexp" "strings" + "sort" + "github.com/hyperledger/fabric/core/chaincode/platforms/util" cutil "github.com/hyperledger/fabric/core/container/util" pb "github.com/hyperledger/fabric/protos/peer" @@ -70,6 +72,26 @@ func decodeUrl(spec *pb.ChaincodeSpec) (string, error) { return urlLocation, nil } +func getGopath() (string, error) { + gopath := os.Getenv("GOPATH") + // Only take the first element of GOPATH + splitGoPath := filepath.SplitList(gopath) + if len(splitGoPath) == 0 { + return "", fmt.Errorf("invalid GOPATH environment variable value:[%s]", gopath) + } + return splitGoPath[0], nil +} + +func filter(vs []string, f func(string) bool) []string { + vsf := make([]string, 0) + for _, v := range vs { + if f(v) { + vsf = append(vsf, v) + } + } + return vsf +} + // ValidateSpec validates Go chaincodes func (goPlatform *Platform) ValidateSpec(spec *pb.ChaincodeSpec) error { path, err := url.Parse(spec.ChaincodeId.Path) @@ -81,13 +103,10 @@ func (goPlatform *Platform) ValidateSpec(spec *pb.ChaincodeSpec) error { //which we do later anyway. But we *can* - and *should* - test for existence of local paths. //Treat empty scheme as a local filesystem path if path.Scheme == "" { - gopath := os.Getenv("GOPATH") - // Only take the first element of GOPATH - splitGoPath := filepath.SplitList(gopath) - if len(splitGoPath) == 0 { - return fmt.Errorf("invalid GOPATH environment variable value:[%s]", gopath) + gopath, err := getGopath() + if err != nil { + return err } - gopath = splitGoPath[0] pathToCheck := filepath.Join(gopath, "src", spec.ChaincodeId.Path) exists, err := pathExists(pathToCheck) if err != nil { @@ -156,35 +175,176 @@ func (goPlatform *Platform) ValidateDeploymentSpec(cds *pb.ChaincodeDeploymentSp return nil } -// WritePackage writes the Go chaincode package +// Generates a deployment payload for GOLANG as a series of src/$pkg entries in .tar.gz format func (goPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) { var err error - inputbuf := bytes.NewBuffer(nil) - gw := gzip.NewWriter(inputbuf) - tw := tar.NewWriter(gw) - - //ignore the generated hash. Just use the tw - //The hash could be used in a future enhancement - //to check, warn of duplicate installs etc. - _, err = collectChaincodeFiles(spec, tw) + // -------------------------------------------------------------------------------------- + // retrieve a CodeDescriptor from either HTTP or the filesystem + // -------------------------------------------------------------------------------------- + code, err := getCode(spec) if err != nil { return nil, err } + defer code.Cleanup() + + // -------------------------------------------------------------------------------------- + // Update our environment for the purposes of executing go-list directives + // -------------------------------------------------------------------------------------- + env := getEnv() + gopaths := splitEnvPaths(env["GOPATH"]) + goroots := splitEnvPaths(env["GOROOT"]) + gopaths[code.Gopath] = true + env["GOPATH"] = flattenEnvPaths(gopaths) + + // -------------------------------------------------------------------------------------- + // Retrieve the list of first-order imports referenced by the chaincode + // -------------------------------------------------------------------------------------- + imports, err := listImports(env, code.Pkg) + if err != nil { + return nil, fmt.Errorf("Error obtaining imports: %s", err) + } - err = writeChaincodePackage(spec, tw) + // -------------------------------------------------------------------------------------- + // Remove any imports that are provided by the ccenv or system + // -------------------------------------------------------------------------------------- + var provided = map[string]bool{ + "github.com/hyperledger/fabric/core/chaincode/shim": true, + "github.com/hyperledger/fabric/protos/peer": true, + } - tw.Close() - gw.Close() + imports = filter(imports, func(pkg string) bool { + // Drop if provided by CCENV + if _, ok := provided[pkg]; ok == true { + logger.Debugf("Discarding provided package %s", pkg) + return false + } + + // Drop if provided by GOROOT + for goroot := range goroots { + fqp := filepath.Join(goroot, "src", pkg) + exists, err := pathExists(fqp) + if err == nil && exists { + logger.Debugf("Discarding GOROOT package %s", pkg) + return false + } + } + + // Else, we keep it + logger.Debugf("Accepting import: %s", pkg) + return true + }) + // -------------------------------------------------------------------------------------- + // Assemble the fully resolved list of transitive dependencies from the imports that remain + // -------------------------------------------------------------------------------------- + deps := make(map[string]bool) + + for _, pkg := range imports { + _deps, err := listDeps(env, pkg) + if err != nil { + return nil, fmt.Errorf("Error obtaining dependencies for %s: %s", pkg, err) + } + + // Merge with our top list + for _, dep := range _deps { + deps[dep] = true + } + } + + // cull "" if it exists + delete(deps, "") + + // -------------------------------------------------------------------------------------- + // Find the source from our first-order code package ... + // -------------------------------------------------------------------------------------- + fileMap, err := findSource(code.Gopath, code.Pkg) if err != nil { return nil, err } - payload := inputbuf.Bytes() + // -------------------------------------------------------------------------------------- + // ... followed by the source for any non-system dependencies that our code-package has + // from the filtered list + // -------------------------------------------------------------------------------------- + for dep := range deps { + + logger.Debugf("processing dep: %s", dep) + + // Each dependency should either be in our GOPATH or GOROOT. We are not interested in packaging + // any of the system packages. However, the official way (go-list) to make this determination + // is too expensive to run for every dep. Therefore, we cheat. We assume that any packages that + // cannot be found must be system packages and silently skip them + for gopath := range gopaths { + fqp := filepath.Join(gopath, "src", dep) + exists, err := pathExists(fqp) + + logger.Debugf("checking: %s exists: %v", fqp, exists) + + if err == nil && exists { + + // We only get here when we found it, so go ahead and load its code + files, err := findSource(gopath, dep) + if err != nil { + return nil, err + } + + // Merge the map manually + for _, file := range files { + fileMap[file.Name] = file + } + } + } + } + + logger.Debugf("done") + + // -------------------------------------------------------------------------------------- + // Reclassify and sort the files: + // + // Two goals: + // * Remap non-package dependencies to package/vendor + // * Sort the final filename so the tarball at least looks sane in terms of package grouping + // -------------------------------------------------------------------------------------- + files := make(Sources, 0) + pkgPath := filepath.Join("src", code.Pkg) + vendorPath := filepath.Join(pkgPath, "vendor") + for _, file := range fileMap { + // Vendor any packages that are not already within our chaincode's primary package. We + // detect this by checking the path-prefix. Anything that is prefixed by "src/$pkg" + // (which includes the package itself and anything explicitly vendored in "src/$pkg/vendor") + // are left unperturbed. Everything else is implicitly vendored under src/$pkg/vendor by + // simply remapping "src" -> "src/$pkg/vendor" in the tarball index. + if strings.HasPrefix(file.Name, pkgPath) == false { + origName := file.Name + file.Name = strings.Replace(origName, "src", vendorPath, 1) + logger.Debugf("vendoring %s -> %s", origName, file.Name) + } + + files = append(files, file) + } + + sort.Sort(files) + + // -------------------------------------------------------------------------------------- + // Write out our tar package + // -------------------------------------------------------------------------------------- + payload := bytes.NewBuffer(nil) + gw := gzip.NewWriter(payload) + tw := tar.NewWriter(gw) + + for _, file := range files { + err = cutil.WriteFileToPackage(file.Path, file.Name, tw) + if err != nil { + return nil, fmt.Errorf("Error writing %s to tar: %s", file.Name, err) + } + } + + tw.Close() + gw.Close() - return payload, nil + return payload.Bytes(), nil } func (goPlatform *Platform) GenerateDockerfile(cds *pb.ChaincodeDeploymentSpec) (string, error) { diff --git a/core/chaincode/platforms/golang/platform_test.go b/core/chaincode/platforms/golang/platform_test.go index 30be9f4233e..7aac06f7ca2 100644 --- a/core/chaincode/platforms/golang/platform_test.go +++ b/core/chaincode/platforms/golang/platform_test.go @@ -26,10 +26,10 @@ import ( "testing" "time" + "github.com/docker/docker/pkg/testutil/assert" "github.com/hyperledger/fabric/core/config" pb "github.com/hyperledger/fabric/protos/peer" "github.com/spf13/viper" - "github.com/stretchr/testify/assert" ) func testerr(err error, succ bool) error { @@ -118,25 +118,61 @@ func TestPlatform_GoPathNotSet(t *testing.T) { defer os.Setenv("GOPATH", gopath) os.Setenv("GOPATH", "") - f := func() { - p.ValidateSpec(spec) - } - assert.NotPanics(t, f) - assert.Contains(t, p.ValidateSpec(spec).Error(), "invalid GOPATH environment variable value") + err := p.ValidateSpec(spec) + assert.Contains(t, err.Error(), "invalid GOPATH environment variable value") } -func Test_writeGopathSrc(t *testing.T) { +func Test_findSource(t *testing.T) { + gopath, err := getGopath() + if err != nil { + t.Errorf("failed to get GOPATH: %s", err) + } - inputbuf := bytes.NewBuffer(nil) - tw := tar.NewWriter(inputbuf) + var source SourceMap - err := writeGopathSrc(tw, "") + source, err = findSource(gopath, "github.com/hyperledger/fabric/peer") if err != nil { - t.Fail() - t.Logf("Error writing gopath src: %s", err) + t.Errorf("failed to find source: %s", err) + } + + if _, ok := source["src/github.com/hyperledger/fabric/peer/main.go"]; !ok { + t.Errorf("Failed to find expected source file: %v", source) + } + + source, err = findSource(gopath, "acme.com/this/should/not/exist") + if err == nil { + t.Errorf("Success when failure was expected") + } +} + +func Test_DeploymentPayload(t *testing.T) { + platform := &Platform{} + spec := &pb.ChaincodeSpec{ + ChaincodeId: &pb.ChaincodeID{ + Path: "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02", + }, } - //ioutil.WriteFile("/tmp/chaincode_deployment.tar", inputbuf.Bytes(), 0644) + payload, err := platform.GetDeploymentPayload(spec) + assert.NilError(t, err) + + t.Logf("payload size: %d", len(payload)) + + is := bytes.NewReader(payload) + gr, err := gzip.NewReader(is) + if err == nil { + tr := tar.NewReader(gr) + + for { + header, err := tr.Next() + if err != nil { + // We only get here if there are no more entries to scan + break + } + + t.Logf("%s (%d)", header.Name, header.Size) + } + } } func Test_decodeUrl(t *testing.T) { @@ -205,6 +241,7 @@ func TestGetDeploymentPayload(t *testing.T) { }{ {spec: &pb.ChaincodeSpec{ChaincodeId: &pb.ChaincodeID{Name: "Test Chaincode", Path: "github.com/hyperledger/fabric/examples/chaincode/go/map"}}, succ: true}, {spec: &pb.ChaincodeSpec{ChaincodeId: &pb.ChaincodeID{Name: "Test Chaincode", Path: "github.com/hyperledger/fabric/examples/bad/go/map"}}, succ: false}, + {spec: &pb.ChaincodeSpec{ChaincodeId: &pb.ChaincodeID{Name: "Test Chaincode", Path: "github.com/hyperledger/fabric/test/chaincodes/BadImport"}}, succ: false}, } for _, tst := range tests { diff --git a/examples/e2e_cli/docker-compose-cli.yaml b/examples/e2e_cli/docker-compose-cli.yaml index f933c398c45..e6290cf155f 100644 --- a/examples/e2e_cli/docker-compose-cli.yaml +++ b/examples/e2e_cli/docker-compose-cli.yaml @@ -39,7 +39,7 @@ services: cli: container_name: cli - image: hyperledger/fabric-peer + image: hyperledger/fabric-tools tty: true environment: - GOPATH=/opt/gopath diff --git a/test/chaincodes/BadImport/main.go b/test/chaincodes/BadImport/main.go new file mode 100644 index 00000000000..9c8fef44284 --- /dev/null +++ b/test/chaincodes/BadImport/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "github.com/hyperledger/fabric/core/chaincode/shim" + "fmt" + "bogus/package" +) + +// SimpleChaincode example simple Chaincode implementation +type SimpleChaincode struct { +} + +func main() { + err := shim.Start(new(SimpleChaincode)) + if err != nil { + fmt.Printf("Error starting Simple chaincode: %s", err) + } +}