github.com/kchristidis/fabric@v1.0.4-0.20171028114726-837acd08cde1/core/chaincode/platforms/util/utils.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 util
    18  
    19  import (
    20  	"archive/tar"
    21  	"bytes"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	docker "github.com/fsouza/go-dockerclient"
    29  	"github.com/hyperledger/fabric/common/flogging"
    30  	"github.com/hyperledger/fabric/common/util"
    31  	cutil "github.com/hyperledger/fabric/core/container/util"
    32  )
    33  
    34  var logger = flogging.MustGetLogger("util")
    35  
    36  //ComputeHash computes contents hash based on previous hash
    37  func ComputeHash(contents []byte, hash []byte) []byte {
    38  	newSlice := make([]byte, len(hash)+len(contents))
    39  
    40  	//copy the contents
    41  	copy(newSlice[0:len(contents)], contents[:])
    42  
    43  	//add the previous hash
    44  	copy(newSlice[len(contents):], hash[:])
    45  
    46  	//compute new hash
    47  	hash = util.ComputeSHA256(newSlice)
    48  
    49  	return hash
    50  }
    51  
    52  //HashFilesInDir computes h=hash(h,file bytes) for each file in a directory
    53  //Directory entries are traversed recursively. In the end a single
    54  //hash value is returned for the entire directory structure
    55  func HashFilesInDir(rootDir string, dir string, hash []byte, tw *tar.Writer) ([]byte, error) {
    56  	currentDir := filepath.Join(rootDir, dir)
    57  	logger.Debugf("hashFiles %s", currentDir)
    58  	//ReadDir returns sorted list of files in dir
    59  	fis, err := ioutil.ReadDir(currentDir)
    60  	if err != nil {
    61  		return hash, fmt.Errorf("ReadDir failed %s\n", err)
    62  	}
    63  	for _, fi := range fis {
    64  		name := filepath.Join(dir, fi.Name())
    65  		if fi.IsDir() {
    66  			var err error
    67  			hash, err = HashFilesInDir(rootDir, name, hash, tw)
    68  			if err != nil {
    69  				return hash, err
    70  			}
    71  			continue
    72  		}
    73  		fqp := filepath.Join(rootDir, name)
    74  		buf, err := ioutil.ReadFile(fqp)
    75  		if err != nil {
    76  			logger.Errorf("Error reading %s\n", err)
    77  			return hash, err
    78  		}
    79  
    80  		//get the new hash from file contents
    81  		hash = ComputeHash(buf, hash)
    82  
    83  		if tw != nil {
    84  			is := bytes.NewReader(buf)
    85  			if err = cutil.WriteStreamToPackage(is, fqp, filepath.Join("src", name), tw); err != nil {
    86  				return hash, fmt.Errorf("Error adding file to tar %s", err)
    87  			}
    88  		}
    89  	}
    90  	return hash, nil
    91  }
    92  
    93  //IsCodeExist checks the chaincode if exists
    94  func IsCodeExist(tmppath string) error {
    95  	file, err := os.Open(tmppath)
    96  	if err != nil {
    97  		return fmt.Errorf("Could not open file %s", err)
    98  	}
    99  
   100  	fi, err := file.Stat()
   101  	if err != nil {
   102  		return fmt.Errorf("Could not stat file %s", err)
   103  	}
   104  
   105  	if !fi.IsDir() {
   106  		return fmt.Errorf("File %s is not dir\n", file.Name())
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  type DockerBuildOptions struct {
   113  	Image        string
   114  	Env          []string
   115  	Cmd          string
   116  	InputStream  io.Reader
   117  	OutputStream io.Writer
   118  }
   119  
   120  //-------------------------------------------------------------------------------------------
   121  // DockerBuild
   122  //-------------------------------------------------------------------------------------------
   123  // This function allows a "pass-through" build of chaincode within a docker container as
   124  // an alternative to using standard "docker build" + Dockerfile mechanisms.  The plain docker
   125  // build is somewhat limiting due to the resulting image that is a superset composition of
   126  // the build-time and run-time environments.  This superset can be problematic on several
   127  // fronts, such as a bloated image size, and additional security exposure associated with
   128  // applications that are not needed, etc.
   129  //
   130  // Therefore, this mechanism creates a pipeline consisting of an ephemeral docker
   131  // container that accepts source code as input, runs some function (e.g. "go build"), and
   132  // outputs the result.  The intention is that this output will be consumed as the basis of
   133  // a streamlined container by installing the output into a downstream docker-build based on
   134  // an appropriate minimal image.
   135  //
   136  // The input parameters are fairly simple:
   137  //      - Image:        (optional) The builder image to use or "chaincode.builder"
   138  //      - Env:          (optional) environment variables for the build environment.
   139  //      - Cmd:          The command to execute inside the container.
   140  //      - InputStream:  A tarball of files that will be expanded into /chaincode/input.
   141  //      - OutputStream: A tarball of files that will be gathered from /chaincode/output
   142  //                      after successful execution of Cmd.
   143  //-------------------------------------------------------------------------------------------
   144  func DockerBuild(opts DockerBuildOptions) error {
   145  	client, err := cutil.NewDockerClient()
   146  	if err != nil {
   147  		return fmt.Errorf("Error creating docker client: %s", err)
   148  	}
   149  	if opts.Image == "" {
   150  		opts.Image = cutil.GetDockerfileFromConfig("chaincode.builder")
   151  		if opts.Image == "" {
   152  			return fmt.Errorf("No image provided and \"chaincode.builder\" default does not exist")
   153  		}
   154  	}
   155  
   156  	logger.Debugf("Attempting build with image %s", opts.Image)
   157  
   158  	//-----------------------------------------------------------------------------------
   159  	// Ensure the image exists locally, or pull it from a registry if it doesn't
   160  	//-----------------------------------------------------------------------------------
   161  	_, err = client.InspectImage(opts.Image)
   162  	if err != nil {
   163  		logger.Debugf("Image %s does not exist locally, attempt pull", opts.Image)
   164  
   165  		err = client.PullImage(docker.PullImageOptions{Repository: opts.Image}, docker.AuthConfiguration{})
   166  		if err != nil {
   167  			return fmt.Errorf("Failed to pull %s: %s", opts.Image, err)
   168  		}
   169  	}
   170  
   171  	//-----------------------------------------------------------------------------------
   172  	// Create an ephemeral container, armed with our Env/Cmd
   173  	//-----------------------------------------------------------------------------------
   174  	container, err := client.CreateContainer(docker.CreateContainerOptions{
   175  		Config: &docker.Config{
   176  			Image:        opts.Image,
   177  			Env:          opts.Env,
   178  			Cmd:          []string{"/bin/sh", "-c", opts.Cmd},
   179  			AttachStdout: true,
   180  			AttachStderr: true,
   181  		},
   182  	})
   183  	if err != nil {
   184  		return fmt.Errorf("Error creating container: %s", err)
   185  	}
   186  	defer client.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID})
   187  
   188  	//-----------------------------------------------------------------------------------
   189  	// Upload our input stream
   190  	//-----------------------------------------------------------------------------------
   191  	err = client.UploadToContainer(container.ID, docker.UploadToContainerOptions{
   192  		Path:        "/chaincode/input",
   193  		InputStream: opts.InputStream,
   194  	})
   195  	if err != nil {
   196  		return fmt.Errorf("Error uploading input to container: %s", err)
   197  	}
   198  
   199  	//-----------------------------------------------------------------------------------
   200  	// Attach stdout buffer to capture possible compilation errors
   201  	//-----------------------------------------------------------------------------------
   202  	stdout := bytes.NewBuffer(nil)
   203  	_, err = client.AttachToContainerNonBlocking(docker.AttachToContainerOptions{
   204  		Container:    container.ID,
   205  		OutputStream: stdout,
   206  		ErrorStream:  stdout,
   207  		Logs:         true,
   208  		Stdout:       true,
   209  		Stderr:       true,
   210  		Stream:       true,
   211  	})
   212  	if err != nil {
   213  		return fmt.Errorf("Error attaching to container: %s", err)
   214  	}
   215  
   216  	//-----------------------------------------------------------------------------------
   217  	// Launch the actual build, realizing the Env/Cmd specified at container creation
   218  	//-----------------------------------------------------------------------------------
   219  	err = client.StartContainer(container.ID, nil)
   220  	if err != nil {
   221  		return fmt.Errorf("Error executing build: %s \"%s\"", err, stdout.String())
   222  	}
   223  
   224  	//-----------------------------------------------------------------------------------
   225  	// Wait for the build to complete and gather the return value
   226  	//-----------------------------------------------------------------------------------
   227  	retval, err := client.WaitContainer(container.ID)
   228  	if err != nil {
   229  		return fmt.Errorf("Error waiting for container to complete: %s", err)
   230  	}
   231  	if retval > 0 {
   232  		return fmt.Errorf("Error returned from build: %d \"%s\"", retval, stdout.String())
   233  	}
   234  
   235  	//-----------------------------------------------------------------------------------
   236  	// Finally, download the result
   237  	//-----------------------------------------------------------------------------------
   238  	err = client.DownloadFromContainer(container.ID, docker.DownloadFromContainerOptions{
   239  		Path:         "/chaincode/output/.",
   240  		OutputStream: opts.OutputStream,
   241  	})
   242  	if err != nil {
   243  		return fmt.Errorf("Error downloading output: %s", err)
   244  	}
   245  
   246  	return nil
   247  }