github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/chaincode/platforms/golang/platform.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package golang
     8  
     9  import (
    10  	"archive/tar"
    11  	"bytes"
    12  	"compress/gzip"
    13  	"fmt"
    14  	"io"
    15  	"io/ioutil"
    16  	"os"
    17  	"os/exec"
    18  	"path"
    19  	"path/filepath"
    20  	"regexp"
    21  	"runtime"
    22  	"sort"
    23  	"strings"
    24  
    25  	"github.com/hechain20/hechain/core/chaincode/platforms/util"
    26  	"github.com/hechain20/hechain/internal/ccmetadata"
    27  	pb "github.com/hyperledger/fabric-protos-go/peer"
    28  	"github.com/pkg/errors"
    29  	"github.com/spf13/viper"
    30  )
    31  
    32  // Platform for chaincodes written in Go
    33  type Platform struct{}
    34  
    35  // Name returns the name of this platform.
    36  func (p *Platform) Name() string {
    37  	return pb.ChaincodeSpec_GOLANG.String()
    38  }
    39  
    40  // ValidatePath is used to ensure that path provided points to something that
    41  // looks like go chainccode.
    42  //
    43  // NOTE: this is only used at the _client_ side by the peer CLI.
    44  func (p *Platform) ValidatePath(rawPath string) error {
    45  	_, err := DescribeCode(rawPath)
    46  	if err != nil {
    47  		return err
    48  	}
    49  
    50  	return nil
    51  }
    52  
    53  // NormalizePath is used to extract a relative module path from a module root.
    54  // This should not impact legacy GOPATH chaincode.
    55  //
    56  // NOTE: this is only used at the _client_ side by the peer CLI.
    57  func (p *Platform) NormalizePath(rawPath string) (string, error) {
    58  	modInfo, err := moduleInfo(rawPath)
    59  	if err != nil {
    60  		return "", err
    61  	}
    62  
    63  	// not a module
    64  	if modInfo == nil {
    65  		return rawPath, nil
    66  	}
    67  
    68  	return modInfo.ImportPath, nil
    69  }
    70  
    71  // ValidateCodePackage examines the chaincode archive to ensure it is valid.
    72  //
    73  // NOTE: this code is used in some transaction validation paths but can be changed
    74  // post 2.0.
    75  func (p *Platform) ValidateCodePackage(code []byte) error {
    76  	is := bytes.NewReader(code)
    77  	gr, err := gzip.NewReader(is)
    78  	if err != nil {
    79  		return fmt.Errorf("failure opening codepackage gzip stream: %s", err)
    80  	}
    81  
    82  	re := regexp.MustCompile(`^(src|META-INF)/`)
    83  	tr := tar.NewReader(gr)
    84  	for {
    85  		header, err := tr.Next()
    86  		if err == io.EOF {
    87  			break
    88  		}
    89  		if err != nil {
    90  			return err
    91  		}
    92  
    93  		// maintain check for conforming paths for validation
    94  		if !re.MatchString(header.Name) {
    95  			return fmt.Errorf("illegal file name in payload: %s", header.Name)
    96  		}
    97  
    98  		// only files and directories; no links or special files
    99  		mode := header.FileInfo().Mode()
   100  		if mode&^(os.ModeDir|0o777) != 0 {
   101  			return fmt.Errorf("illegal file mode in payload: %s", header.Name)
   102  		}
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  // Directory constant copied from tar package.
   109  const c_ISDIR = 0o40000
   110  
   111  // Default compression to use for production. Test packages disable compression.
   112  var gzipCompressionLevel = gzip.DefaultCompression
   113  
   114  // GetDeploymentPayload creates a gzip compressed tape archive that contains the
   115  // required assets to build and run go chaincode.
   116  //
   117  // NOTE: this is only used at the _client_ side by the peer CLI.
   118  func (p *Platform) GetDeploymentPayload(codepath string) ([]byte, error) {
   119  	codeDescriptor, err := DescribeCode(codepath)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	fileMap, err := findSource(codeDescriptor)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	var dependencyPackageInfo []PackageInfo
   130  	if !codeDescriptor.Module {
   131  		for _, dist := range distributions() {
   132  			pi, err := gopathDependencyPackageInfo(dist.goos, dist.goarch, codeDescriptor.Path)
   133  			if err != nil {
   134  				return nil, err
   135  			}
   136  			dependencyPackageInfo = append(dependencyPackageInfo, pi...)
   137  		}
   138  	}
   139  
   140  	for _, pkg := range dependencyPackageInfo {
   141  		for _, filename := range pkg.Files() {
   142  			sd := SourceDescriptor{
   143  				Name: path.Join("src", pkg.ImportPath, filename),
   144  				Path: filepath.Join(pkg.Dir, filename),
   145  			}
   146  			fileMap[sd.Name] = sd
   147  		}
   148  	}
   149  
   150  	payload := bytes.NewBuffer(nil)
   151  	gw, err := gzip.NewWriterLevel(payload, gzipCompressionLevel)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	tw := tar.NewWriter(gw)
   156  
   157  	// Create directories so they get sane ownership and permissions
   158  	for _, dirname := range fileMap.Directories() {
   159  		err := tw.WriteHeader(&tar.Header{
   160  			Typeflag: tar.TypeDir,
   161  			Name:     dirname + "/",
   162  			Mode:     c_ISDIR | 0o755,
   163  			Uid:      500,
   164  			Gid:      500,
   165  		})
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  	}
   170  
   171  	for _, file := range fileMap.Sources() {
   172  		err = util.WriteFileToPackage(file.Path, file.Name, tw)
   173  		if err != nil {
   174  			return nil, fmt.Errorf("Error writing %s to tar: %s", file.Name, err)
   175  		}
   176  	}
   177  
   178  	err = tw.Close()
   179  	if err == nil {
   180  		err = gw.Close()
   181  	}
   182  	if err != nil {
   183  		return nil, errors.Wrapf(err, "failed to create tar for chaincode")
   184  	}
   185  
   186  	return payload.Bytes(), nil
   187  }
   188  
   189  func (p *Platform) GenerateDockerfile() (string, error) {
   190  	var buf []string
   191  	buf = append(buf, "FROM "+util.GetDockerImageFromConfig("chaincode.golang.runtime"))
   192  	buf = append(buf, "ADD binpackage.tar /usr/local/bin")
   193  
   194  	return strings.Join(buf, "\n"), nil
   195  }
   196  
   197  const (
   198  	staticLDFlagsOpts  = "-ldflags \"-linkmode external -extldflags '-static'\""
   199  	dynamicLDFlagsOpts = ""
   200  )
   201  
   202  func getLDFlagsOpts() string {
   203  	if viper.GetBool("chaincode.golang.dynamicLink") {
   204  		return dynamicLDFlagsOpts
   205  	}
   206  	return staticLDFlagsOpts
   207  }
   208  
   209  var buildScript = `
   210  set -e
   211  if [ -f "/chaincode/input/src/go.mod" ] && [ -d "/chaincode/input/src/vendor" ]; then
   212      cd /chaincode/input/src
   213      GO111MODULE=on go build -v -mod=vendor %[1]s -o /chaincode/output/chaincode %[2]s
   214  elif [ -f "/chaincode/input/src/go.mod" ]; then
   215      cd /chaincode/input/src
   216      GO111MODULE=on go build -v -mod=readonly %[1]s -o /chaincode/output/chaincode %[2]s
   217  elif [ -f "/chaincode/input/src/%[2]s/go.mod" ] && [ -d "/chaincode/input/src/%[2]s/vendor" ]; then
   218      cd /chaincode/input/src/%[2]s
   219      GO111MODULE=on go build -v -mod=vendor %[1]s -o /chaincode/output/chaincode .
   220  elif [ -f "/chaincode/input/src/%[2]s/go.mod" ]; then
   221      cd /chaincode/input/src/%[2]s
   222      GO111MODULE=on go build -v -mod=readonly %[1]s -o /chaincode/output/chaincode .
   223  else
   224      GO111MODULE=off GOPATH=/chaincode/input:$GOPATH go build -v %[1]s -o /chaincode/output/chaincode %[2]s
   225  fi
   226  echo Done!
   227  `
   228  
   229  func (p *Platform) DockerBuildOptions(path string) (util.DockerBuildOptions, error) {
   230  	env := []string{}
   231  	for _, key := range []string{"GOPROXY", "GOSUMDB"} {
   232  		if val, ok := os.LookupEnv(key); ok {
   233  			env = append(env, fmt.Sprintf("%s=%s", key, val))
   234  			continue
   235  		}
   236  		if key == "GOPROXY" {
   237  			env = append(env, "GOPROXY=https://proxy.golang.org")
   238  		}
   239  	}
   240  	ldFlagOpts := getLDFlagsOpts()
   241  	return util.DockerBuildOptions{
   242  		Cmd: fmt.Sprintf(buildScript, ldFlagOpts, path),
   243  		Env: env,
   244  	}, nil
   245  }
   246  
   247  // CodeDescriptor describes the code we're packaging.
   248  type CodeDescriptor struct {
   249  	Source       string // absolute path of the source to package
   250  	MetadataRoot string // absolute path META-INF
   251  	Path         string // import path of the package
   252  	Module       bool   // does this represent a go module
   253  }
   254  
   255  func (cd CodeDescriptor) isMetadata(path string) bool {
   256  	return strings.HasPrefix(
   257  		filepath.Clean(path),
   258  		filepath.Clean(cd.MetadataRoot),
   259  	)
   260  }
   261  
   262  // DescribeCode returns GOPATH and package information.
   263  func DescribeCode(path string) (*CodeDescriptor, error) {
   264  	if path == "" {
   265  		return nil, errors.New("cannot collect files from empty chaincode path")
   266  	}
   267  
   268  	// Use the module root as the source path for go modules
   269  	modInfo, err := moduleInfo(path)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  
   274  	if modInfo != nil {
   275  		// calculate where the metadata should be relative to module root
   276  		relImport, err := filepath.Rel(modInfo.ModulePath, modInfo.ImportPath)
   277  		if err != nil {
   278  			return nil, err
   279  		}
   280  
   281  		return &CodeDescriptor{
   282  			Module:       true,
   283  			MetadataRoot: filepath.Join(modInfo.Dir, relImport, "META-INF"),
   284  			Path:         modInfo.ImportPath,
   285  			Source:       modInfo.Dir,
   286  		}, nil
   287  	}
   288  
   289  	return describeGopath(path)
   290  }
   291  
   292  func describeGopath(importPath string) (*CodeDescriptor, error) {
   293  	output, err := exec.Command("go", "list", "-f", "{{.Dir}}", importPath).Output()
   294  	if err != nil {
   295  		return nil, wrapExitErr(err, "'go list' failed")
   296  	}
   297  	sourcePath := filepath.Clean(strings.TrimSpace(string(output)))
   298  
   299  	return &CodeDescriptor{
   300  		Path:         importPath,
   301  		MetadataRoot: filepath.Join(sourcePath, "META-INF"),
   302  		Source:       sourcePath,
   303  	}, nil
   304  }
   305  
   306  func regularFileExists(path string) (bool, error) {
   307  	fi, err := os.Stat(path)
   308  	switch {
   309  	case os.IsNotExist(err):
   310  		return false, nil
   311  	case err != nil:
   312  		return false, err
   313  	default:
   314  		return fi.Mode().IsRegular(), nil
   315  	}
   316  }
   317  
   318  func moduleInfo(path string) (*ModuleInfo, error) {
   319  	entryWD, err := os.Getwd()
   320  	if err != nil {
   321  		return nil, errors.Wrap(err, "failed to get working directory")
   322  	}
   323  
   324  	// directory doesn't exist so unlikely to be a module
   325  	if err := os.Chdir(path); err != nil {
   326  		return nil, nil
   327  	}
   328  	defer func() {
   329  		if err := os.Chdir(entryWD); err != nil {
   330  			panic(fmt.Sprintf("failed to restore working directory: %s", err))
   331  		}
   332  	}()
   333  
   334  	// Using `go list -m -f '{{ if .Main }}{{.GoMod}}{{ end }}' all` may try to
   335  	// generate a go.mod when a vendor tool is in use. To avoid that behavior
   336  	// we use `go env GOMOD` followed by an existence check.
   337  	cmd := exec.Command("go", "env", "GOMOD")
   338  	cmd.Env = append(os.Environ(), "GO111MODULE=on")
   339  	output, err := cmd.Output()
   340  	if err != nil {
   341  		return nil, wrapExitErr(err, "failed to determine module root")
   342  	}
   343  
   344  	modExists, err := regularFileExists(strings.TrimSpace(string(output)))
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  	if !modExists {
   349  		return nil, nil
   350  	}
   351  
   352  	return listModuleInfo()
   353  }
   354  
   355  type SourceDescriptor struct {
   356  	Name string
   357  	Path string
   358  }
   359  
   360  type Sources []SourceDescriptor
   361  
   362  func (s Sources) Len() int           { return len(s) }
   363  func (s Sources) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   364  func (s Sources) Less(i, j int) bool { return s[i].Name < s[j].Name }
   365  
   366  type SourceMap map[string]SourceDescriptor
   367  
   368  func (s SourceMap) Sources() Sources {
   369  	var sources Sources
   370  	for _, src := range s {
   371  		sources = append(sources, src)
   372  	}
   373  
   374  	sort.Sort(sources)
   375  	return sources
   376  }
   377  
   378  func (s SourceMap) Directories() []string {
   379  	dirMap := map[string]bool{}
   380  	for entryName := range s {
   381  		dir := path.Dir(entryName)
   382  		for dir != "." && !dirMap[dir] {
   383  			dirMap[dir] = true
   384  			dir = path.Dir(dir)
   385  		}
   386  	}
   387  
   388  	var dirs []string
   389  	for dir := range dirMap {
   390  		dirs = append(dirs, dir)
   391  	}
   392  	sort.Strings(dirs)
   393  
   394  	return dirs
   395  }
   396  
   397  func findSource(cd *CodeDescriptor) (SourceMap, error) {
   398  	sources := SourceMap{}
   399  
   400  	walkFn := func(path string, info os.FileInfo, err error) error {
   401  		if err != nil {
   402  			return err
   403  		}
   404  
   405  		if info.IsDir() {
   406  			// Allow import of the top level chaincode directory into chaincode code package
   407  			if path == cd.Source {
   408  				return nil
   409  			}
   410  
   411  			// Allow import of META-INF metadata directories into chaincode code package tar.
   412  			// META-INF directories contain chaincode metadata artifacts such as statedb index definitions
   413  			if cd.isMetadata(path) {
   414  				return nil
   415  			}
   416  
   417  			// include everything except hidden dirs when we're not vendoring
   418  			if cd.Module && !strings.HasPrefix(info.Name(), ".") {
   419  				return nil
   420  			}
   421  
   422  			// Do not import any other directories into chaincode code package
   423  			return filepath.SkipDir
   424  		}
   425  
   426  		relativeRoot := cd.Source
   427  		if cd.isMetadata(path) {
   428  			relativeRoot = cd.MetadataRoot
   429  		}
   430  
   431  		name, err := filepath.Rel(relativeRoot, path)
   432  		if err != nil {
   433  			return errors.Wrapf(err, "failed to calculate relative path for %s", path)
   434  		}
   435  
   436  		switch {
   437  		case cd.isMetadata(path):
   438  			// Skip hidden files in metadata
   439  			if strings.HasPrefix(info.Name(), ".") {
   440  				return nil
   441  			}
   442  			name = filepath.Join("META-INF", name)
   443  			err := validateMetadata(name, path)
   444  			if err != nil {
   445  				return err
   446  			}
   447  		case cd.Module:
   448  			name = filepath.Join("src", name)
   449  		default:
   450  			// skip top level go.mod and go.sum when not in module mode
   451  			if name == "go.mod" || name == "go.sum" {
   452  				return nil
   453  			}
   454  			name = filepath.Join("src", cd.Path, name)
   455  		}
   456  
   457  		name = filepath.ToSlash(name)
   458  		sources[name] = SourceDescriptor{Name: name, Path: path}
   459  		return nil
   460  	}
   461  
   462  	if err := filepath.Walk(cd.Source, walkFn); err != nil {
   463  		return nil, errors.Wrap(err, "walk failed")
   464  	}
   465  
   466  	return sources, nil
   467  }
   468  
   469  func validateMetadata(name, path string) error {
   470  	contents, err := ioutil.ReadFile(path)
   471  	if err != nil {
   472  		return err
   473  	}
   474  
   475  	// Validate metadata file for inclusion in tar
   476  	// Validation is based on the passed filename with path
   477  	err = ccmetadata.ValidateMetadataFile(filepath.ToSlash(name), contents)
   478  	if err != nil {
   479  		return err
   480  	}
   481  
   482  	return nil
   483  }
   484  
   485  // dist holds go "distribution" information. The full list of distributions can
   486  // be obtained with `go tool dist list.
   487  type dist struct{ goos, goarch string }
   488  
   489  // distributions returns the list of OS and ARCH combinations that we calcluate
   490  // deps for.
   491  func distributions() []dist {
   492  	// pre-populate linux architecutures
   493  	dists := map[dist]bool{
   494  		{goos: "linux", goarch: "amd64"}: true,
   495  	}
   496  
   497  	// add local OS and ARCH, linux and current ARCH
   498  	dists[dist{goos: runtime.GOOS, goarch: runtime.GOARCH}] = true
   499  	dists[dist{goos: "linux", goarch: runtime.GOARCH}] = true
   500  
   501  	var list []dist
   502  	for d := range dists {
   503  		list = append(list, d)
   504  	}
   505  
   506  	return list
   507  }