Skip to content

Commit

Permalink
Add deploy command
Browse files Browse the repository at this point in the history
Signed-off-by: Nicko Guyer <nicko.guyer@kaleido.io>
  • Loading branch information
nguyer committed Mar 8, 2022
1 parent 7617d77 commit 5d068b5
Show file tree
Hide file tree
Showing 14 changed files with 392 additions and 91 deletions.
90 changes: 90 additions & 0 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright © 2022 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"fmt"

"github.com/hyperledger/firefly-cli/internal/docker"
"github.com/hyperledger/firefly-cli/internal/stacks"
"github.com/spf13/cobra"
)

// deployCmd represents the deploy command
var deployCmd = &cobra.Command{
Use: "deploy <stack_name> <filename>",
Short: "Deploy a compiled smart contract to the blockchain used by a FireFly stack",
Long: `Deploy a compiled smart contract to the blockchain used by a FireFly stack`,
RunE: func(cmd *cobra.Command, args []string) error {

if err := docker.CheckDockerConfig(); err != nil {
return err
}

stackManager := stacks.NewStackManager(logger)
if len(args) == 0 {
return fmt.Errorf("no stack specified")
}
stackName := args[0]
if len(args) < 2 {
return fmt.Errorf("no filename specified")
}
filename := args[1]

if exists, err := stacks.CheckExists(stackName); err != nil {
return err
} else if !exists {
return fmt.Errorf("stack '%s' does not exist", stackName)
}

if err := stackManager.LoadStack(stackName, verbose); err != nil {
return err
}

contractNames, err := stackManager.GetContracts(filename)
if err != nil {
return err
}
if len(contractNames) < 1 {
return fmt.Errorf("no contracts found in file: '%s'", filename)
}
selectedContractName := contractNames[0]
if len(contractNames) > 1 {
selectedContractName, err = selectMenu("select the contract to deploy", contractNames)
fmt.Print("\n")
if err != nil {
return err
}
}

fmt.Printf("deploying %s... ", selectedContractName)
contractAddress, err := stackManager.DeployContract(filename, selectedContractName, 0)
if err != nil {
return err
}

fmt.Print("done\n\n")
fmt.Printf("contract address: %s\n", contractAddress)
fmt.Print("\n")

return nil
},
}

func init() {
rootCmd.AddCommand(deployCmd)
}
41 changes: 36 additions & 5 deletions cmd/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)

Expand All @@ -32,11 +33,7 @@ func prompt(promptText string, validate func(string) error) (string, error) {
} else {
str = strings.TrimSpace(str)
if err := validate(str); err != nil {
if fancyFeatures {
fmt.Printf("\u001b[31mError: %s\u001b[0m\n", err.Error())
} else {
fmt.Printf("Error: %s\n", err.Error())
}
printError(err)
} else {
return str, nil
}
Expand All @@ -60,3 +57,37 @@ func confirm(promptText string) error {
}
}
}

func selectMenu(promptText string, options []string) (string, error) {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("\n")
for i, option := range options {
fmt.Printf(" %v) %s\n", i+1, option)
}
fmt.Printf("\n%s: ", promptText)
if str, err := reader.ReadString('\n'); err != nil {
return "", err
} else {
str = strings.TrimSpace(str)
index, err := strconv.Atoi(str)
if err != nil {
printError(fmt.Errorf("'%s' is not a valid option", str))
continue
}
if index < 1 || index > len(options) {
printError(fmt.Errorf("'%s' is not a valid option", str))
continue
}
return options[index-1], nil
}
}
}

func printError(err error) {
if fancyFeatures {
fmt.Printf("\u001b[31mError: %s\u001b[0m\n", err.Error())
} else {
fmt.Printf("Error: %s\n", err.Error())
}
}
2 changes: 2 additions & 0 deletions internal/blockchain/blockchain_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ type IBlockchainProvider interface {
GetDockerServiceDefinitions() []*docker.ServiceDefinition
GetFireflyConfig(m *types.Member) (blockchainConfig *core.BlockchainConfig, coreConfig *core.OrgConfig)
Reset() error
GetContracts(filename string) ([]string, error)
DeployContract(filename, contractName string, member types.Member) (string, error)
}
19 changes: 18 additions & 1 deletion internal/blockchain/ethereum/besu/besu_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func (p *BesuProvider) FirstTimeSetup() error {
}

func (p *BesuProvider) DeploySmartContracts() error {
return ethereum.DeployContracts(p.Stack, p.Log, p.Verbose)
return ethconnect.DeployContracts(p.Stack, p.Log, p.Verbose)
}

