Skip to content

Commit ee778bc

Browse files
committed
Break out small functions and add godoc
FAB-16108 #done Change-Id: I3768568869b653623272e0587d6de8586c476083 Signed-off-by: Matthew Sykes <sykesmat@us.ibm.com>
1 parent 109fccb commit ee778bc

File tree

2 files changed

+90
-99
lines changed

2 files changed

+90
-99
lines changed

core/container/externalbuilder/externalbuilder.go

Lines changed: 86 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -25,32 +25,33 @@ import (
2525
)
2626

2727
var (
28+
// DefaultEnvWhitelist enumerates the list of environment variables that are
29+
// implicitly propagated to external builder and launcher commands.
2830
DefaultEnvWhitelist = []string{"LD_LIBRARY_PATH", "LIBPATH", "PATH", "TMPDIR"}
29-
logger = flogging.MustGetLogger("chaincode.externalbuilder")
30-
)
3131

32-
const MetadataFile = "metadata.json"
32+
logger = flogging.MustGetLogger("chaincode.externalbuilder")
33+
)
3334

35+
// BuildInfo contains metadata is that is saved to the local file system with the
36+
// assets generated by an external builder. This is used to associate build output
37+
// with the builder that generated it.
3438
type BuildInfo struct {
39+
// BuilderName is the user provided name of the external builder.
3540
BuilderName string `json:"builder_name"`
3641
}
3742

43+
// A Detector is responsible for orchestrating the external builder detection and
44+
// build process.
3845
type Detector struct {
46+
// DurablePath is the file system location where chaincode assets are persisted.
3947
DurablePath string
40-
Builders []*Builder
48+
// Builders are the builders that detect and build processing will use.
49+
Builders []*Builder
4150
}
4251

