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

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package externalbuilder_test
     8  
     9  import (
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"github.com/hyperledger/fabric/common/flogging"
    17  	"github.com/hyperledger/fabric/core/container/ccintf"
    18  	"github.com/hyperledger/fabric/core/container/externalbuilder"
    19  	"github.com/hyperledger/fabric/core/peer"
    20  	. "github.com/onsi/ginkgo"
    21  	. "github.com/onsi/gomega"
    22  	"go.uber.org/zap"
    23  	"go.uber.org/zap/zapcore"
    24  )
    25  
    26  var _ = Describe("externalbuilder", func() {
    27  	var (
    28  		codePackage *os.File
    29  		logger      *flogging.FabricLogger
    30  		md          []byte
    31  	)
    32  
    33  	BeforeEach(func() {
    34  		var err error
    35  		codePackage, err = os.Open("testdata/normal_archive.tar.gz")
    36  		Expect(err).NotTo(HaveOccurred())
    37  
    38  		md = []byte(`{"some":"fake-metadata"}`)
    39  
    40  		enc := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{MessageKey: "msg"})
    41  		core := zapcore.NewCore(enc, zapcore.AddSync(GinkgoWriter), zap.NewAtomicLevel())
    42  		logger = flogging.NewFabricLogger(zap.New(core).Named("logger"))
    43  	})
    44  
    45  	AfterEach(func() {
    46  		if codePackage != nil {
    47  			codePackage.Close()
    48  		}
    49  	})
    50  
    51  	Describe("NewBuildContext", func() {
    52  		It("creates a new context, including temporary locations", func() {
    53  			buildContext, err := externalbuilder.NewBuildContext("fake-package-id", md, codePackage)
    54  			Expect(err).NotTo(HaveOccurred())
    55  			defer buildContext.Cleanup()
    56  
    57  			Expect(buildContext.ScratchDir).NotTo(BeEmpty())
    58  			Expect(buildContext.ScratchDir).To(BeADirectory())
    59  
    60  			Expect(buildContext.SourceDir).NotTo(BeEmpty())
    61  			Expect(buildContext.SourceDir).To(BeADirectory())
    62  
    63  			Expect(buildContext.ReleaseDir).NotTo(BeEmpty())
    64  			Expect(buildContext.ReleaseDir).To(BeADirectory())
    65  
    66  			Expect(buildContext.BldDir).NotTo(BeEmpty())
    67  			Expect(buildContext.BldDir).To(BeADirectory())
    68  
    69  			Expect(filepath.Join(buildContext.SourceDir, "a/test.file")).To(BeARegularFile())
    70  		})
    71  
    72  		Context("when the archive cannot be extracted", func() {
    73  			It("returns an error", func() {
    74  				codePackage, err := os.Open("testdata/archive_with_absolute.tar.gz")
    75  				Expect(err).NotTo(HaveOccurred())
    76  				defer codePackage.Close()
    77  				_, err = externalbuilder.NewBuildContext("fake-package-id", md, codePackage)
    78  				Expect(err).To(MatchError(ContainSubstring("could not untar source package")))
    79  			})
    80  		})
    81  
    82  		Context("when package id contains inappropriate chars", func() {
    83  			It("replaces them with dash", func() {
    84  				buildContext, err := externalbuilder.NewBuildContext("i&am/pkg:id", md, codePackage)
    85  				Expect(err).NotTo(HaveOccurred())
    86  				Expect(buildContext.ScratchDir).To(ContainSubstring("fabric-i-am-pkg-id"))
    87  			})
    88  		})
    89  	})
    90  
    91  	Describe("Detector", func() {
    92  		var (
    93  			durablePath string
    94  			detector    *externalbuilder.Detector
    95  		)
    96  
    97  		BeforeEach(func() {
    98  			var err error
    99  			durablePath, err = ioutil.TempDir("", "detect-test")
   100  			Expect(err).NotTo(HaveOccurred())
   101  
   102  			detector = &externalbuilder.Detector{
   103  				Builders: externalbuilder.CreateBuilders([]peer.ExternalBuilder{
   104  					{Path: "bad1", Name: "bad1"},
   105  					{Path: "testdata/goodbuilder", Name: "goodbuilder"},
   106  					{Path: "bad2", Name: "bad2"},
   107  				}, "mspid"),
   108  				DurablePath: durablePath,
   109  			}
   110  		})
   111  
   112  		AfterEach(func() {
   113  			if durablePath != "" {
   114  				err := os.RemoveAll(durablePath)
   115  				Expect(err).NotTo(HaveOccurred())
   116  			}
   117  		})
   118  
   119  		Describe("Build", func() {
   120  			It("iterates over all detectors and chooses the one that matches", func() {
   121  				instance, err := detector.Build("fake-package-id", md, codePackage)
   122  				Expect(err).NotTo(HaveOccurred())
   123  				Expect(instance.Builder.Name).To(Equal("goodbuilder"))
   124  			})
   125  
   126  			Context("when no builder can be found", func() {
   127  				BeforeEach(func() {
   128  					detector.Builders = externalbuilder.CreateBuilders([]peer.ExternalBuilder{
   129  						{Path: "bad1", Name: "bad1"},
   130  					}, "mspid")
   131  				})
   132  
   133  				It("returns a nil instance", func() {
   134  					i, err := detector.Build("fake-package-id", md, codePackage)
   135  					Expect(err).NotTo(HaveOccurred())
   136  					Expect(i).To(BeNil())
   137  				})
   138  			})
   139  
   140  			It("persists the build output", func() {
   141  				_, err := detector.Build("fake-package-id", md, codePackage)
   142  				Expect(err).NotTo(HaveOccurred())
   143  
   144  				Expect(filepath.Join(durablePath, "fake-package-id", "bld")).To(BeADirectory())
   145  				Expect(filepath.Join(durablePath, "fake-package-id", "release")).To(BeADirectory())
   146  				Expect(filepath.Join(durablePath, "fake-package-id", "build-info.json")).To(BeARegularFile())
   147  			})
   148  
   149  			Context("when the durable path cannot be created", func() {
   150  				BeforeEach(func() {
   151  					detector.DurablePath = "path/to/nowhere"
   152  				})
   153  
   154  				It("wraps and returns the error", func() {
   155  					_, err := detector.Build("fake-package-id", md, codePackage)
   156  					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"))
   157  				})
   158  			})
   159  		})
   160  
   161  		Describe("CachedBuild", func() {
   162  			var existingInstance *externalbuilder.Instance
   163  
   164  			BeforeEach(func() {
   165  				var err error
   166  				existingInstance, err = detector.Build("fake-package-id", md, codePackage)
   167  				Expect(err).NotTo(HaveOccurred())
   168  
   169  				// ensure the builder will fail if invoked
   170  				detector.Builders[0].Location = "bad-path"
   171  			})
   172  
   173  			It("returns the existing built instance", func() {
   174  				newInstance, err := detector.Build("fake-package-id", md, codePackage)
   175  				Expect(err).NotTo(HaveOccurred())
   176  				Expect(existingInstance).To(Equal(newInstance))
   177  			})
   178  
   179  			When("the build-info is missing", func() {
   180  				BeforeEach(func() {
   181  					err := os.RemoveAll(filepath.Join(durablePath, "fake-package-id", "build-info.json"))
   182  					Expect(err).NotTo(HaveOccurred())
   183  				})
   184  
   185  				It("returns an error", func() {
   186  					_, err := detector.Build("fake-package-id", md, codePackage)
   187  					Expect(err).To(MatchError(ContainSubstring("existing build could not be restored: could not read '")))
   188  				})
   189  			})
   190  
   191  			When("the build-info is corrupted", func() {
   192  				BeforeEach(func() {
   193  					err := ioutil.WriteFile(filepath.Join(durablePath, "fake-package-id", "build-info.json"), []byte("{corrupted"), 0600)
   194  					Expect(err).NotTo(HaveOccurred())
   195  				})
   196  
   197  				It("returns an error", func() {
   198  					_, err := detector.Build("fake-package-id", md, codePackage)
   199  					Expect(err).To(MatchError(ContainSubstring("invalid character 'c' looking for beginning of object key string")))
   200  				})
   201  			})
   202  
   203  			When("the builder is no longer available", func() {
   204  				BeforeEach(func() {
   205  					detector.Builders = detector.Builders[:1]
   206  				})
   207  
   208  				It("returns an error", func() {
   209  					_, err := detector.Build("fake-package-id", md, codePackage)
   210  					Expect(err).To(MatchError("existing build could not be restored: chaincode 'fake-package-id' was already built with builder 'goodbuilder', but that builder is no longer available"))
   211  				})
   212  			})
   213  		})
   214  	})
   215  
   216  	Describe("Builders", func() {
   217  		var (
   218  			builder      *externalbuilder.Builder
   219  			buildContext *externalbuilder.BuildContext
   220  		)
   221  
   222  		BeforeEach(func() {
   223  			builder = &externalbuilder.Builder{
   224  				Location: "testdata/goodbuilder",
   225  				Name:     "goodbuilder",
   226  				Logger:   logger,
   227  				MSPID:    "mspid",
   228  			}
   229  
   230  			var err error
   231  			buildContext, err = externalbuilder.NewBuildContext("fake-package-id", md, codePackage)
   232  			Expect(err).NotTo(HaveOccurred())
   233  		})
   234  
   235  		AfterEach(func() {
   236  			buildContext.Cleanup()
   237  		})
   238  
   239  		Describe("Detect", func() {
   240  			It("detects when the package is handled by the external builder", func() {
   241  				result := builder.Detect(buildContext)
   242  				Expect(result).To(BeTrue())
   243  			})
   244  
   245  			Context("when the detector exits with a non-zero status", func() {
   246  				BeforeEach(func() {
   247  					builder.Location = "testdata/failbuilder"
   248  				})
   249  
   250  				It("returns false", func() {
   251  					result := builder.Detect(buildContext)
   252  					Expect(result).To(BeFalse())
   253  				})
   254  			})
   255  		})
   256  
   257  		Describe("Build", func() {
   258  			It("builds the package by invoking external builder", func() {
   259  				err := builder.Build(buildContext)
   260  				Expect(err).NotTo(HaveOccurred())
   261  			})
   262  
   263  			Context("when the builder exits with a non-zero status", func() {
   264  				BeforeEach(func() {
   265  					builder.Location = "testdata/failbuilder"
   266  					builder.Name = "failbuilder"
   267  				})
   268  
   269  				It("returns an error", func() {
   270  					err := builder.Build(buildContext)
   271  					Expect(err).To(MatchError("external builder 'failbuilder' failed: exit status 1"))
   272  				})
   273  			})
   274  		})
   275  
   276  		Describe("Release", func() {
   277  			It("releases the package by invoking external builder", func() {
   278  				err := builder.Release(buildContext)
   279  				Expect(err).NotTo(HaveOccurred())
   280  			})
   281  
   282  			When("the release binary is not in the builder", func() {
   283  				BeforeEach(func() {
   284  					builder.Location = "bad-builder-location"
   285  				})
   286  
   287  				It("returns no error as release is optional", func() {
   288  					err := builder.Release(buildContext)
   289  					Expect(err).NotTo(HaveOccurred())
   290  				})
   291  			})
   292  
   293  			When("the builder exits with a non-zero status", func() {
   294  				BeforeEach(func() {
   295  					builder.Location = "testdata/failbuilder"
   296  					builder.Name = "failbuilder"
   297  				})
   298  
   299  				It("returns an error", func() {
   300  					err := builder.Release(buildContext)
   301  					Expect(err).To(MatchError("builder 'failbuilder' release failed: exit status 1"))
   302  				})
   303  			})
   304  		})
   305  
   306  		Describe("Run", func() {
   307  			var (
   308  				fakeConnection *ccintf.PeerConnection
   309  				bldDir         string
   310  			)
   311  
   312  			BeforeEach(func() {
   313  				var err error
   314  				bldDir, err = ioutil.TempDir("", "run-test")
   315  				Expect(err).NotTo(HaveOccurred())
   316  
   317  				fakeConnection = &ccintf.PeerConnection{
   318  					Address: "fake-peer-address",
   319  					TLSConfig: &ccintf.TLSConfig{
   320  						ClientCert: []byte("fake-client-cert"),
   321  						ClientKey:  []byte("fake-client-key"),
   322  						RootCert:   []byte("fake-root-cert"),
   323  					},
   324  				}
   325  			})
   326  
   327  			AfterEach(func() {
   328  				if bldDir != "" {
   329  					err := os.RemoveAll(bldDir)
   330  					Expect(err).NotTo(HaveOccurred())
   331  				}
   332  			})
   333  
   334  			It("runs the package by invoking external builder", func() {
   335  				sess, err := builder.Run("test-ccid", bldDir, fakeConnection)
   336  				Expect(err).NotTo(HaveOccurred())
   337  
   338  				errCh := make(chan error)
   339  				go func() { errCh <- sess.Wait() }()
   340  				Eventually(errCh).Should(Receive(BeNil()))
   341  			})
   342  		})
   343  
   344  		Describe("NewCommand", func() {
   345  			It("only propagates expected variables", func() {
   346  				var expectedEnv []string
   347  				for _, key := range externalbuilder.DefaultEnvWhitelist {
   348  					if val, ok := os.LookupEnv(key); ok {
   349  						expectedEnv = append(expectedEnv, fmt.Sprintf("%s=%s", key, val))
   350  					}
   351  				}
   352  
   353  				cmd := builder.NewCommand("/usr/bin/env")
   354  				Expect(cmd.Env).To(ConsistOf(expectedEnv))
   355  
   356  				output, err := cmd.CombinedOutput()
   357  				Expect(err).NotTo(HaveOccurred())
   358  				env := strings.Split(strings.TrimSuffix(string(output), "\n"), "\n")
   359  				Expect(env).To(ConsistOf(expectedEnv))
   360  			})
   361  		})
   362  	})
   363  
   364  	Describe("SanitizeCCIDPath", func() {
   365  		It("forbids the set of forbidden windows characters", func() {
   366  			sanitizedPath := externalbuilder.SanitizeCCIDPath(`<>:"/\|?*&`)
   367  			Expect(sanitizedPath).To(Equal("----------"))
   368  		})
   369  	})
   370  })