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 }