43-
func (d *Detector) Detect(buildContext *BuildContext) *Builder {
44-
for _, builder := range d.Builders {
45-
if builder.Detect(buildContext) {
46-
return builder
47-
}
48-
}
49-
return nil
50-
}
51-
52-
// CachedBuild returns a build instance that was already built, or nil, or
53-
// when an unexpected error is encountered, an error.
52+
// CachedBuild returns a build instance that was already built or nil when no
53+
// instance has been found. An error is returned only when an unexpected
54+
// condition is encountered.
5455
func (d *Detector) CachedBuild(ccid string) (*Instance, error) {
5556
durablePath := filepath.Join(d.DurablePath, SanitizeCCIDPath(ccid))
5657
_, err := os.Stat(durablePath)
@@ -67,9 +68,8 @@ func (d *Detector) CachedBuild(ccid string) (*Instance, error) {
6768
return nil, errors.WithMessagef(err, "could not read '%s' for build info", buildInfoPath)
6869
}
6970

70-
buildInfo := &BuildInfo{}
71-
err = json.Unmarshal(buildInfoData, buildInfo)
72-
if err != nil {
71+
var buildInfo BuildInfo
72+
if err := json.Unmarshal(buildInfoData, &buildInfo); err != nil {
7373
return nil, errors.WithMessagef(err, "malformed build info at '%s'", buildInfoPath)
7474
}
7575

@@ -87,11 +87,16 @@ func (d *Detector) CachedBuild(ccid string) (*Instance, error) {
8787
return nil, errors.Errorf("chaincode '%s' was already built with builder '%s', but that builder is no longer available", ccid, buildInfo.BuilderName)
8888
}
8989

90+
// Build executes the external builder detect and build process.
91+
//
92+
// Before running the detect and build process, the detector first checks the
93+
// durable path for the results of a previous build for the provided package.
94+
// If found, the detect and build process is skipped and the existing instance
95+
// is returned.
9096
func (d *Detector) Build(ccid string, md *persistence.ChaincodePackageMetadata, codeStream io.Reader) (*Instance, error) {
97+
// A small optimization: prevent exploding the build package out into the
98+
// file system unless there are external builders defined.
9199
if len(d.Builders) == 0 {
92-
// A small optimization, especially while the launcher feature is under development
93-
// let's not explode the build package out into the filesystem unless there are
94-
// external builders to run against it.
95100
return nil, nil
96101
}
97102

@@ -110,7 +115,7 @@ func (d *Detector) Build(ccid string, md *persistence.ChaincodePackageMetadata,
110115
}
111116
defer buildContext.Cleanup()
112117

113-
builder := d.Detect(buildContext)
118+
builder := d.detect(buildContext)
114119
if builder == nil {
115120
logger.Debugf("no external builder detected for %s", ccid)
116121
return nil, nil
@@ -128,7 +133,7 @@ func (d *Detector) Build(ccid string, md *persistence.ChaincodePackageMetadata,
128133

129134
err = os.Mkdir(durablePath, 0700)
130135
if err != nil {
131-
return nil, errors.WithMessagef(err, "could not create dir '%s' to persist build ouput", durablePath)
136+
return nil, errors.WithMessagef(err, "could not create dir '%s' to persist build output", durablePath)
132137
}
133138

134139
buildInfo, err := json.Marshal(&BuildInfo{
@@ -166,6 +171,17 @@ func (d *Detector) Build(ccid string, md *persistence.ChaincodePackageMetadata,
166171
}, nil
167172
}
168173

174+
func (d *Detector) detect(buildContext *BuildContext) *Builder {
175+
for _, builder := range d.Builders {
176+
if builder.Detect(buildContext) {
177+
return builder
178+
}
179+
}
180+
return nil
181+
}
182+
183+
// BuildContext holds references to the various assets locations necessary to
184+
// execute the detect, build, release, and run programs for external builders
169185
type BuildContext struct {
170186
CCID string
171187
Metadata *persistence.ChaincodePackageMetadata
@@ -176,6 +192,11 @@ type BuildContext struct {
176192
BldDir string
177193
}
178194

195+
// NewBuildContext creates the directories required to runt he external
196+
// build process and extracts the chaincode package assets.
197+
//
198+
// Users of the BuildContext must call Cleanup when the build process is
199+
// complete to remove the transient file system assets.
179200
func NewBuildContext(ccid string, md *persistence.ChaincodePackageMetadata, codePackage io.Reader) (bc *BuildContext, err error) {
180201
scratchDir, err := ioutil.TempDir("", "fabric-"+SanitizeCCIDPath(ccid))
181202
if err != nil {
@@ -229,12 +250,15 @@ func NewBuildContext(ccid string, md *persistence.ChaincodePackageMetadata, code
229250
}, nil
230251
}
231252

253+
// Cleanup removes the build context artifacts.
232254
func (bc *BuildContext) Cleanup() {
233255
os.RemoveAll(bc.ScratchDir)
234256
}
235257

236258
var pkgIDreg = regexp.MustCompile("[<>:\"/\\\\|\\?\\*&]")
237259

260+
// SanitizeCCIDPath is used to ensure that special characters are removed from
261+
// file names.
238262
func SanitizeCCIDPath(ccid string) string {
239263
return pkgIDreg.ReplaceAllString(ccid, "-")
240264
}
@@ -256,16 +280,18 @@ func writeMetadataFile(ccid string, md *persistence.ChaincodePackageMetadata, ds
256280
return errors.Wrap(err, "failed to marshal build metadata into JSON")
257281
}
258282

259-
return ioutil.WriteFile(filepath.Join(dst, MetadataFile), mdBytes, 0700)
283+
return ioutil.WriteFile(filepath.Join(dst, "metadata.json"), mdBytes, 0700)
260284
}
261285

286+
// A Builder is used to interact with an external chaincode builder and launcher.
262287
type Builder struct {
263288
EnvWhitelist []string
264289
Location string
265290
Logger *flogging.FabricLogger
266291
Name string
267292
}
268293

294+
// CreateBuilders will construct builders from the peer configuration.
269295
func CreateBuilders(builderConfs []peer.ExternalBuilder) []*Builder {
270296
var builders []*Builder
271297
for _, builderConf := range builderConfs {
@@ -279,11 +305,12 @@ func CreateBuilders(builderConfs []peer.ExternalBuilder) []*Builder {
279305
return builders
280306
}
281307

308+
// Detect runs the `detect` script.
282309
func (b *Builder) Detect(buildContext *BuildContext) bool {
283310
detect := filepath.Join(b.Location, "bin", "detect")
284311
cmd := b.NewCommand(detect, buildContext.SourceDir, buildContext.MetadataDir)
285312

286-
err := RunCommand(b.Logger, cmd)
313+
err := b.runCommand(cmd)
287314
if err != nil {
288315
logger.Debugf("builder '%s' detect failed: %s", b.Name, err)
289316
return false
@@ -292,18 +319,20 @@ func (b *Builder) Detect(buildContext *BuildContext) bool {
292319
return true
293320
}
294321

322+
// Build runs the `build` script.
295323
func (b *Builder) Build(buildContext *BuildContext) error {
296324
build := filepath.Join(b.Location, "bin", "build")
297325
cmd := b.NewCommand(build, buildContext.SourceDir, buildContext.MetadataDir, buildContext.BldDir)
298326

299-
err := RunCommand(b.Logger, cmd)
327+
err := b.runCommand(cmd)
300328
if err != nil {
301329
return errors.Wrapf(err, "external builder '%s' failed", b.Name)
302330
}
303331

304332
return nil
305333
}
306334

335+
// Release runs the `release` script.
307336
func (b *Builder) Release(buildContext *BuildContext) error {
308337
release := filepath.Join(b.Location, "bin", "release")
309338

@@ -312,52 +341,58 @@ func (b *Builder) Release(buildContext *BuildContext) error {
312341
b.Logger.Debugf("Skipping release step for '%s' as no release binary found", buildContext.CCID)
313342
return nil
314343
}
315-
316344
if err != nil {
317345
return errors.WithMessagef(err, "could not stat release binary '%s'", release)
318346
}
319347

320348
cmd := b.NewCommand(release, buildContext.BldDir, buildContext.ReleaseDir)
321-
322-
if err := RunCommand(b.Logger, cmd); err != nil {
349+
err = b.runCommand(cmd)
350+
if err != nil {
323351
return errors.Wrapf(err, "builder '%s' release failed", b.Name)
324352
}
325353

326354
return nil
327355
}
328356

329-
// RunConfig is serialized to disk when launching
330-
type RunConfig struct {
357+
// runConfig is serialized to disk when launching.
358+
type runConfig struct {
331359
CCID string `json:"chaincode_id"`
332360
PeerAddress string `json:"peer_address"`
333-
ClientCert string `json:"client_cert"` // PEM encoded client certifcate
361+
ClientCert string `json:"client_cert"` // PEM encoded client certificate
334362
ClientKey string `json:"client_key"` // PEM encoded client key
335363
RootCert string `json:"root_cert"` // PEM encoded peer chaincode certificate
336364
}
337365

338-
func (b *Builder) Run(ccid, bldDir string, peerConnection *ccintf.PeerConnection) (*Session, error) {
339-
lc := &RunConfig{
340-
PeerAddress: peerConnection.Address,
341-
CCID: ccid,
366+
func newRunConfig(ccid string, peerConnection *ccintf.PeerConnection) runConfig {
367+
var tlsConfig ccintf.TLSConfig
368+
if peerConnection.TLSConfig != nil {
369+
tlsConfig = *peerConnection.TLSConfig
342370
}
343371

344-
if peerConnection.TLSConfig != nil {
345-
lc.ClientCert = string(peerConnection.TLSConfig.ClientCert)
346-
lc.ClientKey = string(peerConnection.TLSConfig.ClientKey)
347-
lc.RootCert = string(peerConnection.TLSConfig.RootCert)
372+
return runConfig{
373+
PeerAddress: peerConnection.Address,
374+
CCID: ccid,
375+
ClientCert: string(tlsConfig.ClientCert),
376+
ClientKey: string(tlsConfig.ClientKey),
377+
RootCert: string(tlsConfig.RootCert),
348378
}
379+
}
349380

381+
// Run starts the `run` script and returns a Session that can be used to
382+
// signal it and wait for termination.
383+
func (b *Builder) Run(ccid, bldDir string, peerConnection *ccintf.PeerConnection) (*Session, error) {
350384
launchDir, err := ioutil.TempDir("", "fabric-run")
351385
if err != nil {
352386
return nil, errors.WithMessage(err, "could not create temp run dir")
353387
}
354388

355-
marshaledLC, err := json.Marshal(lc)
389+
rc := newRunConfig(ccid, peerConnection)
390+
marshaledRC, err := json.Marshal(rc)
356391
if err != nil {
357392
return nil, errors.WithMessage(err, "could not marshal run config")
358393
}
359394

360-
if err := ioutil.WriteFile(filepath.Join(launchDir, "chaincode.json"), marshaledLC, 0600); err != nil {
395+
if err := ioutil.WriteFile(filepath.Join(launchDir, "chaincode.json"), marshaledRC, 0600); err != nil {
361396
return nil, errors.WithMessage(err, "could not write root cert")
362397
}
363398

@@ -377,6 +412,15 @@ func (b *Builder) Run(ccid, bldDir string, peerConnection *ccintf.PeerConnection
377412
return sess, nil
378413
}
379414

415+
// runCommand runs a command and waits for it to complete.
416+
func (b *Builder) runCommand(cmd *exec.Cmd) error {
417+
sess, err := Start(b.Logger, cmd)
418+
if err != nil {
419+
return err
420+
}
421+
return sess.Wait()
422+
}
423+
380424
// NewCommand creates an exec.Cmd that is configured to prune the calling
381425
// environment down to the environment variables specified in the external
382426
// builder's EnvironmentWhitelist and the DefaultEnvWhitelist.
@@ -408,11 +452,3 @@ func contains(envWhiteList []string, key string) bool {
408452
}
409453
return false
410454
}
411-
412-
func RunCommand(logger *flogging.FabricLogger, cmd *exec.Cmd) error {
413-
sess, err := Start(logger, cmd)
414-
if err != nil {
415-
return err
416-
}
417-
return sess.Wait()
418-
}

core/container/externalbuilder/externalbuilder_test.go

Lines changed: 4 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"fmt"
1111
"io/ioutil"
1212
"os"
13-
"os/exec"
1413
"path/filepath"
1514
"strings"
1615

@@ -21,7 +20,6 @@ import (
2120
"github.com/hyperledger/fabric/core/peer"
2221
. "github.com/onsi/ginkgo"
2322
. "github.com/onsi/gomega"
24-
"github.com/onsi/gomega/gbytes"
2523
"go.uber.org/zap"
2624
"go.uber.org/zap/zapcore"
2725
)
@@ -131,7 +129,9 @@ var _ = Describe("externalbuilder", func() {
131129

132130
Context("when no builder can be found", func() {
133131
BeforeEach(func() {
134-
detector.Builders = nil
132+
detector.Builders = externalbuilder.CreateBuilders([]peer.ExternalBuilder{
133+
{Path: "bad1", Name: "bad1"},
134+
})
135135
})
136136

137137
It("returns a nil instance", func() {
@@ -157,7 +157,7 @@ var _ = Describe("externalbuilder", func() {
157157

158158
It("wraps and returns the error", func() {
159159
_, err := detector.Build("fake-package-id", md, codePackage)
160-
Expect(err).To(MatchError("could not create dir 'path/to/nowhere/fake-package-id' to persist build ouput: mkdir path/to/nowhere/fake-package-id: no such file or directory"))
160+
Expect(err).To(MatchError("could not create dir 'path/to/nowhere/fake-package-id' to persist build output: mkdir path/to/nowhere/fake-package-id: no such file or directory"))
161161
})
162162
})
163163
})
@@ -364,51 +364,6 @@ var _ = Describe("externalbuilder", func() {
364364
})
365365
})
366366

367-
Describe("RunCommand", func() {
368-
var (
369-
logger *flogging.FabricLogger
370-
buf *gbytes.Buffer
371-
)
372-
373-
BeforeEach(func() {
374-
buf = gbytes.NewBuffer()
375-
enc := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{MessageKey: "msg"})
376-
core := zapcore.NewCore(enc, zapcore.AddSync(buf), zap.NewAtomicLevel())
377-
logger = flogging.NewFabricLogger(zap.New(core).Named("logger"))
378-
})
379-
380-
It("runs the command, directs stderr to the logger, and includes the command name", func() {
381-
cmd := exec.Command("/bin/sh", "-c", `echo stdout && echo stderr >&2`)
382-
err := externalbuilder.RunCommand(logger, cmd)
383-
Expect(err).NotTo(HaveOccurred())
384-
Expect(buf).To(gbytes.Say("stderr\t" + `{"command": "sh"}` + "\n"))
385-
})
386-
387-
Context("when start fails", func() {
388-
It("returns the error", func() {
389-
cmd := exec.Command("nonsense-program")
390-
err := externalbuilder.RunCommand(logger, cmd)
391-
Expect(err).To(HaveOccurred())
392-
393-
execError, ok := err.(*exec.Error)
394-
Expect(ok).To(BeTrue())
395-
Expect(execError.Name).To(Equal("nonsense-program"))
396-
})
397-
})
398-
399-
Context("when the process exits with a non-zero return", func() {
400-
It("returns the exec.ExitErr for the command", func() {
401-
cmd := exec.Command("false")
402-
err := externalbuilder.RunCommand(logger, cmd)
403-
Expect(err).To(HaveOccurred())
404-
405-
exitErr, ok := err.(*exec.ExitError)
406-
Expect(ok).To(BeTrue())
407-
Expect(exitErr.ExitCode()).To(Equal(1))
408-
})
409-
})
410-
})
411-
412367
Describe("SanitizeCCIDPath", func() {
413368
It("forbids the set of forbidden windows characters", func() {
414369
sanitizedPath := externalbuilder.SanitizeCCIDPath(`<>:"/\|?*&`)

0 commit comments

Comments
 (0)