-
Notifications
You must be signed in to change notification settings - Fork 507
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FABG-990] Implemented lifecycle chaincode packager (#108)
Although this packager should also work with Java and Node, only Golang unit tests were added in this commit. Signed-off-by: Bob Stasyszyn <Bob.Stasyszyn@securekey.com>
- Loading branch information
1 parent
a7b00f7
commit 7ced0c3
Showing
16 changed files
with
2,128 additions
and
0 deletions.
There are no files selected for viewing
31 changes: 31 additions & 0 deletions
31
internal/github.com/hyperledger/fabric/core/chaincode/persistence/chaincode_package.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
/* | ||
Notice: This file has been modified for Hyperledger Fabric SDK Go usage. | ||
Please review third_party pinning scripts and patches for more details. | ||
*/ | ||
|
||
package persistence | ||
|
||
import ( | ||
"regexp" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
// LabelRegexp is the regular expression controlling the allowed characters | ||
// for the package label. | ||
var LabelRegexp = regexp.MustCompile(`^[[:alnum:]][[:alnum:]_.+-]*$`) | ||
|
||
// ValidateLabel return an error if the provided label contains any invalid | ||
// characters, as determined by LabelRegexp. | ||
func ValidateLabel(label string) error { | ||
if !LabelRegexp.MatchString(label) { | ||
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) | ||
} | ||
|
||
return nil | ||
} |
91 changes: 91 additions & 0 deletions
91
internal/github.com/hyperledger/fabric/core/chaincode/persistence/persistence.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
/* | ||
Notice: This file has been modified for Hyperledger Fabric SDK Go usage. | ||
Please review third_party pinning scripts and patches for more details. | ||
*/ | ||
|
||
package persistence | ||
|
||
import ( | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
// FilesystemIO is the production implementation of the IOWriter interface | ||
type FilesystemIO struct { | ||
} | ||
|
||
// WriteFile writes a file to the filesystem; it does so atomically | ||
// by first writing to a temp file and then renaming the file so that | ||
// if the operation crashes midway we're not stuck with a bad package | ||
func (f *FilesystemIO) WriteFile(path, name string, data []byte) error { | ||
if path == "" { | ||
return errors.New("empty path not allowed") | ||
} | ||
tmpFile, err := ioutil.TempFile(path, ".ccpackage.") | ||
if err != nil { | ||
return errors.Wrapf(err, "error creating temp file in directory '%s'", path) | ||
} | ||
defer os.Remove(tmpFile.Name()) | ||
|
||
if n, err := tmpFile.Write(data); err != nil || n != len(data) { | ||
if err == nil { | ||
err = errors.Errorf( | ||
"failed to write the entire content of the file, expected %d, wrote %d", | ||
len(data), n) | ||
} | ||
return errors.Wrapf(err, "error writing to temp file '%s'", tmpFile.Name()) | ||
} | ||
|
||
if err := tmpFile.Close(); err != nil { | ||
return errors.Wrapf(err, "error closing temp file '%s'", tmpFile.Name()) | ||
} | ||
|
||
if err := os.Rename(tmpFile.Name(), filepath.Join(path, name)); err != nil { | ||
return errors.Wrapf(err, "error renaming temp file '%s'", tmpFile.Name()) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Remove removes a file from the filesystem - used for rolling back an in-flight | ||
// Save operation upon a failure | ||
func (f *FilesystemIO) Remove(name string) error { | ||
return os.Remove(name) | ||
} | ||
|
||
// ReadFile reads a file from the filesystem | ||
func (f *FilesystemIO) ReadFile(filename string) ([]byte, error) { | ||
return ioutil.ReadFile(filename) | ||
} | ||
|
||
// ReadDir reads a directory from the filesystem | ||
func (f *FilesystemIO) ReadDir(dirname string) ([]os.FileInfo, error) { | ||
return ioutil.ReadDir(dirname) | ||
} | ||
|
||
// MakeDir makes a directory on the filesystem (and any | ||
// necessary parent directories). | ||
func (f *FilesystemIO) MakeDir(dirname string, mode os.FileMode) error { | ||
return os.MkdirAll(dirname, mode) | ||
} | ||
|
||
// Exists checks whether a file exists | ||
func (*FilesystemIO) Exists(path string) (bool, error) { | ||
_, err := os.Stat(path) | ||
if err == nil { | ||
return true, nil | ||
} | ||
if os.IsNotExist(err) { | ||
return false, nil | ||
} | ||
|
||
return false, errors.Wrapf(err, "could not determine whether file '%s' exists", path) | ||
} |
149 changes: 149 additions & 0 deletions
149
internal/github.com/hyperledger/fabric/core/chaincode/platforms/golang/list.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
/* | ||
Notice: This file has been modified for Hyperledger Fabric SDK Go usage. | ||
Please review third_party pinning scripts and patches for more details. | ||
*/ | ||
|
||
package golang | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
"time" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
const listTimeout = 3 * time.Minute | ||
|
||
// PackageInfo is the subset of data from `go list -deps -json` that's | ||
// necessary to calculate chaincode package dependencies. | ||
type PackageInfo struct { | ||
ImportPath string | ||
Dir string | ||
GoFiles []string | ||
Goroot bool | ||
CFiles []string | ||
CgoFiles []string | ||
HFiles []string | ||
SFiles []string | ||
IgnoredGoFiles []string | ||
Incomplete bool | ||
} | ||
|
||
func (p PackageInfo) Files() []string { | ||
var files []string | ||
files = append(files, p.GoFiles...) | ||
files = append(files, p.CFiles...) | ||
files = append(files, p.CgoFiles...) | ||
files = append(files, p.HFiles...) | ||
files = append(files, p.SFiles...) | ||
files = append(files, p.IgnoredGoFiles...) | ||
return files | ||
} | ||
|
||
// gopathDependencyPackageInfo extracts dependency information for | ||
// specified package. | ||
func gopathDependencyPackageInfo(goos, goarch, pkg string) ([]PackageInfo, error) { | ||
ctx, cancel := context.WithTimeout(context.Background(), listTimeout) | ||
defer cancel() | ||
|
||
cmd := exec.CommandContext(ctx, "go", "list", "-deps", "-json", pkg) | ||
cmd.Env = append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch) | ||
|
||
stdout, err := cmd.StdoutPipe() | ||
if err != nil { | ||
return nil, wrapExitErr(err, "'go list -deps' failed") | ||
} | ||
decoder := json.NewDecoder(stdout) | ||
|
||
err = cmd.Start() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var list []PackageInfo | ||
for { | ||
var packageInfo PackageInfo | ||
err := decoder.Decode(&packageInfo) | ||
if err == io.EOF { | ||
break | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
if packageInfo.Incomplete { | ||
return nil, fmt.Errorf("failed to calculate dependencies: incomplete package: %s", packageInfo.ImportPath) | ||
} | ||
if packageInfo.Goroot { | ||
continue | ||
} | ||
|
||
list = append(list, packageInfo) | ||
} | ||
|
||
err = cmd.Wait() | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "listing deps for package %s failed", pkg) | ||
} | ||
|
||
return list, nil | ||
} | ||
|
||
func wrapExitErr(err error, message string) error { | ||
if ee, ok := err.(*exec.ExitError); ok { | ||
return errors.Wrapf(err, message+" with: %s", strings.TrimRight(string(ee.Stderr), "\n\r\t")) | ||
} | ||
return errors.Wrap(err, message) | ||
} | ||
|
||
type ModuleInfo struct { | ||
Dir string | ||
GoMod string | ||
ImportPath string | ||
ModulePath string | ||
} | ||
|
||
// listModuleInfo extracts module information for the curent working directory. | ||
func listModuleInfo(extraEnv ...string) (*ModuleInfo, error) { | ||
ctx, cancel := context.WithTimeout(context.Background(), listTimeout) | ||
defer cancel() | ||
|
||
cmd := exec.CommandContext(ctx, "go", "list", "-json", ".") | ||
cmd.Env = append(os.Environ(), "GO111MODULE=on") | ||
cmd.Env = append(cmd.Env, extraEnv...) | ||
|
||
output, err := cmd.Output() | ||
if err != nil { | ||
return nil, wrapExitErr(err, "'go list' failed") | ||
} | ||
|
||
var moduleData struct { | ||
ImportPath string | ||
Module struct { | ||
Dir string | ||
Path string | ||
GoMod string | ||
} | ||
} | ||
|
||
if err := json.Unmarshal(output, &moduleData); err != nil { | ||
return nil, errors.Wrap(err, "failed to unmarshal output from 'go list'") | ||
} | ||
|
||
return &ModuleInfo{ | ||
Dir: moduleData.Module.Dir, | ||
GoMod: moduleData.Module.GoMod, | ||
ImportPath: moduleData.ImportPath, | ||
ModulePath: moduleData.Module.Path, | ||
}, nil | ||
} |
Oops, something went wrong.