github.com/ahlemtn/fabric@v2.1.1+incompatible/core/container/dockercontroller/dockercontroller_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package dockercontroller
     8  
     9  import (
    10  	"archive/tar"
    11  	"bytes"
    12  	"compress/gzip"
    13  	"context"
    14  	"encoding/hex"
    15  	"errors"
    16  	"fmt"
    17  	"io"
    18  	"io/ioutil"
    19  	"testing"
    20  	"time"
    21  
    22  	docker "github.com/fsouza/go-dockerclient"
    23  	pb "github.com/hyperledger/fabric-protos-go/peer"
    24  	"github.com/hyperledger/fabric/common/flogging/floggingtest"
    25  	"github.com/hyperledger/fabric/common/metrics/disabled"
    26  	"github.com/hyperledger/fabric/common/metrics/metricsfakes"
    27  	"github.com/hyperledger/fabric/common/util"
    28  	"github.com/hyperledger/fabric/core/chaincode/persistence"
    29  	"github.com/hyperledger/fabric/core/container/ccintf"
    30  	"github.com/hyperledger/fabric/core/container/dockercontroller/mock"
    31  	. "github.com/onsi/gomega"
    32  	"github.com/onsi/gomega/gbytes"
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  // This test used to be part of an integration style test in core/container, moved to here
    38  func TestIntegrationPath(t *testing.T) {
    39  	client, err := docker.NewClientFromEnv()
    40  	assert.NoError(t, err)
    41  
    42  	fakePlatformBuilder := &mock.PlatformBuilder{}
    43  	fakePlatformBuilder.GenerateDockerBuildReturns(InMemBuilder{}.Build())
    44  
    45  	dc := DockerVM{
    46  		PeerID:          "",
    47  		NetworkID:       util.GenerateUUID(),
    48  		BuildMetrics:    NewBuildMetrics(&disabled.Provider{}),
    49  		Client:          client,
    50  		PlatformBuilder: fakePlatformBuilder,
    51  	}
    52  	ccid := "simple"
    53  
    54  	instance, err := dc.Build("simple", &persistence.ChaincodePackageMetadata{
    55  		Type: "type",
    56  		Path: "path",
    57  	}, bytes.NewBuffer([]byte("code-package")))
    58  	require.NoError(t, err)
    59  
    60  	assert.Equal(t, &ContainerInstance{
    61  		CCID:     "simple",
    62  		Type:     "TYPE",
    63  		DockerVM: &dc,
    64  	}, instance)
    65  
    66  	err = dc.Start(ccid, "GOLANG", &ccintf.PeerConnection{
    67  		Address: "peer-address",
    68  	})
    69  	require.NoError(t, err)
    70  
    71  	err = dc.Stop(ccid)
    72  	require.NoError(t, err)
    73  }
    74  
    75  var expectedNodeStartScript = `
    76  set -e
    77  if [ -x /chaincode/start.sh ]; then
    78  	/chaincode/start.sh --peer.address peer-address
    79  else
    80  	cd /usr/local/src
    81  	npm start -- --peer.address peer-address
    82  fi
    83  `
    84  
    85  func TestGetArgs(t *testing.T) {
    86  	tests := []struct {
    87  		name         string
    88  		ccType       pb.ChaincodeSpec_Type
    89  		expectedArgs []string
    90  		expectedErr  string
    91  	}{
    92  		{"golang-chaincode", pb.ChaincodeSpec_GOLANG, []string{"chaincode", "-peer.address=peer-address"}, ""},
    93  		{"java-chaincode", pb.ChaincodeSpec_JAVA, []string{"/root/chaincode-java/start", "--peerAddress", "peer-address"}, ""},
    94  		{"node-chaincode", pb.ChaincodeSpec_NODE, []string{"/bin/sh", "-c", expectedNodeStartScript}, ""},
    95  		{"unknown-chaincode", pb.ChaincodeSpec_Type(999), []string{}, "unknown chaincodeType: 999"},
    96  	}
    97  	for _, tc := range tests {
    98  		vm := &DockerVM{}
    99  
   100  		args, err := vm.GetArgs(tc.ccType.String(), "peer-address")
   101  		if tc.expectedErr != "" {
   102  			assert.EqualError(t, err, tc.expectedErr)
   103  			continue
   104  		}
   105  		assert.NoError(t, err)
   106  		assert.Equal(t, tc.expectedArgs, args)
   107  	}
   108  }
   109  
   110  func TestGetEnv(t *testing.T) {
   111  	vm := &DockerVM{
   112  		LoggingEnv: []string{"LOG_ENV=foo"},
   113  		MSPID:      "mspid",
   114  	}
   115  
   116  	t.Run("nil TLS config", func(t *testing.T) {
   117  		env := vm.GetEnv("test", nil)
   118  		assert.Equal(t, []string{"CORE_CHAINCODE_ID_NAME=test", "LOG_ENV=foo", "CORE_PEER_TLS_ENABLED=false", "CORE_PEER_LOCALMSPID=mspid"}, env)
   119  	})
   120  
   121  	t.Run("real TLS config", func(t *testing.T) {
   122  		env := vm.GetEnv("test", &ccintf.TLSConfig{
   123  			ClientKey:  []byte("key"),
   124  			ClientCert: []byte("cert"),
   125  			RootCert:   []byte("root"),
   126  		})
   127  		assert.Equal(t, []string{
   128  			"CORE_CHAINCODE_ID_NAME=test",
   129  			"LOG_ENV=foo",
   130  			"CORE_PEER_TLS_ENABLED=true",
   131  			"CORE_TLS_CLIENT_KEY_PATH=/etc/hyperledger/fabric/client.key",
   132  			"CORE_TLS_CLIENT_CERT_PATH=/etc/hyperledger/fabric/client.crt",
   133  			"CORE_TLS_CLIENT_KEY_FILE=/etc/hyperledger/fabric/client_pem.key",
   134  			"CORE_TLS_CLIENT_CERT_FILE=/etc/hyperledger/fabric/client_pem.crt",
   135  			"CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/peer.crt",
   136  			"CORE_PEER_LOCALMSPID=mspid",
   137  		}, env)
   138  	})
   139  }
   140  
   141  func Test_Start(t *testing.T) {
   142  	gt := NewGomegaWithT(t)
   143  	dockerClient := &mock.DockerClient{}
   144  	dvm := DockerVM{
   145  		BuildMetrics: NewBuildMetrics(&disabled.Provider{}),
   146  		Client:       dockerClient,
   147  	}
   148  
   149  	ccid := "simple:1.0"
   150  	peerConnection := &ccintf.PeerConnection{
   151  		Address: "peer-address",
   152  		TLSConfig: &ccintf.TLSConfig{
   153  			ClientKey:  []byte("key"),
   154  			ClientCert: []byte("cert"),
   155  			RootCert:   []byte("root"),
   156  		},
   157  	}
   158  
   159  	// case 1: dockerClient.CreateContainer returns error
   160  	testError1 := errors.New("junk1")
   161  	dockerClient.CreateContainerReturns(nil, testError1)
   162  	err := dvm.Start(ccid, "GOLANG", peerConnection)
   163  	gt.Expect(err).To(MatchError(testError1))
   164  	dockerClient.CreateContainerReturns(&docker.Container{}, nil)
   165  
   166  	// case 2: dockerClient.UploadToContainer returns error
   167  	testError2 := errors.New("junk2")
   168  	dockerClient.UploadToContainerReturns(testError2)
   169  	err = dvm.Start(ccid, "GOLANG", peerConnection)
   170  	gt.Expect(err.Error()).To(ContainSubstring("junk2"))
   171  	dockerClient.UploadToContainerReturns(nil)
   172  
   173  	// case 3: start called and dockerClient.CreateContainer returns
   174  	// docker.noSuchImgErr and dockerClient.Start returns error
   175  	testError3 := errors.New("junk3")
   176  	dvm.AttachStdOut = true
   177  	dockerClient.CreateContainerReturns(nil, testError3)
   178  	err = dvm.Start(ccid, "GOLANG", peerConnection)
   179  	gt.Expect(err).To(MatchError(testError3))
   180  	dockerClient.CreateContainerReturns(&docker.Container{}, nil)
   181  
   182  	// case 4: GetArgs returns error
   183  	err = dvm.Start(ccid, "FAKE_TYPE", peerConnection)
   184  	gt.Expect(err).To(MatchError("could not get args: unknown chaincodeType: FAKE_TYPE"))
   185  
   186  	// Success cases
   187  	err = dvm.Start(ccid, "GOLANG", peerConnection)
   188  	gt.Expect(err).NotTo(HaveOccurred())
   189  
   190  	// dockerClient.StopContainer returns error
   191  	err = dvm.Start(ccid, "GOLANG", peerConnection)
   192  	gt.Expect(err).NotTo(HaveOccurred())
   193  
   194  	// dockerClient.KillContainer returns error
   195  	err = dvm.Start(ccid, "GOLANG", peerConnection)
   196  	gt.Expect(err).NotTo(HaveOccurred())
   197  
   198  	// dockerClient.RemoveContainer returns error
   199  	err = dvm.Start(ccid, "GOLANG", peerConnection)
   200  	gt.Expect(err).NotTo(HaveOccurred())
   201  
   202  	err = dvm.Start(ccid, "GOLANG", peerConnection)
   203  	gt.Expect(err).NotTo(HaveOccurred())
   204  }
   205  
   206  func Test_streamOutput(t *testing.T) {
   207  	gt := NewGomegaWithT(t)
   208  
   209  	logger, recorder := floggingtest.NewTestLogger(t)
   210  	containerLogger, containerRecorder := floggingtest.NewTestLogger(t)
   211  
   212  	client := &mock.DockerClient{}
   213  	errCh := make(chan error, 1)
   214  	optsCh := make(chan docker.AttachToContainerOptions, 1)
   215  	client.AttachToContainerStub = func(opts docker.AttachToContainerOptions) error {
   216  		optsCh <- opts
   217  		return <-errCh
   218  	}
   219  
   220  	streamOutput(logger, client, "container-name", containerLogger)
   221  
   222  	var opts docker.AttachToContainerOptions
   223  	gt.Eventually(optsCh).Should(Receive(&opts))
   224  	gt.Eventually(opts.Success).Should(BeSent(struct{}{}))
   225  	gt.Eventually(opts.Success).Should(BeClosed())
   226  
   227  	fmt.Fprintf(opts.OutputStream, "message-one\n")
   228  	fmt.Fprintf(opts.OutputStream, "message-two") // does not get written until after stream closed
   229  	gt.Eventually(containerRecorder).Should(gbytes.Say("message-one"))
   230  	gt.Consistently(containerRecorder.Entries).Should(HaveLen(1))
   231  
   232  	close(errCh)
   233  
   234  	gt.Eventually(recorder).Should(gbytes.Say("Container container-name has closed its IO channel"))
   235  	gt.Consistently(recorder.Entries).Should(HaveLen(1))
   236  	gt.Eventually(containerRecorder).Should(gbytes.Say("message-two"))
   237  	gt.Consistently(containerRecorder.Entries).Should(HaveLen(2))
   238  }
   239  
   240  func Test_BuildMetric(t *testing.T) {
   241  	ccid := "simple:1.0"
   242  	client := &mock.DockerClient{}
   243  
   244  	tests := []struct {
   245  		desc           string
   246  		buildErr       bool
   247  		expectedLabels []string
   248  	}{
   249  		{desc: "success", buildErr: false, expectedLabels: []string{"chaincode", "simple:1.0", "success", "true"}},
   250  		{desc: "failure", buildErr: true, expectedLabels: []string{"chaincode", "simple:1.0", "success", "false"}},
   251  	}
   252  	for _, tt := range tests {
   253  		t.Run(tt.desc, func(t *testing.T) {
   254  			gt := NewGomegaWithT(t)
   255  			fakeChaincodeImageBuildDuration := &metricsfakes.Histogram{}
   256  			fakeChaincodeImageBuildDuration.WithReturns(fakeChaincodeImageBuildDuration)
   257  			dvm := DockerVM{
   258  				BuildMetrics: &BuildMetrics{
   259  					ChaincodeImageBuildDuration: fakeChaincodeImageBuildDuration,
   260  				},
   261  				Client: client,
   262  			}
   263  
   264  			if tt.buildErr {
   265  				client.BuildImageReturns(errors.New("Error building image"))
   266  			}
   267  			dvm.buildImage(ccid, &bytes.Buffer{})
   268  
   269  			gt.Expect(fakeChaincodeImageBuildDuration.WithCallCount()).To(Equal(1))
   270  			gt.Expect(fakeChaincodeImageBuildDuration.WithArgsForCall(0)).To(Equal(tt.expectedLabels))
   271  			gt.Expect(fakeChaincodeImageBuildDuration.ObserveArgsForCall(0)).NotTo(BeZero())
   272  			gt.Expect(fakeChaincodeImageBuildDuration.ObserveArgsForCall(0)).To(BeNumerically("<", 1.0))
   273  		})
   274  	}
   275  }
   276  
   277  func Test_Stop(t *testing.T) {
   278  	dvm := DockerVM{Client: &mock.DockerClient{}}
   279  	ccid := "simple"
   280  
   281  	// Success case
   282  	err := dvm.Stop(ccid)
   283  	assert.NoError(t, err)
   284  }
   285  
   286  func Test_Wait(t *testing.T) {
   287  	dvm := DockerVM{}
   288  
   289  	// happy path
   290  	client := &mock.DockerClient{}
   291  	dvm.Client = client
   292  
   293  	client.WaitContainerReturns(99, nil)
   294  	exitCode, err := dvm.Wait("the-name:the-version")
   295  	assert.NoError(t, err)
   296  	assert.Equal(t, 99, exitCode)
   297  	assert.Equal(t, "the-name-the-version", client.WaitContainerArgsForCall(0))
   298  
   299  	// wait fails
   300  	client.WaitContainerReturns(99, errors.New("no-wait-for-you"))
   301  	_, err = dvm.Wait("")
   302  	assert.EqualError(t, err, "no-wait-for-you")
   303  }
   304  
   305  func TestHealthCheck(t *testing.T) {
   306  	client := &mock.DockerClient{}
   307  	vm := &DockerVM{Client: client}
   308  
   309  	err := vm.HealthCheck(context.Background())
   310  	assert.NoError(t, err)
   311  
   312  	client.PingWithContextReturns(errors.New("Error pinging daemon"))
   313  	err = vm.HealthCheck(context.Background())
   314  	assert.Error(t, err)
   315  	assert.Contains(t, err.Error(), "Error pinging daemon")
   316  }
   317  
   318  type testCase struct {
   319  	name           string
   320  	vm             *DockerVM
   321  	ccid           string
   322  	expectedOutput string
   323  }
   324  
   325  func TestGetVMNameForDocker(t *testing.T) {
   326  	tc := []testCase{
   327  		{
   328  			name:           "mycc",
   329  			vm:             &DockerVM{NetworkID: "dev", PeerID: "peer0"},
   330  			ccid:           "mycc:1.0",
   331  			expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-peer0-mycc-1.0")))),
   332  		},
   333  		{
   334  			name:           "mycc-nonetworkid",
   335  			vm:             &DockerVM{PeerID: "peer1"},
   336  			ccid:           "mycc:1.0",
   337  			expectedOutput: fmt.Sprintf("%s-%s", "peer1-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("peer1-mycc-1.0")))),
   338  		},
   339  		{
   340  			name:           "myCC-UCids",
   341  			vm:             &DockerVM{NetworkID: "Dev", PeerID: "Peer0"},
   342  			ccid:           "myCC:1.0",
   343  			expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("Dev-Peer0-myCC-1.0")))),
   344  		},
   345  		{
   346  			name:           "myCC-idsWithSpecialChars",
   347  			vm:             &DockerVM{NetworkID: "Dev$dev", PeerID: "Peer*0"},
   348  			ccid:           "myCC:1.0",
   349  			expectedOutput: fmt.Sprintf("%s-%s", "dev-dev-peer-0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("Dev$dev-Peer*0-myCC-1.0")))),
   350  		},
   351  		{
   352  			name:           "mycc-nopeerid",
   353  			vm:             &DockerVM{NetworkID: "dev"},
   354  			ccid:           "mycc:1.0",
   355  			expectedOutput: fmt.Sprintf("%s-%s", "dev-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-mycc-1.0")))),
   356  		},
   357  		{
   358  			name:           "myCC-LCids",
   359  			vm:             &DockerVM{NetworkID: "dev", PeerID: "peer0"},
   360  			ccid:           "myCC:1.0",
   361  			expectedOutput: fmt.Sprintf("%s-%s", "dev-peer0-mycc-1.0", hex.EncodeToString(util.ComputeSHA256([]byte("dev-peer0-myCC-1.0")))),
   362  		},
   363  	}
   364  
   365  	for _, test := range tc {
   366  		name, err := test.vm.GetVMNameForDocker(test.ccid)
   367  		assert.Nil(t, err, "Expected nil error")
   368  		assert.Equal(t, test.expectedOutput, name, "Unexpected output for test case name: %s", test.name)
   369  	}
   370  
   371  }
   372  
   373  func TestGetVMName(t *testing.T) {
   374  	tc := []testCase{
   375  		{
   376  			name:           "myCC-preserveCase",
   377  			vm:             &DockerVM{NetworkID: "Dev", PeerID: "Peer0"},
   378  			ccid:           "myCC:1.0",
   379  			expectedOutput: fmt.Sprintf("%s", "Dev-Peer0-myCC-1.0"),
   380  		},
   381  	}
   382  
   383  	for _, test := range tc {
   384  		name := test.vm.GetVMName(test.ccid)
   385  		assert.Equal(t, test.expectedOutput, name, "Unexpected output for test case name: %s", test.name)
   386  	}
   387  
   388  }
   389  
   390  func Test_buildImage(t *testing.T) {
   391  	client := &mock.DockerClient{}
   392  	dvm := DockerVM{
   393  		BuildMetrics: NewBuildMetrics(&disabled.Provider{}),
   394  		Client:       client,
   395  		NetworkMode:  "network-mode",
   396  	}
   397  
   398  	err := dvm.buildImage("simple", &bytes.Buffer{})
   399  	assert.NoError(t, err)
   400  	assert.Equal(t, 1, client.BuildImageCallCount())
   401  
   402  	opts := client.BuildImageArgsForCall(0)
   403  	assert.Equal(t, "simple-a7a39b72f29718e653e73503210fbb597057b7a1c77d1fe321a1afcff041d4e1", opts.Name)
   404  	assert.False(t, opts.Pull)
   405  	assert.Equal(t, "network-mode", opts.NetworkMode)
   406  	assert.Equal(t, &bytes.Buffer{}, opts.InputStream)
   407  	assert.NotNil(t, opts.OutputStream)
   408  }
   409  
   410  func Test_buildImageFailure(t *testing.T) {
   411  	client := &mock.DockerClient{}
   412  	client.BuildImageReturns(errors.New("oh-bother-we-failed-badly"))
   413  	dvm := DockerVM{
   414  		BuildMetrics: NewBuildMetrics(&disabled.Provider{}),
   415  		Client:       client,
   416  		NetworkMode:  "network-mode",
   417  	}
   418  
   419  	err := dvm.buildImage("simple", &bytes.Buffer{})
   420  	assert.EqualError(t, err, "oh-bother-we-failed-badly")
   421  }
   422  
   423  func TestBuild(t *testing.T) {
   424  	buildMetrics := NewBuildMetrics(&disabled.Provider{})
   425  	md := &persistence.ChaincodePackageMetadata{
   426  		Type: "type",
   427  		Path: "path",
   428  	}
   429  
   430  	t.Run("when the image does not exist", func(t *testing.T) {
   431  		client := &mock.DockerClient{}
   432  		client.InspectImageReturns(nil, docker.ErrNoSuchImage)
   433  
   434  		fakePlatformBuilder := &mock.PlatformBuilder{}
   435  		fakePlatformBuilder.GenerateDockerBuildReturns(&bytes.Buffer{}, nil)
   436  
   437  		dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics, PlatformBuilder: fakePlatformBuilder}
   438  		_, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package")))
   439  		assert.NoError(t, err, "should have built successfully")
   440  
   441  		assert.Equal(t, 1, client.BuildImageCallCount())
   442  
   443  		require.Equal(t, 1, fakePlatformBuilder.GenerateDockerBuildCallCount())
   444  		ccType, path, codePackageStream := fakePlatformBuilder.GenerateDockerBuildArgsForCall(0)
   445  		assert.Equal(t, "TYPE", ccType)
   446  		assert.Equal(t, "path", path)
   447  		codePackage, err := ioutil.ReadAll(codePackageStream)
   448  		require.NoError(t, err)
   449  		assert.Equal(t, []byte("code-package"), codePackage)
   450  
   451  	})
   452  
   453  	t.Run("when inspecting the image fails", func(t *testing.T) {
   454  		client := &mock.DockerClient{}
   455  		client.InspectImageReturns(nil, errors.New("inspecting-image-fails"))
   456  
   457  		dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics}
   458  		_, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package")))
   459  		assert.EqualError(t, err, "docker image inspection failed: inspecting-image-fails")
   460  
   461  		assert.Equal(t, 0, client.BuildImageCallCount())
   462  	})
   463  
   464  	t.Run("when the image exists", func(t *testing.T) {
   465  		client := &mock.DockerClient{}
   466  
   467  		dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics}
   468  		_, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package")))
   469  		assert.NoError(t, err)
   470  
   471  		assert.Equal(t, 0, client.BuildImageCallCount())
   472  	})
   473  
   474  	t.Run("when the platform builder fails", func(t *testing.T) {
   475  		client := &mock.DockerClient{}
   476  		client.InspectImageReturns(nil, docker.ErrNoSuchImage)
   477  		client.BuildImageReturns(errors.New("no-build-for-you"))
   478  
   479  		fakePlatformBuilder := &mock.PlatformBuilder{}
   480  		fakePlatformBuilder.GenerateDockerBuildReturns(nil, errors.New("fake-builder-error"))
   481  
   482  		dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics, PlatformBuilder: fakePlatformBuilder}
   483  		_, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package")))
   484  		assert.Equal(t, 1, client.InspectImageCallCount())
   485  		assert.Equal(t, 1, fakePlatformBuilder.GenerateDockerBuildCallCount())
   486  		assert.Equal(t, 0, client.BuildImageCallCount())
   487  		assert.EqualError(t, err, "platform builder failed: fake-builder-error")
   488  	})
   489  
   490  	t.Run("when building the image fails", func(t *testing.T) {
   491  		client := &mock.DockerClient{}
   492  		client.InspectImageReturns(nil, docker.ErrNoSuchImage)
   493  		client.BuildImageReturns(errors.New("no-build-for-you"))
   494  
   495  		fakePlatformBuilder := &mock.PlatformBuilder{}
   496  
   497  		dvm := &DockerVM{Client: client, BuildMetrics: buildMetrics, PlatformBuilder: fakePlatformBuilder}
   498  		_, err := dvm.Build("chaincode-name:chaincode-version", md, bytes.NewBuffer([]byte("code-package")))
   499  		assert.Equal(t, 1, client.InspectImageCallCount())
   500  		assert.Equal(t, 1, client.BuildImageCallCount())
   501  		assert.EqualError(t, err, "docker image build failed: no-build-for-you")
   502  	})
   503  }
   504  
   505  type InMemBuilder struct{}
   506  
   507  func (imb InMemBuilder) Build() (io.Reader, error) {
   508  	buf := &bytes.Buffer{}
   509  	fmt.Fprintln(buf, "FROM busybox:latest")
   510  	fmt.Fprintln(buf, `RUN ln -s /bin/true /bin/chaincode`)
   511  	fmt.Fprintln(buf, `CMD ["tail", "-f", "/dev/null"]`)
   512  
   513  	startTime := time.Now()
   514  	inputbuf := bytes.NewBuffer(nil)
   515  	gw := gzip.NewWriter(inputbuf)
   516  	tr := tar.NewWriter(gw)
   517  	tr.WriteHeader(&tar.Header{
   518  		Name:       "Dockerfile",
   519  		Size:       int64(buf.Len()),
   520  		ModTime:    startTime,
   521  		AccessTime: startTime,
   522  		ChangeTime: startTime,
   523  	})
   524  	tr.Write(buf.Bytes())
   525  	tr.Close()
   526  	gw.Close()
   527  	return inputbuf, nil
   528  }
   529  
   530  type mockBuilder struct {
   531  	buildFunc func() (io.Reader, error)
   532  }
   533  
   534  func (m *mockBuilder) Build() (io.Reader, error) {
   535  	return m.buildFunc()
   536  }