diff --git a/.gitignore b/.gitignore index 8a3f001..f5cabd4 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ .DS_Store -.vscode ff/ff dist/ *.iml diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ebd65cb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug init mode", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "ff/main.go", + "args": ["init", "ethereum", "dev", "2"] + }, + { + "name": "Debug remove mode", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "ff/main.go", + "args": ["remove", "dev", "--force"] + }, + { + "name": "Debug start mode", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "ff/main.go", + "args": ["start", "dev"] + }, + ] +} \ No newline at end of file diff --git a/cmd/init_ethereum.go b/cmd/init_ethereum.go index cc19073..8a68c1a 100644 --- a/cmd/init_ethereum.go +++ b/cmd/init_ethereum.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" + "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" @@ -37,14 +38,27 @@ var initEthereumCmd = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { ctx := log.WithVerbosity(context.Background(), verbose) ctx = log.WithLogger(ctx, logger) + version, err := docker.CheckDockerConfig() + if err != nil { + return err + } + // Needs this context for cleaning up as part of Remove Stack + // If an error occurs as part of init + ctx = context.WithValue(ctx, docker.CtxComposeVersionKey{}, version) + stackManager := stacks.NewStackManager(ctx) if err := initCommon(args); err != nil { return err } if err := stackManager.InitStack(&initOptions); err != nil { - if err := stackManager.RemoveStack(); err != nil { - return err + verr := stackManager.RemoveStack() + + // log the remove error if present + if verr != nil { + l := log.LoggerFromContext(ctx) + l.Info(fmt.Sprintf("Error whilst removing the stack: %s", verr.Error())) } + // return the init error to not hide the issue 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) diff --git a/internal/blockchain/ethereum/quorum/private_transaction_manager.go b/internal/blockchain/ethereum/quorum/private_transaction_manager.go index 5e40bdb..421404b 100644 --- a/internal/blockchain/ethereum/quorum/private_transaction_manager.go +++ b/internal/blockchain/ethereum/quorum/private_transaction_manager.go @@ -22,6 +22,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "strings" "github.com/hyperledger/firefly-cli/internal/docker" @@ -54,7 +55,15 @@ func CreateTesseraKeys(ctx context.Context, image, outputDirectory, prefix, name return "", "", "", err } fmt.Println("generating tessera keys") - err = docker.RunDockerCommand(ctx, outputDirectory, "run", "--rm", "-v", fmt.Sprintf("%s:/keystore", outputDirectory), image, "-keygen", "-filename", fmt.Sprintf("/keystore/%s", filename)) + args := []string{"run"} + // Order of args matter and platform argument needs + // to be early in the list + if runtime.GOARCH == "arm64" { + args = append(args, "--platform", "linux/amd64") + } + args = append(args, "--rm", "-v", fmt.Sprintf("%s:/keystore", outputDirectory), image, "-keygen", "-filename", fmt.Sprintf("/keystore/%s", filename)) + + err = docker.RunDockerCommand(ctx, outputDirectory, args...) if err != nil { return "", "", "", err } diff --git a/internal/blockchain/ethereum/quorum/quorum_provider.go b/internal/blockchain/ethereum/quorum/quorum_provider.go index 0e3052d..d5e9938 100644 --- a/internal/blockchain/ethereum/quorum/quorum_provider.go +++ b/internal/blockchain/ethereum/quorum/quorum_provider.go @@ -23,6 +23,7 @@ import ( "os" "path" "path/filepath" + "runtime" "strconv" "time" @@ -246,7 +247,7 @@ func (p *QuorumProvider) GetDockerServiceDefinitions() []*docker.ServiceDefiniti for i := 0; i < memberCount; i++ { var quorumDependsOn map[string]map[string]string if !p.stack.PrivateTransactionManager.Equals(types.PrivateTransactionManagerNone) { - quorumDependsOn = map[string]map[string]string{fmt.Sprintf("tessera_%d", i): {"condition": "service_started"}} + quorumDependsOn = map[string]map[string]string{fmt.Sprintf("tessera_%d", i): {"condition": "service_healthy"}} serviceDefinitions[i+memberCount] = &docker.ServiceDefinition{ ServiceName: fmt.Sprintf("tessera_%d", i), Service: &docker.Service{ @@ -257,10 +258,25 @@ func (p *QuorumProvider) GetDockerServiceDefinitions() []*docker.ServiceDefiniti Ports: []string{fmt.Sprintf("%d:%s", p.stack.ExposedPtmPort+(i*ExposedBlockchainPortMultiplier), TmTpPort)}, // defaults 4100, 4110, 4120, 4130 Environment: p.stack.EnvironmentVars, EntryPoint: []string{"/bin/sh", "-c", "/data/docker-entrypoint.sh"}, - Deploy: map[string]interface{}{"restart_policy": map[string]string{"condition": "on-failure", "max_attempts": "3"}}, + Deploy: map[string]interface{}{"restart_policy": map[string]interface{}{"condition": "on-failure", "max_attempts": int64(3)}}, + HealthCheck: &docker.HealthCheck{ + Test: []string{ + "CMD", + "curl", + "--fail", + fmt.Sprintf("http://localhost:%s/upcheck", TmTpPort), + }, + Interval: "15s", // 6000 requests in a day + Retries: 30, + }, }, VolumeNames: []string{fmt.Sprintf("tessera_%d", i)}, } + + // No arm64 images for Tessera + if runtime.GOARCH == "arm64" { + serviceDefinitions[i+memberCount].Service.Platform = "linux/amd64" + } } serviceDefinitions[i] = &docker.ServiceDefinition{ ServiceName: fmt.Sprintf("quorum_%d", i), diff --git a/internal/docker/docker_config.go b/internal/docker/docker_config.go index 897067a..75fa5b9 100644 --- a/internal/docker/docker_config.go +++ b/internal/docker/docker_config.go @@ -61,6 +61,7 @@ type Service struct { EnvFile string `yaml:"env_file,omitempty"` Expose []int `yaml:"expose,omitempty"` Deploy map[string]interface{} `yaml:"deploy,omitempty"` + Platform string `yaml:"platform,omitempty"` } type DockerComposeConfig struct { @@ -79,7 +80,7 @@ var StandardLogOptions = &LoggingConfig{ func CreateDockerCompose(s *types.Stack) *DockerComposeConfig { compose := &DockerComposeConfig{ - Version: "2.1", + Version: "2.4", Services: make(map[string]*Service), Volumes: make(map[string]struct{}), } diff --git a/internal/stacks/stack_manager.go b/internal/stacks/stack_manager.go index 2fa45da..6454c5a 100644 --- a/internal/stacks/stack_manager.go +++ b/internal/stacks/stack_manager.go @@ -25,6 +25,7 @@ import ( "os/exec" "path" "path/filepath" + "runtime" "strconv" "strings" "sync" @@ -65,6 +66,10 @@ type StackManager struct { once sync.Once } +var unsupportedARM64Images map[string]bool = map[string]bool{ + "quorumengineering/tessera:24.4": true, +} + func ListStacks() ([]string, error) { files, err := os.ReadDir(constants.StacksDir) if err != nil { @@ -214,6 +219,7 @@ func (s *StackManager) runDockerComposeCommand(command ...string) error { return err } } + return err } return docker.RunDockerComposeCommand(s.ctx, s.Stack.StackDir, command...) } @@ -696,7 +702,11 @@ func (s *StackManager) PullStack(options *types.PullOptions) error { for _, image := range images { if _, ok := hasPulled[image]; !ok { s.Log.Info(fmt.Sprintf("pulling '%s'", image)) - if err := docker.RunDockerCommandRetry(s.ctx, s.Stack.InitDir, options.Retries, "pull", image); err != nil { + args := []string{"pull", image} + if unsupportedARM64Images[image] && runtime.GOARCH == "arm64" { + args = append(args, "--platform=linux/amd64") + } + if err := docker.RunDockerCommandRetry(s.ctx, s.Stack.InitDir, options.Retries, args[0:]...); err != nil { return err } else { hasPulled[image] = true