Skip to content

Commit a083360

Browse files
authored
Merge pull request #349 from hyperledger-firefly/fix-deploy
Verify stack type on deploy command and validate chaincode
2 parents 9ff1c32 + 64240dd commit a083360

11 files changed

Lines changed: 240 additions & 30 deletions

File tree

cmd/deploy_ethereum.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/hyperledger-firefly/cli/internal/docker"
2424
"github.com/hyperledger-firefly/cli/internal/log"
2525
"github.com/hyperledger-firefly/cli/internal/stacks"
26+
"github.com/hyperledger-firefly/cli/pkg/types"
2627
"github.com/spf13/cobra"
2728
)
2829

@@ -55,6 +56,9 @@ solc --combined-json abi,bin contract.sol > contract.json
5556
if err := stackManager.LoadStack(stackName); err != nil {
5657
return err
5758
}
59+
if !stackManager.Stack.BlockchainProvider.Equals(types.BlockchainProviderEthereum) {
60+
return fmt.Errorf("stack '%s' is not an Ethereum stack (blockchain provider: %s). Use 'ff deploy fabric' for Fabric stacks", stackName, stackManager.Stack.BlockchainProvider)
61+
}
5862
contractNames, err := stackManager.GetContracts(filename, args[2:])
5963
if err != nil {
6064
return err

cmd/deploy_fabric.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/hyperledger-firefly/cli/internal/docker"
2424
"github.com/hyperledger-firefly/cli/internal/log"
2525
"github.com/hyperledger-firefly/cli/internal/stacks"
26+
"github.com/hyperledger-firefly/cli/pkg/types"
2627
"github.com/spf13/cobra"
2728
)
2829

