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 })