func (p *BesuProvider) PreStart() error {
Expand Down Expand Up @@ -287,6 +287,23 @@ func (p *BesuProvider) GetFireflyConfig(m *types.Member) (blockchainConfig *core
return
}

func (p *BesuProvider) GetContracts(filename string) ([]string, error) {
contracts, err := ethereum.ReadCombinedABIJSON(filename)
if err != nil {
return []string{}, err
}
contractNames := make([]string, len(contracts.Contracts))
i := 0
for contractName := range contracts.Contracts {
contractNames[i] = contractName
}
return contractNames, err
}

func (p *BesuProvider) DeployContract(filename, contractName string, member types.Member) (string, error) {
return ethconnect.DeployCustomContract(p.getEthconnectURL(&member), member.Address, filename, contractName)
}

func (p *BesuProvider) getEthconnectURL(member *types.Member) string {
if !member.External {
return fmt.Sprintf("http://ethconnect_%s:8080", member.ID)
Expand Down
84 changes: 15 additions & 69 deletions internal/blockchain/ethereum/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,59 +18,21 @@ package ethereum

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"path/filepath"

"github.com/hyperledger/firefly-cli/internal/blockchain/ethereum/ethconnect"
"github.com/hyperledger/firefly-cli/internal/constants"
"github.com/hyperledger/firefly-cli/internal/docker"
"github.com/hyperledger/firefly-cli/internal/log"
"github.com/hyperledger/firefly-cli/pkg/types"
)

func DeployContracts(s *types.Stack, log log.Logger, verbose bool) error {
var containerName string
for _, member := range s.Members {
if !member.External {
containerName = fmt.Sprintf("%s_firefly_core_%s", s.Name, member.ID)
break
}
}
if containerName == "" {
return errors.New("unable to extract contracts from container - no valid firefly core containers found in stack")
}
log.Info("extracting smart contracts")

if err := ExtractContracts(s.Name, containerName, "/firefly/contracts", verbose); err != nil {
return err
}

fireflyContract, err := ReadCompiledContract(filepath.Join(constants.StacksDir, s.Name, "contracts", "Firefly.json"))
if err != nil {
return err
}

var fireflyContractAddress string
for _, member := range s.Members {
if fireflyContractAddress == "" {
// TODO: version the registered name
log.Info(fmt.Sprintf("deploying firefly contract on '%s'", member.ID))
fireflyContractAddress, err = DeployContract(member, fireflyContract, "firefly", map[string]string{})
if err != nil {
return err
}
} else {
log.Info(fmt.Sprintf("registering firefly contract on '%s'", member.ID))
err = RegisterContract(member, fireflyContract, fireflyContractAddress, "firefly", map[string]string{})
if err != nil {
return err
}
}
}
type CompiledContracts struct {
Contracts map[string]*CompiledContract `json:"contracts"`
}

return nil
type CompiledContract struct {
ABI interface{} `json:"abi"`
Bytecode string `json:"bin"`
}

func ReadCompiledContract(filePath string) (*types.Contract, error) {
Expand All @@ -83,35 +45,19 @@ func ReadCompiledContract(filePath string) (*types.Contract, error) {
return contract, nil
}

func ExtractContracts(stackName string, containerName string, dirName string, verbose bool) error {
workingDir := filepath.Join(constants.StacksDir, stackName)
if err := docker.RunDockerCommand(workingDir, verbose, verbose, "cp", containerName+":"+dirName, workingDir); err != nil {
return err
}
return nil
}

func DeployContract(member *types.Member, contract *types.Contract, name string, args map[string]string) (string, error) {
ethconnectUrl := fmt.Sprintf("http://127.0.0.1:%v", member.ExposedConnectorPort)
abiResponse, err := ethconnect.PublishABI(ethconnectUrl, contract)
if err != nil {
return "", err
}
deployResponse, err := ethconnect.DeployContract(ethconnectUrl, abiResponse.ID, member.Address, args, name)
func ReadCombinedABIJSON(filePath string) (*CompiledContracts, error) {
d, _ := ioutil.ReadFile(filePath)
var contracts *CompiledContracts
err := json.Unmarshal(d, &contracts)
if err != nil {
return "", err
return nil, err
}
return deployResponse.ContractAddress, nil
return contracts, nil
}

func RegisterContract(member *types.Member, contract *types.Contract, contractAddress string, name string, args map[string]string) error {
ethconnectUrl := fmt.Sprintf("http://127.0.0.1:%v", member.ExposedConnectorPort)
abiResponse, err := ethconnect.PublishABI(ethconnectUrl, contract)
if err != nil {
return err
}
_, err = ethconnect.RegisterContract(ethconnectUrl, abiResponse.ID, contractAddress, member.Address, name, args)
if err != nil {
func ExtractContracts(stackName string, containerName string, dirName string, verbose bool) error {
workingDir := filepath.Join(constants.StacksDir, stackName)
if err := docker.RunDockerCommand(workingDir, verbose, verbose, "cp", containerName+":"+dirName, workingDir); err != nil {
return err
}
return nil
Expand Down
Loading

0 comments on commit 5d068b5

Please sign in to comment.