Skip to content

Commit 923bf3a

Browse files
committed
[FAB-678|1820] Support peer-side Dockerfile generation
Our deployment payloads carry Dockerfile contents in most contexts. This is problematic for a variety of reasons. For example, it assumes that the client is in a position to understand what docker images are available to each and every peer in the network. Consider that some peers may have different image availability or be running on different architectures. In addition, the client is then responsible for installing a meaningful peer.crt without having any knowledge of the peers the chaincode will need to execute against. It is also a potential source of attack and/or misconfiguration since it provides another method to execute code within the endorser network outside of the normal constraints provided by the chaincode execution environment. To solve all this, this patch splits the chaincode::Platform.WritePackage() into two distinct phases: 1) GetDeploymentPayload() 2) GenerateDockerBuild() Phase (1) is executed in the same context as the previous WritePackage() operation. That is, solely during deployment payload generation via the CLI methods. Phase (2) is now executed only by Endorsers just before they need to create a Docker image from a given codepackage. This phase is responsible for synthesizing an appropriate Dockerfile based on parameters such as the type of chaincode, the architecture of the peer, the certificate of the peer, etc. Fixes FAB-1820 Part of the fix for FAB-678. Change-Id: Ibf04efcde24e3fec30c4b9857b924fbe7654d197 Signed-off-by: Greg Haskins <[email protected]>
1 parent e1e4efc commit 923bf3a

17 files changed

+346
-243
lines changed

bddtests/context_endorser.go

+3-7
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"github.com/DATA-DOG/godog/gherkin"
2727
"github.com/hyperledger/fabric/common/util"
2828
"github.com/hyperledger/fabric/core/chaincode/platforms"
29-
"github.com/hyperledger/fabric/core/container"
3029
pb "github.com/hyperledger/fabric/protos/peer"
3130
"github.com/hyperledger/fabric/protos/utils"
3231
"golang.org/x/net/context"
@@ -55,12 +54,9 @@ func (b *BDDContext) build(spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec,
5554
return nil, err
5655
}
5756

58-
vm, err := container.NewVM()
59-
if err != nil {
60-
return nil, fmt.Errorf("Error getting vm")
61-
}
62-
63-
codePackageBytes, err = vm.BuildChaincodeContainer(spec)
57+
// FIXME: This only returns a deployment spec...the chaincode is not compiled.
58+
// Is compilation needed?
59+
codePackageBytes, err := platforms.GetDeploymentPayload(spec)
6460
if err != nil {
6561
return nil, err
6662
}

core/chaincode/chaincode_support.go

+18-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ limitations under the License.
1717
package chaincode
1818

