diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ec6812c..b34b9c3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ jobs: # Build the daemon binary and if a release publish if a 'tag' build # amd64/arm64 binary_build: - name: Binary Build + name: Binary Daemon Build runs-on: ubuntu-latest strategy: matrix: @@ -25,10 +25,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 - - name: Use Go 1.17 + - name: Use Go 1.18 uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.18 - name: Build Binary run: go build -v -o bin/microfabd cmd/microfabd/main.go - name: Package Binary @@ -41,6 +41,31 @@ jobs: with: files: microfab-*.tgz + # Build the cli binary and if a release publish if a 'tag' build + # amd64/arm64 + binary_cli_build: + name: Binary CLI Build + runs-on: ubuntu-latest + strategy: + matrix: + goarch: [amd64, arm64] + env: + GOARCH: ${{ matrix.goarch }} + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Use Go 1.18 + uses: actions/setup-go@v2 + with: + go-version: 1.18 + - name: Build Binary + run: go build -v -o bin/microfab-${GOARCH} cmd/microfab/main.go + - name: Publish Binary to GitHub Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + files: bin/microfab-${GOARCH} + # Build the container images and push to the ghcr.io repo # amd64/arm64 container_build: diff --git a/README.md b/README.md index 6debc0f..59d4722 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,12 @@ Check the [reference](./docs/DevelopingContracts.md) in this repo for details in ## Tutorial Check the [Quick Start Tutorial](./docs/Tutorial.md) - nothing to deployed smart contract in under 5minutes; + +``` +curl -sSL https://github.com/hyperledger-labs/microfab/releases/download/v0.0.18/microfab-linux-amd64 -o microfab +microfab start --log +``` + ## Why microfab? There are other 'form factors' of Fabric some are aimed at production/k8s deployments others more development focussed. diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 49c29c4..9c2971e 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -22,20 +22,21 @@ curl -sSL https://github.com/hyperledger-labs/microfab/raw/main/integration/data - Start Microfab with it's default configuration; (in a separate terminal run `docker logs -f microfab` so you can see what it's doing) ``` -docker run -d --rm -p 8080:8080 --name microfab ghcr.io/hyperledger-labs/microfab:latest +curl -sSL https://github.com/hyperledger-labs/microfab/releases/download/v0.0.18/microfab-linux-amd64 -o microfab + +microfab start ``` - We need to get the configuration of microfab and the address identities that it created; using the Hyperledger Labs *weft* tool is the quickest + ``` -curl -s http://console.127-0-0-1.nip.io:8080/ak/api/v1/components | npx @hyperledger-labs/weft microfab -w _wallets -p _gateways -m _msp -f +microfab connect ``` -- This will show us some environment variables we can use to work with Fabric. +- This writes out a certificates and keys in a structure to use with the PeerCLI. Set the current shell enviroment variables for org1 ``` -export CORE_PEER_LOCALMSPID=Org1MSP -export CORE_PEER_MSPCONFIGPATH=$(pwd)/_msp/Org1/org1admin/msp -export CORE_PEER_ADDRESS=org1peer-api.127-0-0-1.nip.io:8080 +source _mfcfg/org1.env ``` - We can then Install, Approve and Commit the chaincode definition @@ -78,4 +79,71 @@ peer chaincode invoke -C channel1 -n assettx \ -c '{"Args":["org.hyperledger.fabric:GetMetadata"]}' \ --orderer orderer-api.127-0-0-1.nip.io:8080 2>&1 \ git stat| sed -e 's/^.*payload://' | sed -e 's/..$//' -e 's/^.//' -e 's/\\"/"/g' | jq +``` + +## Microfab CLI + +The CLI is a small binary wrapper that will create the docker image (pulling the image if needed), and write out the identitiy information. + +The (original) way was to run the docker commands manually, see below for the equivalents + +``` +Microfab Launch Control + +Usage: + microfab [command] + +microfab + connect Writes out connection details for use by the Peer CLI and SDKs + ping Pings the microfab image to see if it's running + start Starts the microfab image running + stop Stops the microfab image running + +Additional Commands: + completion Generate the autocompletion script for the specified shell + help Help about any command + +Flags: + -h, --help help for microfab + -v, --version version for microfab + +``` + +### Start +``` +Starts the microfab image running + +Usage: + microfab start [flags] + +Flags: + --config string Microfab config (default "{\"endorsing_organizations\":[{\"name\":\"org1\"}],\"channels\":[{\"name\":\"mychannel\",\"endorsing_organizations\":[\"org1\"]},{\"name\":\"appchannel\",\"endorsing_organizations\":[\"org1\"]}],\"capability_level\":\"V2_5\"}") + --configFile string Microfab config file + -f, --force Force restart if microfab already running + -h, --help help for start + -l, --logs Display the logs (docker logs -f microfab) +``` + +### Connect + +``` +Writes out connection details for use by the Peer CLI and SDKs + +Usage: + microfab connect [flags] + +Flags: + -f, --force Force overwriting details directory + -h, --help help for connect + --msp string msp output directory (default "_mfcfg") +``` + +## Docker Command Equivalents + +``` +docker run -d --rm -p 8080:8080 --name microfab ghcr.io/hyperledger-labs/microfab:latest +``` + +``` +curl -s http://console.127-0-0-1.nip.io:8080/ak/api/v1/components | npx @hyperledger-labs/weft microfab -w _wallets -p _gateways -m _msp -f ``` \ No newline at end of file diff --git a/pkg/microfab/connect.go b/pkg/microfab/connect.go index d4b1e8b..1ff5f50 100644 --- a/pkg/microfab/connect.go +++ b/pkg/microfab/connect.go @@ -14,13 +14,19 @@ import ( ) var connectCmd = &cobra.Command{ - Use: "connect", - Short: "Writes out connection details for use by the Peer CLI and SDKs", + Use: "connect", + Short: "Writes out connection details for use by the Peer CLI and SDKs", + GroupID: "mf", RunE: func(cmd *cobra.Command, args []string) error { return connect() }, } +func init() { + connectCmd.PersistentFlags().BoolVarP(&force, "force", "f", false, "Force overwriting details directory") + connectCmd.PersistentFlags().StringVar(&mspdir, "msp", "_mfcfg", "msp output directory") +} + func connect() error { urlStr := "http://console.127-0-0-1.nip.io:8080" @@ -35,7 +41,7 @@ func connect() error { log.Printf("Identity and Configuration '%s'\n", rootDir) // check to see if the directory exists, and if it does emptry - cfgExists, err := exists(rootDir) + cfgExists, err := Exists(rootDir) if err != nil { return err } @@ -97,11 +103,13 @@ func connect() error { return errors.Wrapf(err, "Unable to form path for context") } - f.WriteString(fmt.Sprintf("CORE_PEER_ADDRESS=%s\n", u.Host)) - f.WriteString(fmt.Sprintf("CORE_PEER_LOCALMSPID=%s\n", peer.MSPID)) - f.WriteString(fmt.Sprintf("CORE_PEER_MSPCONFIGPATH=%s\n", idRoot)) + f.WriteString(fmt.Sprintf("export CORE_PEER_ADDRESS=%s\n", u.Host)) + f.WriteString(fmt.Sprintf("export CORE_PEER_LOCALMSPID=%s\n", peer.MSPID)) + f.WriteString(fmt.Sprintf("export CORE_PEER_MSPCONFIGPATH=%s\n", idRoot)) f.Sync() + log.Printf("For %s context run 'source %s'", org, f.Name()) + } return nil @@ -120,15 +128,3 @@ func isEmpty(name string) (bool, error) { } return false, err // Either not empty or error, suits both cases } - -// exists returns whether the given file or directory exists -func exists(path string) (bool, error) { - _, err := os.Stat(path) - if err == nil { - return true, nil - } - if os.IsNotExist(err) { - return false, nil - } - return false, err -} diff --git a/pkg/microfab/ping.go b/pkg/microfab/ping.go index 37b4c4f..1cdb20b 100644 --- a/pkg/microfab/ping.go +++ b/pkg/microfab/ping.go @@ -10,8 +10,9 @@ import ( ) var pingCmd = &cobra.Command{ - Use: "ping", - Short: "Pings the microfab image to see if it's running", + Use: "ping", + Short: "Pings the microfab image to see if it's running", + GroupID: "mf", RunE: func(cmd *cobra.Command, args []string) error { return ping() }, diff --git a/pkg/microfab/root.go b/pkg/microfab/root.go index 03592df..ba488c1 100644 --- a/pkg/microfab/root.go +++ b/pkg/microfab/root.go @@ -16,9 +16,12 @@ var rootCmd = &cobra.Command{ SilenceErrors: true, } +var defaultCfg = `{"endorsing_organizations":[{"name":"org1"}],"channels":[{"name":"mychannel","endorsing_organizations":["org1"]},{"name":"appchannel","endorsing_organizations":["org1"]}],"capability_level":"V2_5"}` + var cfg string var mspdir string var force bool +var cfgFile string // Execute the microfab command func Execute() { @@ -31,12 +34,8 @@ func Execute() { } func init() { - rootCmd.PersistentFlags().StringVar(&cfg, "config", "", "Microfab config") - rootCmd.PersistentFlags().StringVar(&mspdir, "msp", "_mfcfg", "msp output directory") - rootCmd.PersistentFlags().BoolVar(&force, "force", false, "Force overwriting msp directory") - - viper.BindPFlag("MICROFAB_CONFIG", rootCmd.PersistentFlags().Lookup("config")) + rootCmd.AddGroup(&cobra.Group{ID: "mf", Title: "microfab"}) rootCmd.AddCommand(startCmd) rootCmd.AddCommand(stopCmd) rootCmd.AddCommand(connectCmd) diff --git a/pkg/microfab/start.go b/pkg/microfab/start.go index e134a92..47f2052 100644 --- a/pkg/microfab/start.go +++ b/pkg/microfab/start.go @@ -4,10 +4,12 @@ import ( "context" "fmt" "log" + "os" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" + "github.com/docker/docker/pkg/stdcopy" "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -15,14 +17,26 @@ import ( ) var startCmd = &cobra.Command{ - Use: "start", - Short: "Starts the microfab image running", + Use: "start", + Short: "Starts the microfab image running", + GroupID: "mf", RunE: func(cmd *cobra.Command, args []string) error { return start() }, } +var logs bool + func init() { + startCmd.PersistentFlags().BoolVarP(&force, "force", "f", false, "Force restart if microfab already running") + startCmd.PersistentFlags().BoolVarP(&logs, "logs", "l", false, "Display the logs (docker logs -f microfab)") + + startCmd.PersistentFlags().StringVar(&cfg, "config", defaultCfg, "Microfab config") + startCmd.PersistentFlags().StringVar(&cfgFile, "configFile", "", "Microfab config file") + + startCmd.MarkFlagsMutuallyExclusive("config", "configFile") + + viper.BindPFlag("MICROFAB_CONFIG", rootCmd.PersistentFlags().Lookup("config")) } @@ -38,10 +52,9 @@ func start() error { log.Printf("Starting microfab container..\n") - cfg = viper.GetString("MICROFAB_CONFIG") - - if cfg == "" { - return errors.Errorf("Can't start - config is blank") + cfg, err = GetConfig() + if err != nil { + return errors.Wrapf(err, "Unable to determine config") } env[0] = "FABRIC_LOGGING_SPEC=info" @@ -96,6 +109,14 @@ func start() error { log.Printf("Container ID %s\n", resp.ID) log.Printf("Microfab is up and running\n") + if logs { + out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true}) + if err != nil { + return err + } + + stdcopy.StdCopy(os.Stdout, os.Stderr, out) + } return nil } diff --git a/pkg/microfab/stop.go b/pkg/microfab/stop.go index ebef97f..5170710 100644 --- a/pkg/microfab/stop.go +++ b/pkg/microfab/stop.go @@ -12,8 +12,9 @@ import ( ) var stopCmd = &cobra.Command{ - Use: "stop", - Short: "Stops the microfab image running", + Use: "stop", + Short: "Stops the microfab image running", + GroupID: "mf", RunE: func(cmd *cobra.Command, args []string) error { return Stop("microfab") }, diff --git a/pkg/microfab/util.go b/pkg/microfab/util.go index c0aa230..071cb71 100644 --- a/pkg/microfab/util.go +++ b/pkg/microfab/util.go @@ -6,11 +6,14 @@ import ( "encoding/json" "fmt" "log" + "os" + "path" "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/pkg/errors" + "github.com/spf13/viper" "golang.org/x/exp/slices" ) @@ -100,3 +103,39 @@ func ImageRunning(containerName string) (bool, error) { return found, nil } + +// GetConfig resolves the microfab configuration +func GetConfig() (string, error) { + cfg = viper.GetString("MICROFAB_CONFIG") + if cfg == "" { + cf := path.Clean(cfgFile) + exist, err := Exists(cf) + if err != nil { + return "", err + } + + if exist { + cfgData, err := os.ReadFile(cf) + if err != nil { + return "", err + } + cfg = string(cfgData) + } else { + return "", errors.Errorf("Unable to locate config from file, envvar or cli option") + } + + } + return cfg, nil +} + +// Exists returns whether the given file or directory exists +func Exists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +}