|
8 | 8 | "os"
|
9 | 9 | "path/filepath"
|
10 | 10 |
|
| 11 | + "io" |
| 12 | + |
| 13 | + "github.com/fsouza/go-dockerclient" |
11 | 14 | "github.com/hyperledger/fabric/common/util"
|
12 | 15 | cutil "github.com/hyperledger/fabric/core/container/util"
|
13 | 16 | "github.com/op/go-logging"
|
@@ -90,3 +93,126 @@ func IsCodeExist(tmppath string) error {
|
90 | 93 |
|
91 | 94 | return nil
|
92 | 95 | }
|
| 96 | + |
| 97 | +type DockerBuildOptions struct { |
| 98 | + Image string |
| 99 | + Env []string |
| 100 | + Cmd string |
| 101 | + InputStream io.Reader |
| 102 | + OutputStream io.Writer |
| 103 | +} |
| 104 | + |
| 105 | +//------------------------------------------------------------------------------------------- |
| 106 | +// DockerBuild |
| 107 | +//------------------------------------------------------------------------------------------- |
| 108 | +// This function allows a "pass-through" build of chaincode within a docker container as |
| 109 | +// an alternative to using standard "docker build" + Dockerfile mechanisms. The plain docker |
| 110 | +// build is somewhat limiting due to the resulting image that is a superset composition of |
| 111 | +// the build-time and run-time environments. This superset can be problematic on several |
| 112 | +// fronts, such as a bloated image size, and additional security exposure associated with |
| 113 | +// applications that are not needed, etc. |
| 114 | +// |
| 115 | +// Therefore, this mechanism creates a pipeline consisting of an ephemeral docker |
| 116 | +// container that accepts source code as input, runs some function (e.g. "go build"), and |
| 117 | +// outputs the result. The intention is that this output will be consumed as the basis of |
| 118 | +// a streamlined container by installing the output into a downstream docker-build based on |
| 119 | +// an appropriate minimal image. |
| 120 | +// |
| 121 | +// The input parameters are fairly simple: |
| 122 | +// - Image: (optional) The builder image to use or "chaincode.builder" |
| 123 | +// - Env: (optional) environment variables for the build environment. |
| 124 | +// - Cmd: The command to execute inside the container. |
| 125 | +// - InputStream: A tarball of files that will be expanded into /chaincode/input. |
| 126 | +// - OutputStream: A tarball of files that will be gathered from /chaincode/output |
| 127 | +// after successful execution of Cmd. |
| 128 | +//------------------------------------------------------------------------------------------- |
| 129 | +func DockerBuild(opts DockerBuildOptions) error { |
| 130 | + client, err := cutil.NewDockerClient() |
| 131 | + if err != nil { |
| 132 | + return fmt.Errorf("Error creating docker client: %s", err) |
| 133 | + } |
| 134 | + |
| 135 | + if opts.Image == "" { |
| 136 | + opts.Image = cutil.GetDockerfileFromConfig("chaincode.builder") |
| 137 | + if opts.Image == "" { |
| 138 | + return fmt.Errorf("No image provided and \"chaincode.builder\" default does not exist") |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + //----------------------------------------------------------------------------------- |
| 143 | + // Create an ephemeral container, armed with our Env/Cmd |
| 144 | + //----------------------------------------------------------------------------------- |
| 145 | + container, err := client.CreateContainer(docker.CreateContainerOptions{ |
| 146 | + Config: &docker.Config{ |
| 147 | + Image: opts.Image, |
| 148 | + Env: opts.Env, |
| 149 | + Cmd: []string{"/bin/sh", "-c", opts.Cmd}, |
| 150 | + AttachStdout: true, |
| 151 | + AttachStderr: true, |
| 152 | + }, |
| 153 | + }) |
| 154 | + if err != nil { |
| 155 | + return fmt.Errorf("Error creating container: %s", err) |
| 156 | + } |
| 157 | + defer client.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID}) |
| 158 | + |
| 159 | + //----------------------------------------------------------------------------------- |
| 160 | + // Upload our input stream |
| 161 | + //----------------------------------------------------------------------------------- |
| 162 | + err = client.UploadToContainer(container.ID, docker.UploadToContainerOptions{ |
| 163 | + Path: "/chaincode/input", |
| 164 | + InputStream: opts.InputStream, |
| 165 | + }) |
| 166 | + if err != nil { |
| 167 | + return fmt.Errorf("Error uploading input to container: %s", err) |
| 168 | + } |
| 169 | + |
| 170 | + //----------------------------------------------------------------------------------- |
| 171 | + // Attach stdout buffer to capture possible compilation errors |
| 172 | + //----------------------------------------------------------------------------------- |
| 173 | + stdout := bytes.NewBuffer(nil) |
| 174 | + _, err = client.AttachToContainerNonBlocking(docker.AttachToContainerOptions{ |
| 175 | + Container: container.ID, |
| 176 | + OutputStream: stdout, |
| 177 | + ErrorStream: stdout, |
| 178 | + Logs: true, |
| 179 | + Stdout: true, |
| 180 | + Stderr: true, |
| 181 | + Stream: true, |
| 182 | + }) |
| 183 | + if err != nil { |
| 184 | + return fmt.Errorf("Error attaching to container: %s", err) |
| 185 | + } |
| 186 | + |
| 187 | + //----------------------------------------------------------------------------------- |
| 188 | + // Launch the actual build, realizing the Env/Cmd specified at container creation |
| 189 | + //----------------------------------------------------------------------------------- |
| 190 | + err = client.StartContainer(container.ID, nil) |
| 191 | + if err != nil { |
| 192 | + return fmt.Errorf("Error executing build: %s \"%s\"", err, stdout.String()) |
| 193 | + } |
| 194 | + |
| 195 | + //----------------------------------------------------------------------------------- |
| 196 | + // Wait for the build to complete and gather the return value |
| 197 | + //----------------------------------------------------------------------------------- |
| 198 | + retval, err := client.WaitContainer(container.ID) |
| 199 | + if err != nil { |
| 200 | + return fmt.Errorf("Error waiting for container to complete: %s", err) |
| 201 | + } |
| 202 | + if retval > 0 { |
| 203 | + return fmt.Errorf("Error returned from build: %d \"%s\"", retval, stdout.String()) |
| 204 | + } |
| 205 | + |
| 206 | + //----------------------------------------------------------------------------------- |
| 207 | + // Finally, download the result |
| 208 | + //----------------------------------------------------------------------------------- |
| 209 | + err = client.DownloadFromContainer(container.ID, docker.DownloadFromContainerOptions{ |
| 210 | + Path: "/chaincode/output/.", |
| 211 | + OutputStream: opts.OutputStream, |
| 212 | + }) |
| 213 | + if err != nil { |
| 214 | + return fmt.Errorf("Error downloading output: %s", err) |
| 215 | + } |
| 216 | + |
| 217 | + return nil |
| 218 | +} |
0 commit comments