1919
import (
20-
"bytes"
2120
"fmt"
2221
"io"
2322
"strconv"
@@ -32,6 +31,7 @@ import (
3231
"strings"
3332

3433
"github.com/hyperledger/fabric/common/flogging"
34+
"github.com/hyperledger/fabric/core/chaincode/platforms"
3535
"github.com/hyperledger/fabric/core/chaincode/shim"
3636
"github.com/hyperledger/fabric/core/common/ccprovider"
3737
"github.com/hyperledger/fabric/core/container"
@@ -314,10 +314,19 @@ func (chaincodeSupport *ChaincodeSupport) sendInitOrReady(context context.Contex
314314
func (chaincodeSupport *ChaincodeSupport) getArgsAndEnv(cccid *ccprovider.CCContext, cLang pb.ChaincodeSpec_Type) (args []string, envs []string, err error) {
315315
canName := cccid.GetCanonicalName()
316316
envs = []string{"CORE_CHAINCODE_ID_NAME=" + canName}
317-
//if TLS is enabled, pass TLS material to chaincode
317+
318+
// ----------------------------------------------------------------------------
319+
// Pass TLS options to chaincode
320+
// ----------------------------------------------------------------------------
321+
// Note: The peer certificate is only baked into the image during the build
322+
// phase (see core/chaincode/platforms). This logic below merely assumes the
323+
// image is already configured appropriately and is simply toggling the feature
324+
// on or off. If the peer's x509 has changed since the chaincode was deployed,
325+
// the image may be stale and the admin will need to remove the current containers
326+
// before restarting the peer.
327+
// ----------------------------------------------------------------------------
318328
if chaincodeSupport.peerTLS {
319329
envs = append(envs, "CORE_PEER_TLS_ENABLED=true")
320-
envs = append(envs, "CORE_PEER_TLS_CERT_FILE="+chaincodeSupport.peerTLSCertFile)
321330
if chaincodeSupport.peerTLSSvrHostOrd != "" {
322331
envs = append(envs, "CORE_PEER_TLS_SERVERHOSTOVERRIDE="+chaincodeSupport.peerTLSSvrHostOrd)
323332
}
@@ -540,7 +549,7 @@ func (chaincodeSupport *ChaincodeSupport) Launch(context context.Context, cccid
540549
//launch container if it is a System container or not in dev mode
541550
if (!chaincodeSupport.userRunsCC || cds.ExecEnv == pb.ChaincodeDeploymentSpec_SYSTEM) && (chrte == nil || chrte.handler == nil) {
542551

543-
builder := func() (io.Reader, error) { return bytes.NewBuffer(cds.CodePackage), nil }
552+
builder := func() (io.Reader, error) { return platforms.GenerateDockerBuild(cds) }
544553
err = chaincodeSupport.launchAndWaitForRegister(context, cccid, cds, cLang, builder)
545554
if err != nil {
546555
chaincodeLogger.Errorf("launchAndWaitForRegister failed %s", err)
@@ -600,7 +609,11 @@ func (chaincodeSupport *ChaincodeSupport) Deploy(context context.Context, cccid
600609
return cds, fmt.Errorf("error getting args for chaincode %s", err)
601610
}
602611

603-
var targz io.Reader = bytes.NewBuffer(cds.CodePackage)
612+
targz, err := platforms.GenerateDockerBuild(cds)
613+
if err != nil {
614+
return cds, fmt.Errorf("error converting CodePackage to Docker: %s", err)
615+
}
616+
604617
cir := &container.CreateImageReq{CCID: ccintf.CCID{ChaincodeSpec: cds.ChaincodeSpec, NetworkID: chaincodeSupport.peerNetworkID, PeerID: chaincodeSupport.peerID, ChainID: cccid.ChainID, Version: cccid.Version}, Args: args, Reader: targz, Env: envs}
605618

606619
vmtype, _ := chaincodeSupport.getVMType(cds)

core/chaincode/chaincodetest.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,6 @@ chaincode:
355355
# be appended depedendent upon the chaincode specification.
356356
Dockerfile: |
357357
FROM hyperledger/fabric-ccenv:$(ARCH)-$(PROJECT_VERSION)
358-
COPY src $GOPATH/src
359358
WORKDIR $GOPATH
360359
361360
car:

core/chaincode/platforms/car/package.go

+48-44
Original file line numberDiff line numberDiff line change
@@ -18,77 +18,81 @@ package car
1818

1919
import (
2020
"archive/tar"
21+
"bytes"
2122
"fmt"
2223
"io"
2324
"io/ioutil"
2425
"net/http"
25-
"os"
26+
"net/url"
2627
"strings"
27-
"time"
2828

2929
cutil "github.com/hyperledger/fabric/core/container/util"
3030
pb "github.com/hyperledger/fabric/protos/peer"
3131
)
3232

33-
func download(path string) (string, error) {
34-
if strings.HasPrefix(path, "http://") {
35-
// The file is remote, so we need to download it to a temporary location first
36-
37-
var tmp *os.File
38-
var err error
39-
tmp, err = ioutil.TempFile("", "car")
40-
if err != nil {
41-
return "", fmt.Errorf("Error creating temporary file: %s", err)
42-
}
43-
defer os.Remove(tmp.Name())
44-
defer tmp.Close()
45-
46-
resp, err := http.Get(path)
47-
if err != nil {
48-
return "", fmt.Errorf("Error with HTTP GET: %s", err)
49-
}
50-
defer resp.Body.Close()
51-
52-
_, err = io.Copy(tmp, resp.Body)
53-
if err != nil {
54-
return "", fmt.Errorf("Error downloading bytes: %s", err)
55-
}
56-
57-
return tmp.Name(), nil
33+
func httpDownload(path string) ([]byte, error) {
34+
35+
// The file is remote, so we need to download it first
36+
var err error
37+
38+
resp, err := http.Get(path)
39+
if err != nil {
40+
return nil, fmt.Errorf("Error with HTTP GET: %s", err)
41+
}
42+
defer resp.Body.Close()
43+
44+
buf := bytes.NewBuffer(nil)
45+
46+
// FIXME: Validate maximum size constraints are not violated
47+
_, err = io.Copy(buf, resp.Body)
48+
if err != nil {
49+
return nil, fmt.Errorf("Error downloading bytes: %s", err)
5850
}
5951

60-
return path, nil
52+
return buf.Bytes(), nil
6153
}
6254

63-
// WritePackage satisfies the platform interface for generating a docker package
64-
// that encapsulates the environment for a CAR based chaincode
65-
func (carPlatform *Platform) WritePackage(spec *pb.ChaincodeSpec, tw *tar.Writer) error {
55+
var httpSchemes = map[string]bool{
56+
"http": true,
57+
"https": true,
58+
}
6659

67-
path, err := download(spec.ChaincodeID.Path)
60+
func (carPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) {
61+
62+
path := spec.ChaincodeID.Path
63+
url, err := url.Parse(path)
6864
if err != nil {
69-
return err
65+
return nil, fmt.Errorf("Error decoding %s: %s", path, err)
66+
}
67+
68+
if _, ok := httpSchemes[url.Scheme]; ok {
69+
return httpDownload(path)
70+
} else {
71+
// All we know is its _not_ an HTTP scheme. Assume the path is a local file
72+
// and see if it works.
73+
return ioutil.ReadFile(url.Path)
7074
}
75+
}
76+
77+
func (carPlatform *Platform) GenerateDockerBuild(cds *pb.ChaincodeDeploymentSpec, tw *tar.Writer) (string, error) {
7178

7279
var buf []string
80+
var err error
81+
82+
spec := cds.ChaincodeSpec
7383

7484
//let the executable's name be chaincode ID's name
7585
buf = append(buf, cutil.GetDockerfileFromConfig("chaincode.car.Dockerfile"))
76-
buf = append(buf, "COPY package.car /tmp/package.car")
86+
buf = append(buf, "COPY codepackage.car /tmp/codepackage.car")
7787
// invoking directly for maximum JRE compatiblity
78-
buf = append(buf, fmt.Sprintf("RUN java -jar /usr/local/bin/chaintool buildcar /tmp/package.car -o $GOPATH/bin/%s && rm /tmp/package.car", spec.ChaincodeID.Name))
88+
buf = append(buf, fmt.Sprintf("RUN java -jar /usr/local/bin/chaintool buildcar /tmp/codepackage.car -o $GOPATH/bin/%s && rm /tmp/codepackage.car", spec.ChaincodeID.Name))
7989

8090
dockerFileContents := strings.Join(buf, "\n")
81-
dockerFileSize := int64(len([]byte(dockerFileContents)))
82-
83-
//Make headers identical by using zero time
84-
var zeroTime time.Time
85-
tw.WriteHeader(&tar.Header{Name: "Dockerfile", Size: dockerFileSize, ModTime: zeroTime, AccessTime: zeroTime, ChangeTime: zeroTime})
86-
tw.Write([]byte(dockerFileContents))
8791

88-
err = cutil.WriteFileToPackage(path, "package.car", tw)
92+
err = cutil.WriteBytesToPackage("codepackage.car", cds.CodePackage, tw)
8993
if err != nil {
90-
return err
94+
return "", err
9195
}
9296

93-
return nil
97+
return dockerFileContents, nil
9498
}

core/chaincode/platforms/car/test/car_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func TestCar_BuildImage(t *testing.T) {
4747

4848
chaincodePath := cwd + "/org.hyperledger.chaincode.example02-0.1-SNAPSHOT.car"
4949
spec := &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_CAR, ChaincodeID: &pb.ChaincodeID{Name: "cartest", Path: chaincodePath}, Input: &pb.ChaincodeInput{Args: util.ToChaincodeArgs("f")}}
50-
if _, err := vm.BuildChaincodeContainer(spec); err != nil {
50+
if err := vm.BuildChaincodeContainer(spec); err != nil {
5151
t.Error(err)
5252
}
5353
}

core/chaincode/platforms/golang/package.go

+4-51
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,6 @@ package golang
1919
import (
2020
"archive/tar"
2121
"fmt"
22-
"strings"
23-
"time"
24-
25-
"github.com/spf13/viper"
26-
27-
"errors"
2822

2923
cutil "github.com/hyperledger/fabric/core/container/util"
3024
pb "github.com/hyperledger/fabric/protos/peer"
@@ -34,53 +28,12 @@ import (
3428
//will just package rest of the bytes
3529
func writeChaincodePackage(spec *pb.ChaincodeSpec, tw *tar.Writer) error {
3630

37-
var urlLocation string
38-
if strings.HasPrefix(spec.ChaincodeID.Path, "http://") {
39-
urlLocation = spec.ChaincodeID.Path[7:]
40-
} else if strings.HasPrefix(spec.ChaincodeID.Path, "https://") {
41-
urlLocation = spec.ChaincodeID.Path[8:]
42-
} else {
43-
urlLocation = spec.ChaincodeID.Path
44-
}
45-
46-
if urlLocation == "" {
47-
return errors.New("ChaincodeSpec's path/URL cannot be empty")
48-
}
49-
50-
if strings.LastIndex(urlLocation, "/") == len(urlLocation)-1 {
51-
urlLocation = urlLocation[:len(urlLocation)-1]
52-
}
53-
toks := strings.Split(urlLocation, "/")
54-
if toks == nil || len(toks) == 0 {
55-
return fmt.Errorf("cannot get path components from %s", urlLocation)
56-
}
57-
58-
chaincodeGoName := toks[len(toks)-1]
59-
if chaincodeGoName == "" {
60-
return fmt.Errorf("could not get chaincode name from path %s", urlLocation)
61-
}
62-
63-
var buf []string
64-
65-
buf = append(buf, cutil.GetDockerfileFromConfig("chaincode.golang.Dockerfile"))
66-
//let the executable's name be chaincode ID's name
67-
buf = append(buf, fmt.Sprintf("RUN go install %s && mv $GOPATH/bin/%s $GOPATH/bin/%s", urlLocation, chaincodeGoName, spec.ChaincodeID.Name))
68-
//NOTE-this could have been abstracted away so we could use it for all platforms in a common manner
69-
//However, it would still be docker specific. Hence any such abstraction has to be done in a manner that
70-
//is not just language dependent but also container depenedent. So lets make this change per platform for now
71-
//in the interest of avoiding over-engineering without proper abstraction
72-
if viper.GetBool("peer.tls.enabled") {
73-
buf = append(buf, fmt.Sprintf("COPY src/certs/cert.pem %s", viper.GetString("peer.tls.cert.file")))
31+
urlLocation, err := decodeUrl(spec)
32+
if err != nil {
33+
return fmt.Errorf("could not decode url: %s", err)
7434
}
7535

76-
dockerFileContents := strings.Join(buf, "\n")
77-
dockerFileSize := int64(len([]byte(dockerFileContents)))
78-
79-
//Make headers identical by using zero time
80-
var zeroTime time.Time
81-
tw.WriteHeader(&tar.Header{Name: "Dockerfile", Size: dockerFileSize, ModTime: zeroTime, AccessTime: zeroTime, ChangeTime: zeroTime})
82-
tw.Write([]byte(dockerFileContents))
83-
err := cutil.WriteGopathSrc(tw, urlLocation)
36+
err = cutil.WriteGopathSrc(tw, urlLocation)
8437
if err != nil {
8538
return fmt.Errorf("Error writing Chaincode package contents: %s", err)
8639
}

0 commit comments

Comments
 (0)