Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/deploy_ethereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/hyperledger-firefly/cli/internal/docker"
"github.com/hyperledger-firefly/cli/internal/log"
"github.com/hyperledger-firefly/cli/internal/stacks"
"github.com/hyperledger-firefly/cli/pkg/types"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -55,6 +56,9 @@ solc --combined-json abi,bin contract.sol > contract.json
if err := stackManager.LoadStack(stackName); err != nil {
return err
}
if !stackManager.Stack.BlockchainProvider.Equals(types.BlockchainProviderEthereum) {
return fmt.Errorf("stack '%s' is not an Ethereum stack (blockchain provider: %s). Use 'ff deploy fabric' for Fabric stacks", stackName, stackManager.Stack.BlockchainProvider)
}
contractNames, err := stackManager.GetContracts(filename, args[2:])
if err != nil {
return err
Expand Down
4 changes: 4 additions & 0 deletions cmd/deploy_fabric.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/hyperledger-firefly/cli/internal/docker"
"github.com/hyperledger-firefly/cli/internal/log"
"github.com/hyperledger-firefly/cli/internal/stacks"
"github.com/hyperledger-firefly/cli/pkg/types"
"github.com/spf13/cobra"
)

Expand All @@ -49,6 +50,9 @@ var deployFabricCmd = &cobra.Command{
if err := stackManager.LoadStack(stackName); err != nil {
return err
}
if !stackManager.Stack.BlockchainProvider.Equals(types.BlockchainProviderFabric) {
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)
}
contractAddress, err := stackManager.DeployContract(filename, filename, 0, args[2:])
if err != nil {
return fmt.Errorf("%s. usage: %s deploy <stack_name> <filename> <channel> <chaincode> <version>", err.Error(), ExecutableName)
Expand Down
5 changes: 2 additions & 3 deletions cmd/init_cardano.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@ var initCardanoCmd = &cobra.Command{
return err
}
if err := stackManager.InitStack(&initOptions); err != nil {
if err := stackManager.RemoveStack(); err != nil {
return err
}
// Try to clean up, but don't mask the original error
_ = stackManager.RemoveStack()
Comment thread
EnriqueL8 marked this conversation as resolved.
Outdated
return err
}
fmt.Printf("Stack '%s' created!\nTo start your new stack run:\n\n%s start %s\n", initOptions.StackName, rootCmd.Use, initOptions.StackName)
Expand Down
5 changes: 2 additions & 3 deletions cmd/init_fabric.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ var initFabricCmd = &cobra.Command{
return err
}
if err := stackManager.InitStack(&initOptions); err != nil {
if err := stackManager.RemoveStack(); err != nil {
return err
}
// Try to clean up, but don't mask the original error
_ = stackManager.RemoveStack()
Comment thread
EnriqueL8 marked this conversation as resolved.
Outdated
return err
}
fmt.Printf("Stack '%s' created!\nTo start your new stack run:\n\n%s start %s\n", initOptions.StackName, rootCmd.Use, initOptions.StackName)
Expand Down
5 changes: 2 additions & 3 deletions cmd/init_tezos.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ var initTezosCmd = &cobra.Command{
return err
}
if err := stackManager.InitStack(&initOptions); err != nil {
if err := stackManager.RemoveStack(); err != nil {
return err
}
// Try to clean up, but don't mask the original error
_ = stackManager.RemoveStack()
return err
}
fmt.Printf("Stack '%s' created!\nTo start your new stack run:\n\n%s start %s\n", initOptions.StackName, rootCmd.Use, initOptions.StackName)
Expand Down
8 changes: 5 additions & 3 deletions cmd/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ Pull the images for a stack .
if spin != nil {
spin.Start()
}
Comment thread
EnriqueL8 marked this conversation as resolved.
Outdated
if err := stackManager.PullStack(&pullOptions); err != nil {
return err
}
err = stackManager.PullStack(&pullOptions)
if spin != nil {
spin.Stop()
}
// Throw an error after stopping the spin, this will prevent the user's terminal from having the spinner as overlay
if err != nil {
return err
}
return nil
},
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ This command will start a stack and run it in the background.
spin.Start()
}
messages, err := stackManager.StartStack(&startOptions)
if err != nil {
return err
}
if spin != nil {
spin.Stop()
}
if err != nil {
return err
}
fmt.Print("\n\n")
for _, message := range messages {
fmt.Printf("%s\n\n", message)
Expand Down
52 changes: 51 additions & 1 deletion internal/blockchain/fabric/fabric_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
package fabric

import (
"archive/tar"
"compress/gzip"
"context"
_ "embed"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -438,7 +441,7 @@ func (p *FabricProvider) queryInstalled() (*QueryInstalledResponse, error) {
var res *QueryInstalledResponse
err = json.Unmarshal([]byte(str), &res)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse queryinstalled response as JSON: %w. Raw output: %s", err, str)
}
return res, nil
}
Expand Down Expand Up @@ -515,11 +518,58 @@ func (p *FabricProvider) GetContracts(filename string, extraArgs []string) ([]st
return []string{filename}, nil
}

// validateChaincodePackage checks that the chaincode package file exists and is a valid tar.gz file.
// Fabric chaincode packages created by 'peer lifecycle chaincode package' are tar.gz files.
func (p *FabricProvider) validateChaincodePackage(filename string) error {
// Check if file exists
fileInfo, err := os.Stat(filename)
if os.IsNotExist(err) {
return fmt.Errorf("chaincode package file not found: %s", filename)
}
if err != nil {
return fmt.Errorf("error accessing chaincode package file: %w", err)
}
if fileInfo.IsDir() {
return fmt.Errorf("chaincode package path is a directory, expected a file: %s", filename)
}

// Verify the file is a valid gzip archive
file, err := os.Open(filename)
if err != nil {
return fmt.Errorf("failed to open chaincode package file: %w", err)
}
defer file.Close()

gzReader, err := gzip.NewReader(file)
if err != nil {
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)
}
defer gzReader.Close()

// Verify the gzip contains a valid tar archive by reading at least one header
tarReader := tar.NewReader(gzReader)
_, err = tarReader.Next()
if err == io.EOF {
return fmt.Errorf("invalid chaincode package: the tar.gz archive is empty")
}
if err != nil {
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)
}