@@ -49,6 +50,9 @@ var deployFabricCmd = &cobra.Command{
4950
if err := stackManager.LoadStack(stackName); err != nil {
5051
return err
5152
}
53+
if !stackManager.Stack.BlockchainProvider.Equals(types.BlockchainProviderFabric) {
54+
return fmt.Errorf("stack '%s' is not a Fabric stack (blockchain provider: %s). Use 'ff deploy ethereum' for Ethereum-based stacks", stackName, stackManager.Stack.BlockchainProvider)
55+
}
5256
contractAddress, err := stackManager.DeployContract(filename, filename, 0, args[2:])
5357
if err != nil {
5458
return fmt.Errorf("%s. usage: %s deploy <stack_name> <filename> <channel> <chaincode> <version>", err.Error(), ExecutableName)

cmd/init_cardano.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,10 @@ var initCardanoCmd = &cobra.Command{
4949
return err
5050
}
5151
if err := stackManager.InitStack(&initOptions); err != nil {
52-
if err := stackManager.RemoveStack(); err != nil {
53-
return err
52+
// Try to clean up, but log rather than return any error to not mask the original error
53+
if verr := stackManager.RemoveStack(); verr != nil {
54+
l := log.LoggerFromContext(ctx)
55+
l.Info(fmt.Sprintf("Error whilst removing the stack: %s", verr.Error()))
5456
}
5557
return err
5658
}

cmd/init_fabric.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ var initFabricCmd = &cobra.Command{
5454
return err
5555
}
5656
if err := stackManager.InitStack(&initOptions); err != nil {
57-
if err := stackManager.RemoveStack(); err != nil {
58-
return err
57+
// Try to clean up, but log rather than return any error to not mask the original error
58+
if verr := stackManager.RemoveStack(); verr != nil {
59+
l := log.LoggerFromContext(ctx)
60+
l.Info(fmt.Sprintf("Error whilst removing the stack: %s", verr.Error()))
5961
}
6062
return err
6163
}

cmd/init_tezos.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ var initTezosCmd = &cobra.Command{
5050
return err
5151
}
5252
if err := stackManager.InitStack(&initOptions); err != nil {
53-
if err := stackManager.RemoveStack(); err != nil {
54-
return err
53+
// Try to clean up, but log rather than return any error to not mask the original error
54+
if verr := stackManager.RemoveStack(); verr != nil {
55+
l := log.LoggerFromContext(ctx)
56+
l.Info(fmt.Sprintf("Error whilst removing the stack: %s", verr.Error()))
5557
}
5658
return err
5759
}

cmd/pull.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,9 @@ Pull the images for a stack .
6363
if err := stackManager.LoadStack(stackName); err != nil {
6464
return err
6565
}
66-
if spin != nil {
67-
spin.Start()
68-
}
69-
if err := stackManager.PullStack(&pullOptions); err != nil {
70-
return err
71-
}
72-
if spin != nil {
73-
spin.Stop()
74-
}
75-
return nil
66+
return withSpinner(spin, func() error {
67+
return stackManager.PullStack(&pullOptions)
68+
})
7669
},
7770
}
7871

cmd/root.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"os"
2323

24+
"github.com/briandowns/spinner"
2425
"github.com/mattn/go-isatty"
2526
"github.com/spf13/cobra"
2627

@@ -42,6 +43,16 @@ var logger log.Logger = &log.StdoutLogger{
4243
// name of the executable, this is for the help messages
4344
var ExecutableName string = os.Args[0]
4445

46+
// withSpinner runs fn with the spinner (if configured) running, and stops it
47+
// before returning so an error is not overlaid by the spinner in the user's terminal
48+
func withSpinner(spin *spinner.Spinner, fn func() error) error {
49+
if spin != nil {
50+
spin.Start()
51+
defer spin.Stop()
52+
}
53+
return fn()
54+
}
55+
4556
func GetFireflyASCIIArt() string {
4657
s := ""
4758
s += "\u001b[33m _______ ________ \u001b[0m\n" // yellow

cmd/start.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,12 @@ This command will start a stack and run it in the background.
7171
fmt.Println("this will take a few seconds longer since this is the first time you're running this stack...")
7272
}
7373

74-
if spin != nil {
75-
spin.Start()
76-
}
77-
messages, err := stackManager.StartStack(&startOptions)
78-
if err != nil {
74+
var messages []string
75+
if err := withSpinner(spin, func() (err error) {
76+
messages, err = stackManager.StartStack(&startOptions)
77+
return err
78+
}); err != nil {
7979
return err
80-
}
81-
if spin != nil {
82-
spin.Stop()
8380
}
8481
fmt.Print("\n\n")
8582
for _, message := range messages {

internal/blockchain/fabric/fabric_provider.go

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717
package fabric
1818

1919
import (
20+
"archive/tar"
21+
"compress/gzip"
2022
"context"
2123
_ "embed"
2224
"encoding/json"
2325
"errors"
2426
"fmt"
27+
"io"
2528
"os"
2629
"path"
2730
"path/filepath"
@@ -438,7 +441,7 @@ func (p *FabricProvider) queryInstalled() (*QueryInstalledResponse, error) {
438441
var res *QueryInstalledResponse
439442
err = json.Unmarshal([]byte(str), &res)
440443
if err != nil {
441-
return nil, err
444+
return nil, fmt.Errorf("failed to parse queryinstalled response as JSON: %w. Raw output: %s", err, str)
442445
}
443446
return res, nil
444447
}
@@ -515,11 +518,58 @@ func (p *FabricProvider) GetContracts(filename string, extraArgs []string) ([]st
515518
return []string{filename}, nil
516519
}
517520

521+
// validateChaincodePackage checks that the chaincode package file exists and is a valid tar.gz file.
522+
// Fabric chaincode packages created by 'peer lifecycle chaincode package' are tar.gz files.
523+
func (p *FabricProvider) validateChaincodePackage(filename string) error {
524+
// Check if file exists
525+
fileInfo, err := os.Stat(filename)
526+
if os.IsNotExist(err) {
527+
return fmt.Errorf("chaincode package file not found: %s", filename)
528+
}
529+
if err != nil {
530+
return fmt.Errorf("error accessing chaincode package file: %w", err)
531+
}
532+
if fileInfo.IsDir() {
533+
return fmt.Errorf("chaincode package path is a directory, expected a file: %s", filename)
534+
}
535+
536+
// Verify the file is a valid gzip archive
537+
file, err := os.Open(filename)
538+
if err != nil {
539+
return fmt.Errorf("failed to open chaincode package file: %w", err)
540+
}
541+
defer file.Close()
542+
543+
gzReader, err := gzip.NewReader(file)
544+
if err != nil {
545+
return fmt.Errorf("invalid chaincode package file format. Expected a gzip-compressed tar archive (.tar.gz or .tgz) created by 'peer lifecycle chaincode package'. The file '%s' does not appear to be a valid gzip file: %w", filename, err)
546+
}
547+
defer gzReader.Close()
548+
549+
// Verify the gzip contains a valid tar archive by reading at least one header
550+
tarReader := tar.NewReader(gzReader)
551+
_, err = tarReader.Next()
552+
if err == io.EOF {
553+
return fmt.Errorf("invalid chaincode package: the tar.gz archive is empty")
554+
}
555+
if err != nil {
556+
return fmt.Errorf("invalid chaincode package file format. The file '%s' is gzip-compressed but does not contain a valid tar archive: %w", filename, err)
557+
}
558+
559+
return nil
560+
}
561+
518562
func (p *FabricProvider) DeployContract(filename, contractName, instanceName string, member *types.Organization, extraArgs []string) (*types.ContractDeploymentResult, error) {
519563
filename, err := filepath.Abs(filename)
520564
if err != nil {
521565
return nil, err
522566
}
567+
568+
// Validate that the chaincode package file exists and is a valid gzip file
569+
if err := p.validateChaincodePackage(filename); err != nil {
570+
return nil, err
571+
}
572+
523573
switch {
524574
case len(extraArgs) < 1:
525575
return nil, fmt.Errorf("channel not set")

internal/blockchain/fabric/fabric_provider_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package fabric
22

33
import (
4+
"archive/tar"
5+
"bytes"
6+
"compress/gzip"
47
"context"
58
"fmt"
69
"os"
@@ -404,3 +407,141 @@ func TestRegisterIdentity(t *testing.T) {
404407
})
405408

406409
}
410+
411+
func TestValidateChaincodePackage(t *testing.T) {
412+
p := &FabricProvider{}
413+
414+
// Helper function to create a valid tar.gz file
415+
createValidTarGz := func(t *testing.T, filename string) {
416+
var buf bytes.Buffer
417+
gzWriter := gzip.NewWriter(&buf)
418+
tarWriter := tar.NewWriter(gzWriter)
419+
420+
// Add a file to the tar archive
421+
content := []byte("test chaincode content")
422+
header := &tar.Header{
423+
Name: "metadata.json",
424+
Mode: 0644,
425+
Size: int64(len(content)),
426+
}
427+
err := tarWriter.WriteHeader(header)
428+
assert.NoError(t, err)
429+
_, err = tarWriter.Write(content)
430+
assert.NoError(t, err)
431+
432+
err = tarWriter.Close()
433+
assert.NoError(t, err)
434+
err = gzWriter.Close()
435+
assert.NoError(t, err)
436+
437+
err = os.WriteFile(filename, buf.Bytes(), 0644)
438+
assert.NoError(t, err)
439+
}
440+
441+
t.Run("valid tar.gz file", func(t *testing.T) {
442+
tmpDir := t.TempDir()
443+
validTarGzFile := filepath.Join(tmpDir, "valid.tar.gz")
444+
createValidTarGz(t, validTarGzFile)
445+
446+
err := p.validateChaincodePackage(validTarGzFile)
447+
assert.NoError(t, err)
448+
})
449+
450+
t.Run("file not found", func(t *testing.T) {
451+
err := p.validateChaincodePackage("/nonexistent/path/chaincode.tar.gz")
452+
assert.Error(t, err)
453+
assert.Contains(t, err.Error(), "chaincode package file not found")
454+
})
455+
456+
t.Run("path is a directory", func(t *testing.T) {
457+
tmpDir := t.TempDir()
458+
err := p.validateChaincodePackage(tmpDir)
459+
assert.Error(t, err)
460+
assert.Contains(t, err.Error(), "chaincode package path is a directory")
461+
})
462+
463+
t.Run("invalid - plain text file", func(t *testing.T) {
464+
tmpDir := t.TempDir()
465+
plainTextFile := filepath.Join(tmpDir, "plain.txt")
466+
err := os.WriteFile(plainTextFile, []byte("this is not a gzip file"), 0644)
467+
assert.NoError(t, err)
468+
469+
err = p.validateChaincodePackage(plainTextFile)
470+
assert.Error(t, err)
471+
assert.Contains(t, err.Error(), "invalid chaincode package file format")
472+
assert.Contains(t, err.Error(), "does not appear to be a valid gzip file")
473+
})
474+
475+
t.Run("invalid - zip file", func(t *testing.T) {
476+
tmpDir := t.TempDir()
477+
// ZIP files start with PK (0x50, 0x4B)
478+
zipLikeFile := filepath.Join(tmpDir, "fake.zip")
479+
err := os.WriteFile(zipLikeFile, []byte{0x50, 0x4B, 0x03, 0x04, 0x00, 0x00}, 0644)
480+
assert.NoError(t, err)
481+
482+
err = p.validateChaincodePackage(zipLikeFile)
483+
assert.Error(t, err)
484+
assert.Contains(t, err.Error(), "invalid chaincode package file format")
485+
})
486+
487+
t.Run("invalid - random binary", func(t *testing.T) {
488+
tmpDir := t.TempDir()
489+
randomFile := filepath.Join(tmpDir, "random.bin")
490+
err := os.WriteFile(randomFile, []byte{0xDE, 0xAD, 0xBE, 0xEF}, 0644)
491+
assert.NoError(t, err)
492+
493+
err = p.validateChaincodePackage(randomFile)
494+
assert.Error(t, err)
495+
assert.Contains(t, err.Error(), "invalid chaincode package file format")
496+
})
497+
498+
t.Run("invalid - gzip but not tar (plain gzipped content)", func(t *testing.T) {
499+
tmpDir := t.TempDir()
500+
gzipOnlyFile := filepath.Join(tmpDir, "gzip_only.gz")
501+
502+
var buf bytes.Buffer
503+
gzWriter := gzip.NewWriter(&buf)
504+
_, err := gzWriter.Write([]byte("this is gzipped but not a tar archive"))
505+
assert.NoError(t, err)
506+
err = gzWriter.Close()
507+
assert.NoError(t, err)
508+
509+
err = os.WriteFile(gzipOnlyFile, buf.Bytes(), 0644)
510+
assert.NoError(t, err)
511+
512+
err = p.validateChaincodePackage(gzipOnlyFile)
513+
assert.Error(t, err)
514+
assert.Contains(t, err.Error(), "does not contain a valid tar archive")
515+
})
516+
517+
t.Run("invalid - empty tar.gz", func(t *testing.T) {
518+
tmpDir := t.TempDir()
519+
emptyTarGzFile := filepath.Join(tmpDir, "empty.tar.gz")
520+
521+
var buf bytes.Buffer
522+
gzWriter := gzip.NewWriter(&buf)
523+
tarWriter := tar.NewWriter(gzWriter)
524+
// Close without adding any files
525+
err := tarWriter.Close()
526+
assert.NoError(t, err)
527+
err = gzWriter.Close()
528+
assert.NoError(t, err)
529+
530+
err = os.WriteFile(emptyTarGzFile, buf.Bytes(), 0644)
531+
assert.NoError(t, err)
532+
533+
err = p.validateChaincodePackage(emptyTarGzFile)
534+
assert.Error(t, err)
535+
assert.Contains(t, err.Error(), "tar.gz archive is empty")
536+
})
537+
538+
t.Run("invalid - empty file", func(t *testing.T) {
539+
tmpDir := t.TempDir()
540+
emptyFile := filepath.Join(tmpDir, "empty.tar.gz")
541+
err := os.WriteFile(emptyFile, []byte{}, 0644)
542+
assert.NoError(t, err)
543+
544+
err = p.validateChaincodePackage(emptyFile)
545+
assert.Error(t, err)
546+
})
547+
}

0 commit comments

Comments
 (0)