Skip to content

Commit

Permalink
[FABG-990] Implemented lifecycle chaincode packager (#108)
Browse files Browse the repository at this point in the history
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
bstasyszyn authored Jul 22, 2020
1 parent a7b00f7 commit 7ced0c3
Show file tree
Hide file tree
Showing 16 changed files with 2,128 additions and 0 deletions.
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
}
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)
}
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
}
Loading

0 comments on commit 7ced0c3

Please sign in to comment.