return nil
}

func (p *FabricProvider) DeployContract(filename, contractName, instanceName string, member *types.Organization, extraArgs []string) (*types.ContractDeploymentResult, error) {
filename, err := filepath.Abs(filename)
if err != nil {
return nil, err
}

// Validate that the chaincode package file exists and is a valid gzip file
if err := p.validateChaincodePackage(filename); err != nil {
return nil, err
}

switch {
case len(extraArgs) < 1:
return nil, fmt.Errorf("channel not set")
Expand Down
141 changes: 141 additions & 0 deletions internal/blockchain/fabric/fabric_provider_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package fabric

import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"fmt"
"os"
Expand Down Expand Up @@ -404,3 +407,141 @@ func TestRegisterIdentity(t *testing.T) {
})

}

func TestValidateChaincodePackage(t *testing.T) {
p := &FabricProvider{}

// Helper function to create a valid tar.gz file
createValidTarGz := func(t *testing.T, filename string) {
var buf bytes.Buffer
gzWriter := gzip.NewWriter(&buf)
tarWriter := tar.NewWriter(gzWriter)

// Add a file to the tar archive
content := []byte("test chaincode content")
header := &tar.Header{
Name: "metadata.json",
Mode: 0644,
Size: int64(len(content)),
}
err := tarWriter.WriteHeader(header)
assert.NoError(t, err)
_, err = tarWriter.Write(content)
assert.NoError(t, err)

err = tarWriter.Close()
assert.NoError(t, err)
err = gzWriter.Close()
assert.NoError(t, err)

err = os.WriteFile(filename, buf.Bytes(), 0644)
assert.NoError(t, err)
}

t.Run("valid tar.gz file", func(t *testing.T) {
tmpDir := t.TempDir()
validTarGzFile := filepath.Join(tmpDir, "valid.tar.gz")
createValidTarGz(t, validTarGzFile)

err := p.validateChaincodePackage(validTarGzFile)
assert.NoError(t, err)
})

t.Run("file not found", func(t *testing.T) {
err := p.validateChaincodePackage("/nonexistent/path/chaincode.tar.gz")
assert.Error(t, err)
assert.Contains(t, err.Error(), "chaincode package file not found")
})

t.Run("path is a directory", func(t *testing.T) {
tmpDir := t.TempDir()
err := p.validateChaincodePackage(tmpDir)
assert.Error(t, err)
assert.Contains(t, err.Error(), "chaincode package path is a directory")
})

t.Run("invalid - plain text file", func(t *testing.T) {
tmpDir := t.TempDir()
plainTextFile := filepath.Join(tmpDir, "plain.txt")
err := os.WriteFile(plainTextFile, []byte("this is not a gzip file"), 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(plainTextFile)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid chaincode package file format")
assert.Contains(t, err.Error(), "does not appear to be a valid gzip file")
})

t.Run("invalid - zip file", func(t *testing.T) {
tmpDir := t.TempDir()
// ZIP files start with PK (0x50, 0x4B)
zipLikeFile := filepath.Join(tmpDir, "fake.zip")
err := os.WriteFile(zipLikeFile, []byte{0x50, 0x4B, 0x03, 0x04, 0x00, 0x00}, 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(zipLikeFile)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid chaincode package file format")
})

t.Run("invalid - random binary", func(t *testing.T) {
tmpDir := t.TempDir()
randomFile := filepath.Join(tmpDir, "random.bin")
err := os.WriteFile(randomFile, []byte{0xDE, 0xAD, 0xBE, 0xEF}, 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(randomFile)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid chaincode package file format")
})

t.Run("invalid - gzip but not tar (plain gzipped content)", func(t *testing.T) {
tmpDir := t.TempDir()
gzipOnlyFile := filepath.Join(tmpDir, "gzip_only.gz")

var buf bytes.Buffer
gzWriter := gzip.NewWriter(&buf)
_, err := gzWriter.Write([]byte("this is gzipped but not a tar archive"))
assert.NoError(t, err)
err = gzWriter.Close()
assert.NoError(t, err)

err = os.WriteFile(gzipOnlyFile, buf.Bytes(), 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(gzipOnlyFile)
assert.Error(t, err)
assert.Contains(t, err.Error(), "does not contain a valid tar archive")
})

t.Run("invalid - empty tar.gz", func(t *testing.T) {
tmpDir := t.TempDir()
emptyTarGzFile := filepath.Join(tmpDir, "empty.tar.gz")

var buf bytes.Buffer
gzWriter := gzip.NewWriter(&buf)
tarWriter := tar.NewWriter(gzWriter)
// Close without adding any files
err := tarWriter.Close()
assert.NoError(t, err)
err = gzWriter.Close()
assert.NoError(t, err)

err = os.WriteFile(emptyTarGzFile, buf.Bytes(), 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(emptyTarGzFile)
assert.Error(t, err)
assert.Contains(t, err.Error(), "tar.gz archive is empty")
})

t.Run("invalid - empty file", func(t *testing.T) {
tmpDir := t.TempDir()
emptyFile := filepath.Join(tmpDir, "empty.tar.gz")
err := os.WriteFile(emptyFile, []byte{}, 0644)
assert.NoError(t, err)

err = p.validateChaincodePackage(emptyFile)
assert.Error(t, err)
})
}
14 changes: 9 additions & 5 deletions internal/stacks/stack_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -782,11 +782,15 @@ func (s *StackManager) ResetStack() error {
}

func (s *StackManager) RemoveStack() error {
if err := s.runDockerComposeCommand("down"); err != nil {
return err
}
if err := s.removeVolumes(); err != nil {
return err
// Only run docker compose down if the docker-compose.yml file exists
composeFile := filepath.Join(s.Stack.StackDir, "docker-compose.yml")
if _, err := os.Stat(composeFile); err == nil {
if err := s.runDockerComposeCommand("down"); err != nil {
return err
}
if err := s.removeVolumes(); err != nil {
return err
}
}
return os.RemoveAll(s.Stack.StackDir)
}
Expand Down
Loading