diff --git a/.dockerignore b/.dockerignore index 168f6c6..6eca100 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1 @@ -/.git/ \ No newline at end of file +/.git/ diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index ffbf94a..0000000 --- a/.drone.yml +++ /dev/null @@ -1,32 +0,0 @@ -workspace: - base: /root - path: go/src/github.com/vulpemventures/nigiri - -pipeline: - test: - image: docker/compose:1.24.0 - environment: - - DOCKER_HOST=tcp://docker:2375 - commands: - - apk update && apk add --no-cache git curl wget bash make build-base - - mkdir -p /root/go/bin /root/go/pkg - # Install Go - - wget -q https://dl.google.com/go/go1.13.4.linux-amd64.tar.gz - - tar -xf go1.13.4.linux-amd64.tar.gz -C /usr/local && rm -rf go* - - export GOROOT=/usr/local/go - - export GOPATH=$HOME/go - - export PATH=$GOPATH/bin:$GOROOT/bin:$PATH - # Test - - export GO111MODULE=on - - go mod init - - bash scripts/install - - go test -v ./... - -services: - docker: - image: docker:dind - privileged: true - - compose: - image: docker/compose:1.24.0 - privileged: true \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9812c8f..7abceee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.15.x + go-version: 1.16.x - name: Cache Go modules uses: actions/cache@v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b6f4cd0..0e0cc6a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,12 +11,23 @@ jobs: name: Unit Tests runs-on: ubuntu-latest steps: - - name: Set up Go 1.x + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Go uses: actions/setup-go@v2 - id: go + with: + go-version: 1.16.x - - name: Check out code into the Go module directory - uses: actions/checkout@v2 + - name: Cache Go modules + uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- - name: Get dependencies run: go get -v -t -d ./... @@ -25,5 +36,4 @@ jobs: run: | make fmt make install - sudo chmod -R 777 . make test-ci diff --git a/.gitignore b/.gitignore index 19a5844..41a780b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ vendor/ build/ -dist/ \ No newline at end of file +dist/ diff --git a/.goreleaser.yml b/.goreleaser.yml index 4438e1c..4b96041 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,7 +1,7 @@ builds: - - main: ./cli/main.go + - main: ./cmd/nigiri ldflags: - - -s -w -X github.com/vulpemventures/nigiri/cli/cmd.version={{.Version}} -X github.com/vulpemventures/nigiri/cli/cmd.commit={{.Commit}} -X github.com/vulpemventures/nigiri/cli/cmd.date={{.Date}} + - -s -X 'main.version={{.Version}}' -X 'main.commit={{.Commit}}' -X 'main.date={{.Date}}' env: - CGO_ENABLED=0 goos: @@ -9,17 +9,22 @@ builds: - darwin goarch: - amd64 + checksum: - name_template: 'checksums.txt' + name_template: "checksums.txt" + snapshot: name_template: "{{ .Tag }}-next" + changelog: sort: asc filters: exclude: - - '^docs:' - - '^test:' + - "^docs:" + - "^test:" archives: - - - format: binary - name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}" \ No newline at end of file + - format: binary + name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}" + +release: + prerelease: auto diff --git a/Makefile b/Makefile index 20393ae..763dabb 100644 --- a/Makefile +++ b/Makefile @@ -1,30 +1,28 @@ -.PHONY: install build release dry-release clean cov fmt help vet test +.PHONY: install clean build release dry-release cov fmt help vet test ## install: installs dependencies install: - export GO111MODULE=on - chmod u+x ./scripts/install - ./scripts/install + go mod download + go mod tidy -## build: build binary for ARM +## clean: cleans the binary +clean: + @echo "Cleaning..." + go clean + +## build: build binary build: - export GO111MODULE=on chmod u+x ./scripts/build ./scripts/build +## release: build and upload binaries to Github Releases release: goreleaser +## dry-release: build and test goreleaser dry-release: goreleaser --snapshot --skip-publish --rm-dist -## clean: cleans the binary -clean: - @echo "Cleaning..." - export GO111MODULE=on - chmod u+x ./scripts/clean - ./scripts/clean - ## help: prints this help message help: @echo "Usage: \n" @@ -46,7 +44,7 @@ test: clean install go test -v -count=1 -race ./... ## test-ci: runs travis tests -test-ci: +test-ci: clean @echo "Testing..." go test -short -v ./... diff --git a/README.md b/README.md index 5bdbe4e..ff529e3 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ It offers a [JSON HTTP proxy passtrough](https://github.com/vulpemventures/nigir You can have Elements too with the `--liquid` flag. -Are you looking to spin-up Nigiri in Travis or Github Action? Look [here](https://github.com/vulpemventures/nigiri-travis) - # No time to make a Nigiri yourself? ## Pre-built binary @@ -51,7 +49,6 @@ $ nigiri rpc --liquid getnewaddress "" "bech32" el1qqwwx9gyrcrjrhgnrnjq9dq9t4hykmr6ela46ej63dnkdkcg8veadrvg5p0xg0zd6j3aug74cv9m4cf4jslwdqnha2w2nsg9x3 ``` - # Make from scratch ## Utensils @@ -83,14 +80,11 @@ $ git clone https://github.com/vulpemventures/nigiri.git $ make install ``` -This will create `~/.nigiri` copying there the `{bitcoin|elements}.conf` you can modify. * Build binary + ``` -# MacOSX -$ make build-mac -# Linux -$ make build-linux +$ make build ``` Done! You should be able to find the binary in the local `./build` folder. Give it permission to execute and move/rename into your PATH. @@ -187,6 +181,21 @@ $ nigiri rpc --liquid getnewaddress "" "bech32" el1qqwwx9gyrcrjrhgnrnjq9dq9t4hykmr6ela46ej63dnkdkcg8veadrvg5p0xg0zd6j3aug74cv9m4cf4jslwdqnha2w2nsg9x3 ``` +* Run in headless mode (without Esplora) +If you are looking to spin-up Nigiri in Travis or Github Action you can use the `--ci` flag. + +``` +$ nigiri start --ci [--liquid] +``` + + +* Update the docker images + +``` +$ nigiri update +``` + + Nigiri uses the default directory `~/.nigiri` to store configuration files and docker-compose files. diff --git a/cli/cmd/faucet.go b/cli/cmd/faucet.go deleted file mode 100644 index 6a96a6d..0000000 --- a/cli/cmd/faucet.go +++ /dev/null @@ -1,120 +0,0 @@ -package cmd - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "strconv" - - "github.com/spf13/cobra" - "github.com/vulpemventures/nigiri/cli/constants" - "github.com/vulpemventures/nigiri/cli/controller" -) - -var FaucetCmd = &cobra.Command{ - Args: func(cmd *cobra.Command, args []string) error { - - if len(args) < 1 { - return errors.New("missing address") - } - return nil - }, - Use: "faucet
[amount] [asset]", - Short: "Generate and send bitcoin to given address", - RunE: faucet, - PreRunE: faucetChecks, -} - -func faucetChecks(cmd *cobra.Command, args []string) error { - datadir, _ := cmd.Flags().GetString("datadir") - isLiquidService, _ := cmd.Flags().GetBool("liquid") - - ctl, err := controller.NewController() - if err != nil { - return err - } - - if err := ctl.ParseDatadir(datadir); err != nil { - return err - } - if len(args) < 1 { - return constants.ErrInvalidArgs - } - - if isRunning, err := ctl.IsNigiriRunning(); err != nil { - return err - } else if !isRunning { - return constants.ErrNigiriNotRunning - } - - if err := ctl.ReadConfigFile(datadir); err != nil { - return err - } - - if isLiquidService && isLiquidService != ctl.GetConfigBoolField(constants.AttachLiquid) { - return constants.ErrNigiriLiquidNotEnabled - } - - return nil -} - -func faucet(cmd *cobra.Command, args []string) error { - isLiquidService, err := cmd.Flags().GetBool("liquid") - datadir, _ := cmd.Flags().GetString("datadir") - if err != nil { - return err - } - request := map[string]interface{}{ - "address": args[0], - } - if len(args) >= 2 { - amountFloat, err := strconv.ParseFloat(args[1], 64) - if err != nil { - return fmt.Errorf("invalid amount: %v", err) - } - request["amount"] = amountFloat - } - if len(args) == 3 { - request["asset"] = args[2] - } - - ctl, err := controller.NewController() - if err != nil { - return err - } - envPath := ctl.GetResourcePath(datadir, "env") - env, _ := ctl.ReadComposeEnvironment(envPath) - envPorts := env["ports"].(map[string]map[string]int) - requestPort := envPorts["bitcoin"]["chopsticks"] - if isLiquidService { - requestPort = envPorts["liquid"]["chopsticks"] - } - payload, err := json.Marshal(request) - if err != nil { - return err - } - res, err := http.Post("http://127.0.0.1:"+strconv.Itoa(requestPort)+"/faucet", "application/json", bytes.NewBuffer(payload)) - if err != nil { - return err - } - data, err := ioutil.ReadAll(res.Body) - if err != nil { - return err - } - if res.StatusCode != http.StatusOK { - return errors.New(string(data)) - } - - var dat map[string]string - if err := json.Unmarshal([]byte(data), &dat); err != nil { - return errors.New("internal error, please try again") - } - if dat["txId"] == "" { - return errors.New("Not Successful") - } - fmt.Println("txId: " + dat["txId"]) - return nil -} diff --git a/cli/cmd/faucet_test.go b/cli/cmd/faucet_test.go deleted file mode 100644 index eadaec5..0000000 --- a/cli/cmd/faucet_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package cmd - -import ( - "testing" - - "github.com/vulpemventures/nigiri/cli/constants" -) - -const ( - btcAddress = "mpSGWQvbAiRt2UNLST1CdWUufoPVsVwLyK" - liquidAddress = "CTEsqL1x9ooWWG9HBaHUpvS2DGJJ4haYdkTQPKj9U8CCdwT5vcudhbYUT8oQwwoS11aYtdznobfgT8rj" -) - -func TestFaucetBitcoinServices(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, bitcoin) - - if err := testCommand("faucet", btcAddress, bitcoin); err != nil { - t.Fatal(err) - } - - testDelete(t) -} - -func TestFaucetLiquidServices(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, liquid) - - if err := testCommand("faucet", liquidAddress, liquid); err != nil { - t.Fatal(err) - } - - testDelete(t) -} - -func TestFaucetShouldFail(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - expectedError := constants.ErrNigiriNotRunning.Error() - - err := testCommand("faucet", btcAddress, bitcoin) - if err == nil { - t.Fatal("Should return error when Nigiri is stopped") - } - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } - - err = testCommand("faucet", liquidAddress, liquid) - if err == nil { - t.Fatal("Should return error when Nigiri is stopped") - } - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } -} - -func TestStartBitcoinAndFaucetNigiriServicesShouldFail(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, bitcoin) - - expectedError := constants.ErrNigiriLiquidNotEnabled.Error() - - err := testCommand("faucet", liquidAddress, liquid) - if err == nil { - t.Fatal("Should return error when trying logging liquid services if not running") - } - - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } - - testDelete(t) -} diff --git a/cli/cmd/flags.go b/cli/cmd/flags.go deleted file mode 100644 index d48fb1d..0000000 --- a/cli/cmd/flags.go +++ /dev/null @@ -1,63 +0,0 @@ -package cmd - -import ( - "encoding/json" - "os" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/vulpemventures/nigiri/cli/config" - "github.com/vulpemventures/nigiri/cli/constants" -) - -var ( - flagDatadir string - flagNetwork string - flagDelete bool - flagAttachLiquid bool - flagLiquidService bool - flagEnv string - flagRpcWallet string -) - -var RootCmd = &cobra.Command{ - Use: "nigiri", - Short: "Nigiri lets you manage a full dockerized bitcoin environment", - Long: "Nigiri lets you create your dockerized environment with a bitcoin and optionally a liquid node + block explorer powered by an electrum server for every network", -} - -func init() { - c := &config.Config{} - viper := c.Viper() - defaultDir := c.GetPath() - defaultJSON, _ := json.Marshal(constants.DefaultEnv) - - RootCmd.PersistentFlags().StringVar(&flagDatadir, "datadir", defaultDir, "Set nigiri default directory") - StartCmd.PersistentFlags().StringVar(&flagNetwork, "network", "regtest", "Set bitcoin network - regtest only for now") - StartCmd.PersistentFlags().BoolVar(&flagAttachLiquid, "liquid", false, "Enable liquid sidechain") - StartCmd.PersistentFlags().StringVar(&flagEnv, "env", string(defaultJSON), "Set compose env in JSON format") - StopCmd.PersistentFlags().BoolVar(&flagDelete, "delete", false, "Stop and delete nigiri") - LogsCmd.PersistentFlags().BoolVar(&flagLiquidService, "liquid", false, "Set to see logs of a liquid service") - FaucetCmd.PersistentFlags().BoolVar(&flagLiquidService, "liquid", false, "Set to donate liquid btc") - PushCmd.PersistentFlags().BoolVar(&flagLiquidService, "liquid", false, "Set to use liquid") - RpcCmd.PersistentFlags().BoolVar(&flagLiquidService, "liquid", false, "Set to use liquid node") - RpcCmd.PersistentFlags().StringVar(&flagRpcWallet, "rpcwallet", "", "rpcwallet to be used for node JSONRPC commands") - - RootCmd.AddCommand(StartCmd) - RootCmd.AddCommand(StopCmd) - RootCmd.AddCommand(LogsCmd) - RootCmd.AddCommand(FaucetCmd) - RootCmd.AddCommand(RpcCmd) - RootCmd.AddCommand(MintCmd) - RootCmd.AddCommand(PushCmd) - RootCmd.AddCommand(VersionCmd) - - viper.BindPFlag(constants.Datadir, RootCmd.PersistentFlags().Lookup("datadir")) - viper.BindPFlag(constants.Network, StartCmd.PersistentFlags().Lookup("network")) - viper.BindPFlag(constants.AttachLiquid, StartCmd.PersistentFlags().Lookup("liquid")) - - cobra.OnInitialize(func() { - log.SetOutput(os.Stdout) - log.SetLevel(log.InfoLevel) - }) -} diff --git a/cli/cmd/logs.go b/cli/cmd/logs.go deleted file mode 100644 index cd659af..0000000 --- a/cli/cmd/logs.go +++ /dev/null @@ -1,107 +0,0 @@ -package cmd - -import ( - "errors" - "os" - "os/exec" - - "github.com/vulpemventures/nigiri/cli/constants" - "github.com/vulpemventures/nigiri/cli/controller" - - "github.com/spf13/cobra" -) - -var logsDescription = "Check Service logs. Requires one Service: " + servicesList() - -var LogsCmd = &cobra.Command{ - Args: func(cmd *cobra.Command, args []string) error { - - if len(args) != 1 { - return errors.New(logsDescription) - } - _, found := controller.Services[args[0]] - if !found { - return errors.New(logsDescription) - } - return nil - }, - Use: "logs ", - Short: logsDescription, - Long: logsDescription, - RunE: logs, - PreRunE: logsChecks, -} - -func servicesList() string { - var servicesString string - for key, _ := range controller.Services { - servicesString += key - servicesString += " | " - } - return servicesString[:len(servicesString)-3] -} - -func logsChecks(cmd *cobra.Command, args []string) error { - datadir, _ := cmd.Flags().GetString("datadir") - isLiquidService, _ := cmd.Flags().GetBool("liquid") - - ctl, err := controller.NewController() - if err != nil { - return err - } - - if err := ctl.ParseDatadir(datadir); err != nil { - return err - } - if len(args) != 1 { - return constants.ErrInvalidArgs - } - - service := args[0] - if err := ctl.ParseServiceName(service); err != nil { - return err - } - - if isRunning, err := ctl.IsNigiriRunning(); err != nil { - return err - } else if !isRunning { - return constants.ErrNigiriNotRunning - } - - if err := ctl.ReadConfigFile(datadir); err != nil { - return err - } - - if isLiquidService && isLiquidService != ctl.GetConfigBoolField(constants.AttachLiquid) { - return constants.ErrNigiriLiquidNotEnabled - } - - return nil -} - -func logs(cmd *cobra.Command, args []string) error { - service := args[0] - datadir, _ := cmd.Flags().GetString("datadir") - isLiquidService, _ := cmd.Flags().GetBool("liquid") - - ctl, err := controller.NewController() - if err != nil { - return err - } - - serviceName := ctl.GetServiceName(service, isLiquidService) - composePath := ctl.GetResourcePath(datadir, "compose") - envPath := ctl.GetResourcePath(datadir, "env") - env := ctl.LoadComposeEnvironment(envPath) - - bashCmd := exec.Command("docker-compose", "-f", composePath, "logs", serviceName) - bashCmd.Stdout = os.Stdout - bashCmd.Stderr = os.Stderr - bashCmd.Env = env - - if err := bashCmd.Run(); err != nil { - return err - } - - return nil -} diff --git a/cli/cmd/logs_test.go b/cli/cmd/logs_test.go deleted file mode 100644 index 36b06cf..0000000 --- a/cli/cmd/logs_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package cmd - -import ( - "testing" - - "github.com/vulpemventures/nigiri/cli/constants" -) - -var ( - serviceList = []string{"node", "electrs", "esplora", "chopsticks"} -) - -func TestLogBitcoinServices(t *testing.T) { - if err := testCommand("start", "", bitcoin); err != nil { - t.Fatal(err) - } - - for _, service := range serviceList { - if err := testCommand("logs", service, bitcoin); err != nil { - t.Fatal(err) - } - } - - if err := testCommand("stop", "", delete); err != nil { - t.Fatal(err) - } -} - -func TestLogLiquidServices(t *testing.T) { - if err := testCommand("start", "", liquid); err != nil { - t.Fatal(err) - } - - for _, service := range serviceList { - if err := testCommand("logs", service, liquid); err != nil { - t.Fatal(err) - } - } - - if err := testCommand("stop", "", delete); err != nil { - t.Fatal(err) - } -} - -func TestLogShouldFail(t *testing.T) { - expectedError := constants.ErrNigiriNotRunning.Error() - - err := testCommand("logs", serviceList[0], bitcoin) - if err == nil { - t.Fatal("Should return error when Nigiri is stopped") - } - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } - - err = testCommand("logs", serviceList[0], liquid) - if err == nil { - t.Fatal("Should return error when Nigiri is stopped") - } - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } -} - -func TestStartBitcoinAndLogNigiriServicesShouldFail(t *testing.T) { - if err := testCommand("start", "", bitcoin); err != nil { - t.Fatal(err) - } - - expectedError := constants.ErrNigiriLiquidNotEnabled.Error() - - err := testCommand("logs", serviceList[0], liquid) - if err == nil { - t.Fatal("Should return error when trying logging liquid services if not running") - } - - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } - - if err := testCommand("stop", "", delete); err != nil { - t.Fatal(err) - } -} diff --git a/cli/cmd/mint.go b/cli/cmd/mint.go deleted file mode 100644 index a2fdf5a..0000000 --- a/cli/cmd/mint.go +++ /dev/null @@ -1,121 +0,0 @@ -package cmd - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "strconv" - - "github.com/spf13/cobra" - "github.com/vulpemventures/nigiri/cli/constants" - "github.com/vulpemventures/nigiri/cli/controller" -) - -var MintCmd = &cobra.Command{ - Use: "mint
[name] [ticker]", - Short: "Liquid only: Issue and send a given quantity of an asset", - RunE: mint, - PreRunE: mintChecks, -} - -func mintChecks(cmd *cobra.Command, args []string) error { - datadir, _ := cmd.Flags().GetString("datadir") - - ctl, err := controller.NewController() - if err != nil { - return err - } - - if err := ctl.ParseDatadir(datadir); err != nil { - return err - } - if len(args) < 2 { - return errors.New("missing required arguments") - } - - if isRunning, err := ctl.IsNigiriRunning(); err != nil { - return err - } else if !isRunning { - return constants.ErrNigiriNotRunning - } - - if err := ctl.ReadConfigFile(datadir); err != nil { - return err - } - - if ctl.GetConfigBoolField(constants.AttachLiquid) != true { - return constants.ErrNigiriLiquidNotEnabled - } - - return nil -} - -func mint(cmd *cobra.Command, args []string) error { - datadir, _ := cmd.Flags().GetString("datadir") - - var request struct { - Address string `json:"address"` - Quantity int `json:"quantity"` - Name string `json:"name"` - Ticker string `json:"ticker"` - } - request.Address = args[0] - request.Quantity, _ = strconv.Atoi(args[1]) - if len(args) >= 3 { - request.Name = args[2] - } - if len(args) == 4 { - request.Ticker = args[3] - } - - ctl, err := controller.NewController() - if err != nil { - return err - } - envPath := ctl.GetResourcePath(datadir, "env") - env, _ := ctl.ReadComposeEnvironment(envPath) - envPorts := env["ports"].(map[string]map[string]int) - requestPort := envPorts["liquid"]["chopsticks"] - - payload, err := json.Marshal(request) - if err != nil { - return err - } - res, err := http.Post("http://127.0.0.1:"+strconv.Itoa(requestPort)+"/mint", "application/json", bytes.NewBuffer(payload)) - if err != nil { - return err - } - - data, err := ioutil.ReadAll(res.Body) - if err != nil { - return err - } - if res.StatusCode != http.StatusOK { - return errors.New(string(data)) - } - var dat map[string]interface{} - var resp string - if err := json.Unmarshal([]byte(data), &dat); err != nil { - return errors.New("Internal error. Try again.") - } - if dat["txId"] == "" { - return errors.New("Not Successful") - } - for key, element := range dat { - if key == "issuance_txin" { - myMap := element.(map[string]interface{}) - resp += key + ":\n" - for key2, element2 := range myMap { - resp += " " - resp += key2 + ": " + fmt.Sprintf("%v", element2) + "\n" - } - continue - } - resp += key + ": " + fmt.Sprintf("%v", element) + "\n" - } - fmt.Println(resp[:len(resp)-1]) - return nil -} diff --git a/cli/cmd/mint_test.go b/cli/cmd/mint_test.go deleted file mode 100644 index 5ef2031..0000000 --- a/cli/cmd/mint_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package cmd - -import ( - "testing" - - "github.com/vulpemventures/nigiri/cli/constants" -) - -func TestMintOneArg(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, liquid) - - if err := testCommand("mint", "ert1q90dz89u8eudeswzynl3p2jke564ejc2cnfcwuq 1000", liquid); err != nil { - t.Fatal(err) - } - - testDelete(t) -} - -func TestMintTwoArgs(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, liquid) - - if err := testCommand("mint", "ert1q90dz89u8eudeswzynl3p2jke564ejc2cnfcwuq 2000 Test", liquid); err != nil { - t.Fatal(err) - } - - testDelete(t) -} - -func TestMintThreeArgs(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, liquid) - - if err := testCommand("mint", "ert1q90dz89u8eudeswzynl3p2jke564ejc2cnfcwuq 3000 TEST TST", liquid); err != nil { - t.Fatal(err) - } - testDelete(t) - -} - -func TestStartBitcoinAndMintShouldFail(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, bitcoin) - - expectedError := constants.ErrNigiriLiquidNotEnabled.Error() - - err := testCommand("mint", "ert1q90dz89u8eudeswzynl3p2jke564ejc2cnfcwuq 1000", liquid) - if err == nil { - t.Fatal("Should return error when trying logging liquid services if not running") - } - - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } - - testDelete(t) -} diff --git a/cli/cmd/push.go b/cli/cmd/push.go deleted file mode 100644 index bec8bdb..0000000 --- a/cli/cmd/push.go +++ /dev/null @@ -1,99 +0,0 @@ -package cmd - -import ( - "bytes" - "errors" - "fmt" - "io/ioutil" - "net/http" - "strconv" - - "github.com/spf13/cobra" - "github.com/vulpemventures/nigiri/cli/constants" - "github.com/vulpemventures/nigiri/cli/controller" -) - -var PushCmd = &cobra.Command{ - Args: func(cmd *cobra.Command, args []string) error { - - if len(args) != 1 { - return errors.New("Missing hex encoded transaction") - } - return nil - }, - Use: "push ", - Short: "Broadcast raw transaction", - RunE: push, - PreRunE: pushChecks, -} - -func pushChecks(cmd *cobra.Command, args []string) error { - datadir, _ := cmd.Flags().GetString("datadir") - isLiquidService, _ := cmd.Flags().GetBool("liquid") - - ctl, err := controller.NewController() - if err != nil { - return err - } - - if err := ctl.ParseDatadir(datadir); err != nil { - return err - } - if len(args) != 1 { - return constants.ErrInvalidArgs - } - - if isRunning, err := ctl.IsNigiriRunning(); err != nil { - return err - } else if !isRunning { - return constants.ErrNigiriNotRunning - } - - if err := ctl.ReadConfigFile(datadir); err != nil { - return err - } - - if isLiquidService && isLiquidService != ctl.GetConfigBoolField(constants.AttachLiquid) { - return constants.ErrNigiriLiquidNotEnabled - } - - return nil -} - -func push(cmd *cobra.Command, args []string) error { - isLiquidService, err := cmd.Flags().GetBool("liquid") - datadir, _ := cmd.Flags().GetString("datadir") - if err != nil { - return err - } - request := args[0] - ctl, err := controller.NewController() - if err != nil { - return err - } - envPath := ctl.GetResourcePath(datadir, "env") - env, _ := ctl.ReadComposeEnvironment(envPath) - envPorts := env["ports"].(map[string]map[string]int) - requestPort := envPorts["bitcoin"]["chopsticks"] - if isLiquidService { - requestPort = envPorts["liquid"]["chopsticks"] - } - hex := []byte(request) - res, err := http.Post("http://127.0.0.1:"+strconv.Itoa(requestPort)+"/tx", "application/string", bytes.NewBuffer(hex)) - if err != nil { - return err - } - data, err := ioutil.ReadAll(res.Body) - if err != nil { - return err - } - if res.StatusCode != http.StatusOK { - return errors.New(string(data)) - } - - if string(data) == "" { - return errors.New("Not Successful") - } - fmt.Println("\ntxId: " + string(data)) - return nil -} diff --git a/cli/cmd/push_test.go b/cli/cmd/push_test.go deleted file mode 100644 index 0b913b4..0000000 --- a/cli/cmd/push_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package cmd - -import ( - "encoding/json" - "errors" - "fmt" - "os/exec" - "strconv" - "strings" - "testing" -) - -/* func TestPushBitcoinTransaction(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, bitcoin) - hex, err := getNewSignedTransaction(bitcoin) - if err != nil { - t.Fatal(err) - } - if err := testCommand("push", hex, bitcoin); err != nil { - t.Fatal(err) - } - testDelete(t) -} */ - -func TestPushLiquidTransaction(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, liquid) - hex, err := getNewSignedTransaction(liquid) - if err != nil { - t.Fatal(err) - } - if err := testCommand("push", hex, liquid); err != nil { - t.Fatal(err) - } - testDelete(t) -} - -func getNewSignedTransaction(isLiquid bool) (string, error) { - txId, vout, amount, asset, err := listUnspent(isLiquid) - if err != nil { - return "", err - } - hex, err := createRawTransaction(txId, vout, amount, asset, isLiquid) - if err != nil { - return "", err - } - hexFinal, err := signRawTransaction(hex, isLiquid) - if err != nil { - return "", err - } - return hexFinal, nil -} - -func listUnspent(isLiquid bool) (string, string, string, string, error) { - bashCmd, err := execCommand([]string{"listunspent"}, isLiquid) - if err != nil { - return "", "", "", "", err - } - type unspent map[string]interface{} - var unspentList []unspent - if err := json.Unmarshal([]byte(bashCmd), &unspentList); err != nil { - return "", "", "", "", errors.New("Internal error. Try again.") - } - txId := fmt.Sprintf("%v", unspentList[0]["txid"]) - vout := fmt.Sprintf("%v", unspentList[0]["vout"]) - amount := fmt.Sprintf("%v", unspentList[0]["amount"]) - asset := "" - if isLiquid { - asset = fmt.Sprintf("%v", unspentList[0]["asset"]) - } - return txId, vout, amount, asset, nil -} - -func createRawTransaction(txId string, vout string, amount string, asset string, isLiquid bool) (string, error) { - type inputs map[string]interface{} - var inputsList [1]inputs - voutInt, err := strconv.Atoi(vout) - if err != nil { - return "", err - } - inputsList[0] = make(inputs, 2) - inputsList[0]["txid"] = txId - inputsList[0]["vout"] = voutInt - inputsJson, err := json.Marshal(inputsList) - if err != nil { - return "", err - } - fee := 0.00001 - amountInt, err := strconv.ParseFloat(amount, 64) - if err != nil { - return "", err - } - amountSend := fmt.Sprintf("%.6f", amountInt-fee) - sendAdress, err := execCommand([]string{"getnewaddress"}, isLiquid) - sendAdressString := string(sendAdress) - if err != nil { - return "", err - } - outputs := make(map[string]interface{}) - if isLiquid { - addressInfoJson, err := execCommand([]string{"getaddressinfo", string(sendAdress)}, isLiquid) - if err != nil { - return "", err - } - var adressInfo map[string]interface{} - if err := json.Unmarshal([]byte(addressInfoJson), &adressInfo); err != nil { - return "", errors.New("Internal error. Try again.") - } - sendAdressString = fmt.Sprintf("%v", adressInfo["unconfidential"]) - outputs["fee"] = fee - } - outputs[sendAdressString], err = strconv.ParseFloat(amountSend, 64) - if err != nil { - return "", err - } - outputsJson, err := json.Marshal(outputs) - if err != nil { - return "", err - } - commandArgs := []string{"createrawtransaction", string(inputsJson), string(outputsJson)} - if isLiquid { - output_assets := make(map[string]interface{}) - output_assets[sendAdressString] = asset - output_assets["fee"] = asset - output_assetsJson, err := json.Marshal(output_assets) - if err != nil { - return "", err - } - commandArgs = append(commandArgs, []string{"0", "false", string(output_assetsJson)}...) - } - bashCmd, err := execCommand(commandArgs, isLiquid) - if err != nil { - return "", err - } - return strings.Fields(string(bashCmd))[0], nil -} - -func signRawTransaction(hex string, isLiquid bool) (string, error) { - bashCmd, err := execCommand([]string{"signrawtransactionwithwallet", hex}, isLiquid) - if err != nil { - return "", err - } - var hexJson map[string]interface{} - if err := json.Unmarshal([]byte(bashCmd), &hexJson); err != nil { - return "", errors.New("Internal error. Try again.") - } - hex = fmt.Sprintf("%v", hexJson["hex"]) - return hex, nil -} - -func execCommand(args []string, isLiquid bool) ([]byte, error) { - rpcArgs := []string{"exec", "bitcoin", "bitcoin-cli", "-datadir=config"} - if isLiquid { - rpcArgs = []string{"exec", "liquid", "elements-cli", "-datadir=config"} - } - cmdArgs := append(rpcArgs, args...) - bashCmd, err := exec.Command("docker", cmdArgs...).Output() - if err != nil { - return nil, err - } - return bashCmd, nil -} diff --git a/cli/cmd/rpc.go b/cli/cmd/rpc.go deleted file mode 100644 index 0c7860a..0000000 --- a/cli/cmd/rpc.go +++ /dev/null @@ -1,77 +0,0 @@ -package cmd - -import ( - "os" - "os/exec" - - "github.com/spf13/cobra" - "github.com/vulpemventures/nigiri/cli/constants" - "github.com/vulpemventures/nigiri/cli/controller" -) - -var RpcCmd = &cobra.Command{ - Use: "rpc ", - Short: "Invoke the bitcoin-cli or elements-cli", - RunE: rpc, - PreRunE: rpcChecks, -} - -func rpcChecks(cmd *cobra.Command, args []string) error { - datadir, _ := cmd.Flags().GetString("datadir") - isLiquidService, _ := cmd.Flags().GetBool("liquid") - - ctl, err := controller.NewController() - if err != nil { - return err - } - - if err := ctl.ParseDatadir(datadir); err != nil { - return err - } - - if isRunning, err := ctl.IsNigiriRunning(); err != nil { - return err - } else if !isRunning { - return constants.ErrNigiriNotRunning - } - - if err := ctl.ReadConfigFile(datadir); err != nil { - return err - } - - if isLiquidService && isLiquidService != ctl.GetConfigBoolField(constants.AttachLiquid) { - return constants.ErrNigiriLiquidNotEnabled - } - - return nil -} - -func rpc(cmd *cobra.Command, args []string) error { - datadir, _ := cmd.Flags().GetString("datadir") - isLiquidService, _ := cmd.Flags().GetBool("liquid") - rpcWallet, _ := cmd.Flags().GetString("rpcwallet") - - ctl, err := controller.NewController() - if err != nil { - return err - } - - envPath := ctl.GetResourcePath(datadir, "env") - env := ctl.LoadComposeEnvironment(envPath) - - rpcArgs := []string{"exec", "bitcoin", "bitcoin-cli", "-datadir=config", "-rpcwallet=" + rpcWallet} - if isLiquidService { - rpcArgs = []string{"exec", "liquid", "elements-cli", "-datadir=config", "-rpcwallet=" + rpcWallet} - } - cmdArgs := append(rpcArgs, args...) - bashCmd := exec.Command("docker", cmdArgs...) - bashCmd.Stdout = os.Stdout - bashCmd.Stderr = os.Stderr - bashCmd.Env = env - - if err := bashCmd.Run(); err != nil { - return err - } - - return nil -} diff --git a/cli/cmd/rpc_test.go b/cli/cmd/rpc_test.go deleted file mode 100644 index 56cb942..0000000 --- a/cli/cmd/rpc_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package cmd - -import ( - "testing" - - "github.com/vulpemventures/nigiri/cli/constants" -) - -func TestRpcBitcoinCommand(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, bitcoin) - - if err := testCommand("rpc", "getblockchaininfo", bitcoin); err != nil { - t.Fatal(err) - } - - testDelete(t) -} - -func TestRpcBitcoinTwoCommands(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, bitcoin) - - if err := testCommand("rpc", "getblockhash 0", bitcoin); err != nil { - t.Fatal(err) - } - - testDelete(t) -} - -func TestRpcLiquidCommand(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, liquid) - - if err := testCommand("rpc", "getblockchaininfo", liquid); err != nil { - t.Fatal(err) - } - - testDelete(t) -} - -func TestRpcLiquidTwoCommands(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, liquid) - - if err := testCommand("rpc", "getblockhash 0", liquid); err != nil { - t.Fatal(err) - } - - testDelete(t) -} - -func TestRpcShouldFail(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - expectedError := constants.ErrNigiriNotRunning.Error() - - err := testCommand("rpc", "getblockchaininfo", bitcoin) - if err == nil { - t.Fatal("Should return error when Nigiri is stopped") - } - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } - - err = testCommand("rpc", "getblockchaininfo", liquid) - if err == nil { - t.Fatal("Should return error when Nigiri is stopped") - } - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } -} - -func TestStartBitcoinAndRpcNigiriServicesShouldFail(t *testing.T) { - if testing.Short() { - t.Skip("skipping testing in short mode") - } - testStart(t, bitcoin) - - expectedError := constants.ErrNigiriLiquidNotEnabled.Error() - - err := testCommand("rpc", "getblockchaininfo", liquid) - if err == nil { - t.Fatal("Should return error when trying logging liquid services if not running") - } - - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } - - testDelete(t) -} diff --git a/cli/cmd/start.go b/cli/cmd/start.go deleted file mode 100644 index 4883868..0000000 --- a/cli/cmd/start.go +++ /dev/null @@ -1,136 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "os/exec" - "strings" - - "github.com/spf13/cobra" - "github.com/vulpemventures/nigiri/cli/constants" - "github.com/vulpemventures/nigiri/cli/controller" -) - -var StartCmd = &cobra.Command{ - Use: "start", - Short: "Build and start Nigiri", - RunE: start, - PreRunE: startChecks, -} - -func startChecks(cmd *cobra.Command, args []string) error { - network, _ := cmd.Flags().GetString("network") - datadir, _ := cmd.Flags().GetString("datadir") - env, _ := cmd.Flags().GetString("env") - - ctl, err := controller.NewController() - if err != nil { - return err - } - - if err := ctl.ParseNetwork(network); err != nil { - return err - } - - if err := ctl.ParseDatadir(datadir); err != nil { - return err - } - composeEnv, err := ctl.ParseEnv(env) - if err != nil { - return err - } - - // if nigiri is already running return error - if isRunning, err := ctl.IsNigiriRunning(); err != nil { - return err - } else if isRunning { - return constants.ErrNigiriAlreadyRunning - } - - // scratch datadir if not exists - if err := os.MkdirAll(datadir, 0755); err != nil { - return err - } - - // if datadir is set we must copy the resources directory from ~/.nigiri - // to the new one - if datadir != ctl.GetDefaultDatadir() { - if err := ctl.NewDatadirFromDefault(datadir); err != nil { - return err - } - } - - // if nigiri not exists, we need to write the configuration file and then - // read from it to get viper updated, otherwise we just read from it. - if isStopped, err := ctl.IsNigiriStopped(); err != nil { - return err - } else if isStopped { - if err := ctl.ReadConfigFile(datadir); err != nil { - return err - } - } else { - filedir := ctl.GetResourcePath(datadir, "config") - if err := ctl.WriteConfigFile(filedir); err != nil { - return err - } - // .env must be in the directory where docker-compose is run from, not where YAML files are placed - // https://docs.docker.com/compose/env-file/ - filedir = ctl.GetResourcePath(datadir, "env") - if err := ctl.WriteComposeEnvironment(filedir, composeEnv); err != nil { - return err - } - } - - return nil -} - -func start(cmd *cobra.Command, args []string) error { - ctl, err := controller.NewController() - if err != nil { - return err - } - - datadir, _ := cmd.Flags().GetString("datadir") - liquidEnabled := ctl.GetConfigBoolField(constants.AttachLiquid) - - envPath := ctl.GetResourcePath(datadir, "env") - composePath := ctl.GetResourcePath(datadir, "compose") - - bashCmd := exec.Command("docker-compose", "-f", composePath, "up", "-d") - if isStopped, err := ctl.IsNigiriStopped(); err != nil { - return err - } else if isStopped { - bashCmd = exec.Command("docker-compose", "-f", composePath, "start") - } - bashCmd.Stdout = os.Stdout - bashCmd.Stderr = os.Stderr - bashCmd.Env = ctl.LoadComposeEnvironment(envPath) - - if err := bashCmd.Run(); err != nil { - return err - } - - path := ctl.GetResourcePath(datadir, "env") - env, err := ctl.ReadComposeEnvironment(path) - if err != nil { - return err - } - - prettyPrintServices := func(chain string, services map[string]int) { - fmt.Printf("%s services:\n", strings.Title(chain)) - for name, port := range services { - formatName := fmt.Sprintf("%s:", name) - fmt.Printf(" %-14s localhost:%d\n", formatName, port) - } - } - - for chain, services := range env["ports"].(map[string]map[string]int) { - if chain == "bitcoin" { - prettyPrintServices(chain, services) - } else if liquidEnabled { - prettyPrintServices(chain, services) - } - } - - return nil -} diff --git a/cli/cmd/start_stop_test.go b/cli/cmd/start_stop_test.go deleted file mode 100644 index 43acf54..0000000 --- a/cli/cmd/start_stop_test.go +++ /dev/null @@ -1,185 +0,0 @@ -package cmd - -import ( - "fmt" - "strings" - "testing" - "time" - - "github.com/vulpemventures/nigiri/cli/constants" - "github.com/vulpemventures/nigiri/cli/controller" -) - -const ( - liquid = true - bitcoin = false - delete = true -) - -var ( - stopCmd = []string{"stop"} - // deleteCmd = append(stopCmd, "--delete") - startCmd = []string{"start"} - // liquidStartCmd = append(startCmd, "--liquid") -) - -func TestStartStopLiquid(t *testing.T) { - // Start/Stop - testStart(t, liquid) - testStop(t) - // Start/Delete - testStart(t, liquid) - testDelete(t) -} - -func TestStartStopBitcoin(t *testing.T) { - // Start/Stop - testStart(t, bitcoin) - testStop(t) - // Start/Delete - testStart(t, bitcoin) - testDelete(t) -} - -func TestStopBeforeStartShouldFail(t *testing.T) { - expectedError := constants.ErrNigiriNotRunning.Error() - - err := testCommand("stop", "", !delete) - if err == nil { - t.Fatal("Should return error when trying to stop before starting") - } - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } - - expectedError = constants.ErrNigiriNotExisting.Error() - err = testCommand("stop", "", delete) - if err == nil { - t.Fatal("Should return error when trying to delete before starting") - } - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } -} - -func TestStartAfterStartShouldFail(t *testing.T) { - expectedError := constants.ErrNigiriAlreadyRunning.Error() - - if err := testCommand("start", "", bitcoin); err != nil { - t.Fatal(err) - } - - err := testCommand("start", "", bitcoin) - if err == nil { - t.Fatal("Should return error when trying to start Nigiri if already started") - } - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } - - err = testCommand("start", "", liquid) - if err == nil { - t.Fatal("Should return error when trying to start Nigiri if already started") - } - if err.Error() != expectedError { - t.Fatalf("Expected error: %s, got: %s", expectedError, err) - } - - if err := testCommand("stop", "", delete); err != nil { - t.Fatal(err) - } -} - -func testStart(t *testing.T, flag bool) { - ctl, err := controller.NewController() - if err != nil { - t.Fatal(err) - } - if err := testCommand("start", "", flag); err != nil { - t.Fatal(err) - } - //Give some time to nigiri to be ready before calling - time.Sleep(5 * time.Second) - if isRunning, err := ctl.IsNigiriRunning(); err != nil { - t.Fatal(err) - } else if !isRunning { - t.Fatal("Nigiri should have been started but services have not been found among running containers") - } -} - -func testStop(t *testing.T) { - ctl, err := controller.NewController() - if err != nil { - t.Fatal(err) - } - if err := testCommand("stop", "", !delete); err != nil { - t.Fatal(err) - } - //Give some time to nigiri to be ready before calling - time.Sleep(5 * time.Second) - if isStopped, err := ctl.IsNigiriStopped(); err != nil { - t.Fatal(err) - } else if !isStopped { - t.Fatal("Nigiri should have been stopped but services have not been found among stopped containers") - } -} - -func testDelete(t *testing.T) { - ctl, err := controller.NewController() - if err != nil { - t.Fatal(err) - } - - if err := testCommand("stop", "", delete); err != nil { - t.Fatal(err) - } - if isStopped, err := ctl.IsNigiriStopped(); err != nil { - t.Fatal(err) - } else if isStopped { - t.Fatal("Nigiri should have been terminated at this point but services have been found among stopped containers") - } -} - -func testCommand(command, arg string, flag bool) error { - cmd := RootCmd - cmd.SetArgs(nil) - - if command == "start" { - args := append(startCmd, fmt.Sprintf("--liquid=%t", flag)) - cmd.SetArgs(args) - } - if command == "stop" { - args := append(stopCmd, fmt.Sprintf("--delete=%t", flag)) - cmd.SetArgs(args) - } - if command == "logs" { - logsCmd := []string{command, arg, fmt.Sprintf("--liquid=%t", flag)} - cmd.SetArgs(logsCmd) - } - if command == "faucet" { - faucetCmd := []string{command, arg, fmt.Sprintf("--liquid=%t", flag)} - cmd.SetArgs(faucetCmd) - } - if command == "rpc" { - logsCmd := []string{command, fmt.Sprintf("--liquid=%t", flag)} - args := strings.Fields(arg) - logsCmd = append(logsCmd, args...) - cmd.SetArgs(logsCmd) - } - if command == "mint" { - logsCmd := []string{command} - args := strings.Fields(arg) - logsCmd = append(logsCmd, args...) - cmd.SetArgs(logsCmd) - } - if command == "push" { - pushCmd := []string{command, arg, fmt.Sprintf("--liquid=%t", flag)} - cmd.SetArgs(pushCmd) - } - - if err := cmd.Execute(); err != nil { - return err - } - - return nil -} diff --git a/cli/cmd/stop.go b/cli/cmd/stop.go deleted file mode 100644 index b915727..0000000 --- a/cli/cmd/stop.go +++ /dev/null @@ -1,103 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "os/exec" - - "github.com/spf13/cobra" - "github.com/vulpemventures/nigiri/cli/constants" - "github.com/vulpemventures/nigiri/cli/controller" -) - -var StopCmd = &cobra.Command{ - Use: "stop", - Short: "Stop and/or delete nigiri", - RunE: stop, - PreRunE: stopChecks, -} - -func stopChecks(cmd *cobra.Command, args []string) error { - datadir, _ := cmd.Flags().GetString("datadir") - delete, _ := cmd.Flags().GetBool("delete") - - ctl, err := controller.NewController() - if err != nil { - return err - } - - if err := ctl.ParseDatadir(datadir); err != nil { - return err - } - - if _, err := os.Stat(datadir); os.IsNotExist(err) { - return constants.ErrDatadirNotExisting - } - - if isRunning, err := ctl.IsNigiriRunning(); err != nil { - return err - } else if !isRunning { - if delete { - if isStopped, err := ctl.IsNigiriStopped(); err != nil { - return err - } else if !isStopped { - return constants.ErrNigiriNotExisting - } - } else { - return constants.ErrNigiriNotRunning - } - } - - if err := ctl.ReadConfigFile(datadir); err != nil { - return err - } - return nil -} - -func stop(cmd *cobra.Command, args []string) error { - delete, _ := cmd.Flags().GetBool("delete") - datadir, _ := cmd.Flags().GetString("datadir") - - ctl, err := controller.NewController() - if err != nil { - return err - } - - composePath := ctl.GetResourcePath(datadir, "compose") - configPath := ctl.GetResourcePath(datadir, "config") - envPath := ctl.GetResourcePath(datadir, "env") - env := ctl.LoadComposeEnvironment(envPath) - - bashCmd := exec.Command("docker-compose", "-f", composePath, "stop") - if delete { - bashCmd = exec.Command("docker-compose", "-f", composePath, "down") - } - bashCmd.Stdout = os.Stdout - bashCmd.Stderr = os.Stderr - bashCmd.Env = env - - if err := bashCmd.Run(); err != nil { - return err - } - - if delete { - fmt.Println("Removing data from volumes...") - if err := ctl.CleanResourceVolumes(datadir); err != nil { - return err - } - - fmt.Println("Removing configuration file...") - if err := os.Remove(configPath); err != nil { - return err - } - - fmt.Println("Removing environmet file...") - if err := os.Remove(envPath); err != nil { - return err - } - - fmt.Println("Nigiri has been cleaned up successfully.") - } - - return nil -} diff --git a/cli/cmd/version.go b/cli/cmd/version.go deleted file mode 100644 index 3b6a226..0000000 --- a/cli/cmd/version.go +++ /dev/null @@ -1,25 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -var ( - version = "dev" - commit = "none" - date = "unknown" - VersionCmd = &cobra.Command{ - Use: "version", - Short: "Show the current version", - RunE: versionFunc, - } -) - -func versionFunc(cmd *cobra.Command, address []string) error { - fmt.Println("Version: " + version) - fmt.Println("Commit: " + commit) - fmt.Println("Date: " + date) - return nil -} diff --git a/cli/config/main.go b/cli/config/main.go deleted file mode 100644 index 54b2893..0000000 --- a/cli/config/main.go +++ /dev/null @@ -1,64 +0,0 @@ -package config - -import ( - "path/filepath" - - homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/viper" - "github.com/vulpemventures/nigiri/cli/constants" -) - -var vip *viper.Viper - -func init() { - vip = viper.New() - defaults := viper.New() - - newDefaultConfig(defaults) - setConfigFromDefaults(vip, defaults) -} - -type Config struct{} - -func (c *Config) Viper() *viper.Viper { - return vip -} - -func (c *Config) ReadFromFile(path string) error { - vip.SetConfigFile(filepath.Join(path, constants.Filename)) - return vip.ReadInConfig() -} - -func (c *Config) WriteConfig(path string) error { - vip.SetConfigFile(path) - return vip.WriteConfig() -} - -func (c *Config) GetString(str string) string { - return vip.GetString(str) -} - -func (c *Config) GetBool(str string) bool { - return vip.GetBool(str) -} - -func (c *Config) GetPath() string { - return getPath() -} - -func getPath() string { - home, _ := homedir.Expand("~") - return filepath.Join(home, ".nigiri") -} - -func newDefaultConfig(v *viper.Viper) { - v.SetDefault(constants.Datadir, getPath()) - v.SetDefault(constants.Network, "regtest") - v.SetDefault(constants.AttachLiquid, false) -} - -func setConfigFromDefaults(v *viper.Viper, d *viper.Viper) { - for key, value := range d.AllSettings() { - v.SetDefault(key, value) - } -} diff --git a/cli/constants/constants.go b/cli/constants/constants.go deleted file mode 100644 index 6e0cde7..0000000 --- a/cli/constants/constants.go +++ /dev/null @@ -1,71 +0,0 @@ -package constants - -import ( - "errors" -) - -const ( - // Datadir key in config json - Datadir = "datadir" - // Network key in config json - Network = "network" - // Filename key in config json - Filename = "nigiri.config.json" - // AttachLiquid key in config json - AttachLiquid = "attachLiquid" - // Version key in config json - Version = "version" -) - -var ( - AvaliableNetworks = []string{"regtest"} - NigiriBitcoinImages = []string{ - "ghcr.io/vulpemventures/bitcoin:latest", - "ghcr.io/vulpemventures/electrs:latest", - "ghcr.io/vulpemventures/esplora:latest", - "ghcr.io/vulpemventures/nigiri-chopsticks:latest", - } - NigiriLiquidImages = []string{ - "ghcr.io/vulpemventures/elements:latest", - "ghcr.io/vulpemventures/electrs-liquid:latest", - } - NigiriImages = append(NigiriBitcoinImages, NigiriLiquidImages...) - DefaultEnv = map[string]interface{}{ - "ports": map[string]map[string]int{ - "bitcoin": { - "peer": 18432, - "node": 18433, - "esplora": 5000, - "electrs": 3002, - "electrs_rpc": 51401, - "chopsticks": 3000, - }, - "liquid": { - "peer": 7040, - "node": 7041, - "esplora": 5001, - "electrs": 3012, - "electrs_rpc": 60401, - "chopsticks": 3001, - }, - }, - "urls": map[string]string{ - "bitcoin_esplora": "http://localhost:3000", - "liquid_esplora": "http://localhost:3001", - }, - } - - ErrInvalidNetwork = errors.New("Network provided is not valid") - ErrInvalidDatadir = errors.New("Datadir provided is not valid: it must be an absolute path") - ErrInvalidServiceName = errors.New("Service provided is not valid") - ErrInvalidArgs = errors.New("Invalid number of args") - ErrInvalidJSON = errors.New("JSON environment provided is not valid: missing required fields") - ErrMalformedJSON = errors.New("Failed to parse malformed JSON environment") - ErrEmptyJSON = errors.New("JSON environment provided is not valid: it must not be empty") - ErrDatadirNotExisting = errors.New("Datadir provided is not valid: it must be an existing path") - ErrNigiriNotRunning = errors.New("Nigiri is not running") - ErrNigiriNotExisting = errors.New("Nigiri does not exists, cannot delete") - ErrNigiriAlreadyRunning = errors.New("Nigiri is already running, please stop it first") - ErrNigiriLiquidNotEnabled = errors.New("Nigiri has been started with no Liquid sidechain.\nPlease stop and restart it using the --liquid flag") - ErrDockerNotRunning = errors.New("Nigiri requires the Docker daemon to be running, but it not seems to be started") -) diff --git a/cli/controller/controller.go b/cli/controller/controller.go deleted file mode 100644 index 3e18a82..0000000 --- a/cli/controller/controller.go +++ /dev/null @@ -1,187 +0,0 @@ -package controller - -import ( - "fmt" - "os/exec" - "path/filepath" - - "github.com/vulpemventures/nigiri/cli/config" - "github.com/vulpemventures/nigiri/cli/constants" -) - -var Services = map[string]bool{ - "node": true, - "esplora": true, - "electrs": true, - "chopsticks": true, -} - -// Controller implements useful functions to securely parse flags provided at run-time -// and to interact with the resources used by Nigiri: -// * docker -// * .env for docker-compose -// * nigiri.config.json config file -type Controller struct { - config *config.Config - parser *Parser - docker *Docker - env *Env -} - -// NewController returns a new Controller instance or error -func NewController() (*Controller, error) { - c := &Controller{} - - dockerClient := &Docker{} - if err := dockerClient.New(); err != nil { - return nil, err - } - c.env = &Env{} - c.parser = newParser(Services) - c.docker = dockerClient - c.config = &config.Config{} - return c, nil -} - -// ParseNetwork checks if a valid network has been provided -func (c *Controller) ParseNetwork(network string) error { - return c.parser.parseNetwork(network) -} - -// ParseDatadir checks if a valid datadir has been provided -func (c *Controller) ParseDatadir(path string) error { - return c.parser.parseDatadir(path) -} - -// ParseEnv checks if a valid JSON format for docker compose has been provided -func (c *Controller) ParseEnv(env string) (string, error) { - return c.parser.parseEnvJSON(env) -} - -// ParseServiceName checks if a valid service has been provided -func (c *Controller) ParseServiceName(name string) error { - return c.parser.parseServiceName(name) -} - -// IsNigiriRunning checks if nigiri is running by looking if the bitcoin -// services are in the list of docker running containers -func (c *Controller) IsNigiriRunning() (bool, error) { - if !c.docker.isDockerRunning() { - return false, constants.ErrDockerNotRunning - } - return c.docker.isNigiriRunning(), nil -} - -// IsNigiriStopped checks that nigiri is not actually running and that -// the bitcoin services appear in the list of non running containers -func (c *Controller) IsNigiriStopped() (bool, error) { - if !c.docker.isDockerRunning() { - return false, constants.ErrDockerNotRunning - } - return c.docker.isNigiriStopped(), nil -} - -// WriteComposeEnvironment creates a .env in datadir used by -// the docker-compose YAML file resource -func (c *Controller) WriteComposeEnvironment(datadir, env string) error { - return c.env.writeEnvForCompose(datadir, env) -} - -// ReadComposeEnvironment reads from .env and returns it as a useful type -func (c *Controller) ReadComposeEnvironment(datadir string) (map[string]interface{}, error) { - return c.env.readEnvForCompose(datadir) -} - -// LoadComposeEnvironment returns an os.Environ created from datadir/.env resource -func (c *Controller) LoadComposeEnvironment(datadir string) []string { - return c.env.load(datadir) -} - -// WriteConfigFile writes the configuration handled by the underlying viper -// into the file at filedir path -func (c *Controller) WriteConfigFile(filedir string) error { - return c.config.WriteConfig(filedir) -} - -// ReadConfigFile reads the configuration of the file at filedir path -func (c *Controller) ReadConfigFile(filedir string) error { - return c.config.ReadFromFile(filedir) -} - -// GetConfigBoolField returns a bool field of the config file -func (c *Controller) GetConfigBoolField(field string) bool { - return c.config.GetBool(field) -} - -// GetConfigStringField returns a string field of the config file -func (c *Controller) GetConfigStringField(field string) string { - return c.config.GetString(field) -} - -// NewDatadirFromDefault copies the default ~/.nigiri at the desidered path -// and cleans the docker volumes to make a fresh Nigiri instance -func (c *Controller) NewDatadirFromDefault(datadir string) error { - defaultDatadir := c.config.GetPath() - cmd := exec.Command("cp", "-R", filepath.Join(defaultDatadir, "resources"), datadir) - if err := cmd.Run(); err != nil { - return err - } - c.CleanResourceVolumes(datadir) - return nil -} - -// GetResourcePath returns the absolute path of the requested resource -func (c *Controller) GetResourcePath(datadir, resource string) string { - if resource == "compose" { - network := c.config.GetString(constants.Network) - if c.config.GetBool(constants.AttachLiquid) { - network += "-liquid" - } - return filepath.Join(datadir, "resources", fmt.Sprintf("docker-compose-%s.yml", network)) - } - if resource == "env" { - return filepath.Join(datadir, ".env") - } - if resource == "config" { - return filepath.Join(datadir, "nigiri.config.json") - } - return "" -} - -// CleanResourceVolumes recursively deletes the content of the -// docker volumes in the resource path -func (c *Controller) CleanResourceVolumes(datadir string) error { - network := c.config.GetString(constants.Network) - attachLiquid := c.config.GetBool(constants.AttachLiquid) - if attachLiquid { - network = fmt.Sprintf("liquid%s", network) - } - volumedir := filepath.Join(datadir, "resources", "volumes", network) - - return c.docker.cleanVolumes(volumedir) -} - -// GetDefaultDatadir returns the absolute path of Nigiri default directory -func (c *Controller) GetDefaultDatadir() string { - return c.config.GetPath() -} - -// GetServiceName returns the right name of the requested service -// If requesting a name for a Liquid service, then the suffix -liquid -// is appended to the canonical name except for "node" that is mapped -// to either "bitcoin" or "liquid" -func (c *Controller) GetServiceName(name string, liquid bool) string { - service := name - if service == "node" { - service = "bitcoin" - } - if liquid { - if service == "bitcoin" { - service = "liquid" - } else { - service = fmt.Sprintf("%s-liquid", service) - } - } - - return service -} diff --git a/cli/controller/docker.go b/cli/controller/docker.go deleted file mode 100644 index f9a5ba8..0000000 --- a/cli/controller/docker.go +++ /dev/null @@ -1,98 +0,0 @@ -package controller - -import ( - "context" - "io/ioutil" - "os" - "path/filepath" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/client" - "github.com/vulpemventures/nigiri/cli/constants" -) - -// Docker type handles interfaction with containers via docker and docker-compose -type Docker struct { - cli *client.Client -} - -// New initialize a new Docker handler -func (d *Docker) New() error { - cli, err := client.NewEnvClient() - if err != nil { - return err - } - d.cli = cli - return nil -} - -func (d *Docker) isDockerRunning() bool { - _, err := d.cli.ContainerList(context.Background(), types.ContainerListOptions{All: false}) - if err != nil { - return false - } - return true -} - -func (d *Docker) findNigiriContainers(listAllContainers bool) bool { - containers, _ := d.cli.ContainerList(context.Background(), types.ContainerListOptions{All: listAllContainers}) - - if len(containers) <= 0 { - return false - } - - images := []string{} - for _, c := range containers { - images = append(images, c.Image) - } - - for _, nigiriImage := range constants.NigiriBitcoinImages { - // just check if services for bitcoin chain are up and running - if !contains(images, nigiriImage) { - return false - } - } - return true -} - -func (d *Docker) isNigiriRunning() bool { - return d.findNigiriContainers(false) -} - -func (d *Docker) isNigiriStopped() bool { - isRunning := d.isNigiriRunning() - if !isRunning { - return d.findNigiriContainers(true) - } - return false -} - -func (d *Docker) cleanVolumes(path string) error { - subdirs, err := ioutil.ReadDir(path) - if err != nil { - return err - } - - for _, d := range subdirs { - path := filepath.Join(path, d.Name()) - subsubdirs, _ := ioutil.ReadDir(path) - for _, sd := range subsubdirs { - if sd.IsDir() { - if err := os.RemoveAll(filepath.Join(path, sd.Name())); err != nil { - return err - } - } - } - } - - return nil -} - -func contains(list []string, elem string) bool { - for _, l := range list { - if l == elem { - return true - } - } - return false -} diff --git a/cli/controller/env.go b/cli/controller/env.go deleted file mode 100644 index 83045c2..0000000 --- a/cli/controller/env.go +++ /dev/null @@ -1,125 +0,0 @@ -package controller - -import ( - "bufio" - "encoding/json" - "fmt" - "io/ioutil" - "os" - "strconv" - "strings" - - "github.com/vulpemventures/nigiri/cli/constants" -) - -// Env implements functions for interacting with the environment file used by -// docker-compose to dynamically set service ports and variables -type Env struct{} - -func (e *Env) writeEnvForCompose(path, strJSON string) error { - var env map[string]interface{} - err := json.Unmarshal([]byte(strJSON), &env) - if err != nil { - return constants.ErrMalformedJSON - } - - fileContent := "" - for chain, services := range env["ports"].(map[string]interface{}) { - for k, v := range services.(map[string]interface{}) { - fileContent += fmt.Sprintf("%s_%s_PORT=%d\n", strings.ToUpper(chain), strings.ToUpper(k), int(v.(float64))) - } - } - for hostname, url := range env["urls"].(map[string]interface{}) { - fileContent += fmt.Sprintf("%s_URL=%s\n", strings.ToUpper(hostname), url.(string)) - } - - return ioutil.WriteFile(path, []byte(fileContent), os.ModePerm) -} - -func (e *Env) readEnvForCompose(path string) (map[string]interface{}, error) { - file, err := os.Open(path) - if err != nil { - return nil, err - } - defer file.Close() - - scanner := bufio.NewScanner(file) - scanner.Split(bufio.ScanLines) - - ports := map[string]map[string]int{ - "bitcoin": map[string]int{}, - "liquid": map[string]int{}, - } - urls := map[string]string{} - // Each line is in the format PREFIX_SERVICE_NAME_SUFFIX=value - // PREFIX is either 'BITCOIN' or 'LIQUID', while SUFFIX is either 'PORT' or 'URL' - for scanner.Scan() { - line := scanner.Text() - splitLine := strings.Split(line, "=") - key := splitLine[0] - - if strings.Contains(key, "PORT") { - value, _ := strconv.Atoi(splitLine[1]) - chain := "bitcoin" - if strings.HasPrefix(key, strings.ToUpper("liquid")) { - chain = "liquid" - } - prefix := strings.ToUpper(fmt.Sprintf("%s_", chain)) - suffix := "_PORT" - trimmedKey := strings.ToLower( - strings.TrimSuffix(strings.TrimPrefix(key, prefix), suffix), - ) - ports[chain][trimmedKey] = value - } else { - // Here the prefix is not trimmed - value := splitLine[1] - suffix := "_URL" - trimmedKey := strings.ToLower(strings.TrimSuffix(key, suffix)) - urls[trimmedKey] = value - } - } - - return map[string]interface{}{"ports": ports, "urls": urls}, nil -} - -func (e *Env) load(path string) []string { - content, _ := ioutil.ReadFile(path) - lines := strings.Split(string(content), "\n") - env := os.Environ() - for _, line := range lines { - if line != "" { - env = append(env, line) - } - } - - return env -} - -type envPortsData struct { - Peer int `json:"peer,omitempty"` - Node int `json:"node,omitempty"` - Esplora int `json:"esplora,omitempty"` - Electrs int `json:"electrs,omitempty"` - ElectrsRPC int `json:"electrs_rpc,omitempty"` - Chopsticks int `json:"chopsticks,omitempty"` -} -type envPorts struct { - Bitcoin *envPortsData `json:"bitcoin,omitempty"` - Liquid *envPortsData `json:"liquid,omitempty"` -} -type envUrls struct { - BitcoinEsplora string `json:"bitcoin_esplora,omitempty"` - LiquidEsplora string `json:"liquid_esplora,omitempty"` -} -type envJSON struct { - Ports *envPorts `json:"ports,omitempty"` - Urls *envUrls `json:"urls,omitempty"` -} - -func (e envJSON) copy() envJSON { - var v envJSON - bytes, _ := json.Marshal(e) - json.Unmarshal(bytes, &v) - - return v -} diff --git a/cli/controller/parser.go b/cli/controller/parser.go deleted file mode 100644 index aeb512f..0000000 --- a/cli/controller/parser.go +++ /dev/null @@ -1,58 +0,0 @@ -package controller - -import ( - "encoding/json" - "fmt" - "path/filepath" - - "github.com/vulpemventures/nigiri/cli/constants" -) - -// Parser implements functions for parsing flags, JSON files -// and system directories passed to the CLI commands -type Parser struct { - services map[string]bool -} - -func newParser(services map[string]bool) *Parser { - p := &Parser{services} - return p -} - -func (p *Parser) parseNetwork(network string) error { - for _, n := range constants.AvaliableNetworks { - if network == n { - return nil - } - } - return constants.ErrInvalidNetwork -} - -func (p *Parser) parseDatadir(path string) error { - if !filepath.IsAbs(path) { - return constants.ErrInvalidDatadir - } - return nil -} - -func (p *Parser) parseEnvJSON(strJSON string) (string, error) { - // merge default json and incoming json by parsing DefaultEnv to - // envJSON type and then parsing the incoming json using the same variable - var parsedJSON envJSON - defaultJSON, _ := json.Marshal(constants.DefaultEnv) - json.Unmarshal(defaultJSON, &parsedJSON) - err := json.Unmarshal([]byte(strJSON), &parsedJSON) - if err != nil { - fmt.Println(err) - return "", constants.ErrMalformedJSON - } - merged, _ := json.Marshal(parsedJSON) - return string(merged), nil -} - -func (p *Parser) parseServiceName(name string) error { - if !p.services[name] { - return constants.ErrInvalidServiceName - } - return nil -} diff --git a/cli/controller/parser_test.go b/cli/controller/parser_test.go deleted file mode 100644 index c20a338..0000000 --- a/cli/controller/parser_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package controller - -import ( - "encoding/json" - "os" - "testing" - - "github.com/vulpemventures/nigiri/cli/constants" -) - -func TestParserParseNetwork(t *testing.T) { - p := &Parser{} - - validNetworks := []string{"regtest"} - for _, n := range validNetworks { - err := p.parseNetwork(n) - if err != nil { - t.Fatal(err) - } - } -} - -func TestParserParseDatadir(t *testing.T) { - p := &Parser{} - - currentDir, _ := os.Getwd() - validDatadirs := []string{currentDir} - for _, n := range validDatadirs { - err := p.parseDatadir(n) - if err != nil { - t.Fatal(err) - } - } -} - -func TestParserParseEnvJSON(t *testing.T) { - p := &Parser{} - - for _, e := range testJSONs { - parsedJSON, _ := json.Marshal(e) - mergedJSON, err := p.parseEnvJSON(string(parsedJSON)) - if err != nil { - t.Fatal(err) - } - t.Log(mergedJSON) - } -} - -func TestParserParseNetworkShouldFail(t *testing.T) { - p := &Parser{} - - invalidNetworks := []string{"simnet", "testnet"} - for _, n := range invalidNetworks { - err := p.parseNetwork(n) - if err == nil { - t.Fatalf("Should have been failed before") - } - if err != constants.ErrInvalidNetwork { - t.Fatalf("Got: %s, wanted: %s", err, constants.ErrInvalidNetwork) - } - } -} - -func TestParserParseDatadirShouldFail(t *testing.T) { - p := &Parser{} - - invalidDatadirs := []string{"."} - for _, d := range invalidDatadirs { - err := p.parseDatadir(d) - if err == nil { - t.Fatalf("Should have been failed before") - } - if err != constants.ErrInvalidDatadir { - t.Fatalf("Got: %s, wanted: %s", err, constants.ErrInvalidDatadir) - } - } -} - -var testJSONs = []map[string]interface{}{ - // only btc services - { - "urls": map[string]string{ - "bitcoin_esplora": "https://blockstream.info/", - }, - "ports": map[string]map[string]int{ - "bitcoin": map[string]int{ - "peer": 1111, - "node": 2222, - "esplora": 3333, - "electrs": 4444, - "chopsticks": 5555, - }, - }, - }, - // btc and liquid services - { - "urls": map[string]string{ - "bitcoin_esplora": "https://blockstream.info/", - "liquid_esplora": "http://blockstream.info/liquid", - }, - "ports": map[string]map[string]int{ - "bitcoin": map[string]int{ - "peer": 1111, - "node": 2222, - "esplora": 3333, - "electrs": 4444, - "chopsticks": 5555, - }, - "liquid": map[string]int{ - "peer": 6666, - "node": 7777, - "esplora": 8888, - "electrs": 9999, - "chopsticks": 1010, - }, - }, - }, - // incomplete examples: - // incomplete bitcoin services - { - "ports": map[string]map[string]int{ - "bitcoin": map[string]int{ - "esplora": 1111, - "electrs": 2222, - "chopsticks": 3333, - }, - }, - "urls": map[string]string{ - "bitcoin_esplora": "http://test.com/api", - }, - }, - // bitcoin services ports and liquid service url - { - "ports": map[string]map[string]int{ - "bitcoin": map[string]int{ - "node": 1111, - "esplora": 2222, - "electrs": 3333, - "chopsticks": 4444, - }, - }, - "urls": map[string]string{ - "liquid_esplora": "http://test.com/liquid/api", - }, - }, - // liquid services ports and bitcoin service url - { - "ports": map[string]map[string]int{ - "liquid": map[string]int{ - "node": 1111, - "esplora": 2222, - "electrs": 3333, - "chopsticks": 4444, - }, - }, - "urls": map[string]string{ - "bitcoin_esplora": "http://test.com/api", - }, - }, - // empty config - {}, -} diff --git a/cli/main.go b/cli/main.go deleted file mode 100644 index 3f83d44..0000000 --- a/cli/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/vulpemventures/nigiri/cli/cmd" - -func main() { - cmd.RootCmd.Execute() -} diff --git a/cmd/nigiri/faucet.go b/cmd/nigiri/faucet.go new file mode 100644 index 0000000..f073640 --- /dev/null +++ b/cmd/nigiri/faucet.go @@ -0,0 +1,83 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "path/filepath" + "strings" + + "github.com/urfave/cli/v2" + "github.com/vulpemventures/nigiri/internal/config" + "github.com/vulpemventures/nigiri/internal/docker" +) + +var faucet = cli.Command{ + Name: "faucet", + Usage: "generate and send bitcoin to given address", + ArgsUsage: "
[amount] [asset]", + Action: faucetAction, + Flags: []cli.Flag{ + &liquidFlag, + }, +} + +func faucetAction(ctx *cli.Context) error { + + if isRunning, _ := nigiriState.GetBool("running"); !isRunning { + return errors.New("nigiri is not running") + } + + if ctx.NArg() < 1 || ctx.NArg() > 3 { + return errors.New("wrong number of arguments") + } + + isLiquid := ctx.Bool("liquid") + datadir := ctx.String("datadir") + composePath := filepath.Join(datadir, config.DefaultCompose) + + var serviceName string = "chopsticks" + if isLiquid { + serviceName = "chopsticks-liquid" + } + + portSlice, err := docker.GetPortsForService(composePath, serviceName) + if err != nil { + return err + } + mappedPorts := strings.Split(portSlice[0], ":") + + request := map[string]interface{}{ + "address": ctx.Args().First(), + } + requestPort := mappedPorts[0] + payload, err := json.Marshal(request) + if err != nil { + return err + } + res, err := http.Post("http://127.0.0.1:"+requestPort+"/faucet", "application/json", bytes.NewBuffer(payload)) + if err != nil { + return err + } + data, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + if res.StatusCode != http.StatusOK { + return errors.New(string(data)) + } + + var dat map[string]string + if err := json.Unmarshal([]byte(data), &dat); err != nil { + return errors.New("internal error, please try again") + } + if dat["txId"] == "" { + return errors.New("not successful") + } + fmt.Println("txId: " + dat["txId"]) + + return nil +} diff --git a/cmd/nigiri/logs.go b/cmd/nigiri/logs.go new file mode 100644 index 0000000..e485276 --- /dev/null +++ b/cmd/nigiri/logs.go @@ -0,0 +1,46 @@ +package main + +import ( + "errors" + "os" + "os/exec" + "path/filepath" + + "github.com/urfave/cli/v2" + "github.com/vulpemventures/nigiri/internal/config" +) + +var logs = cli.Command{ + Name: "logs", + Usage: "check Service logs", + Action: logsAction, + Flags: []cli.Flag{ + &liquidFlag, + }, +} + +func logsAction(ctx *cli.Context) error { + + if isRunning, _ := nigiriState.GetBool("running"); !isRunning { + return errors.New("nigiri is not running") + } + + if ctx.NArg() != 1 { + return errors.New("missing service name") + } + + serviceName := ctx.Args().First() + + datadir := ctx.String("datadir") + composePath := filepath.Join(datadir, config.DefaultCompose) + + bashCmd := exec.Command("docker-compose", "-f", composePath, "logs", serviceName) + bashCmd.Stdout = os.Stdout + bashCmd.Stderr = os.Stderr + + if err := bashCmd.Run(); err != nil { + return err + } + + return nil +} diff --git a/cmd/nigiri/main.go b/cmd/nigiri/main.go new file mode 100644 index 0000000..6a1d66d --- /dev/null +++ b/cmd/nigiri/main.go @@ -0,0 +1,190 @@ +package main + +import ( + "embed" + "fmt" + "io/ioutil" + "os" + "os/user" + "path/filepath" + "strconv" + "strings" + + "github.com/urfave/cli/v2" + "github.com/vulpemventures/nigiri/internal/config" + "github.com/vulpemventures/nigiri/internal/state" +) + +var ( + version = "dev" + commit = "none" + date = "unknown" + + nigiriState = state.New(config.DefaultPath, config.InitialState) +) + +var liquidFlag = cli.BoolFlag{ + Name: "liquid", + Usage: "enable liquid", + Value: false, +} + +var datadirFlag = cli.StringFlag{ + Name: "datadir", + Usage: "use different data directory", + Value: config.DefaultDatadir, +} + +//go:embed resources/docker-compose.yml +//go:embed resources/bitcoin.conf +//go:embed resources/elements.conf +var f embed.FS + +func main() { + app := cli.NewApp() + + app.Version = formatVersion() + app.Name = "nigiri CLI" + app.Usage = "create your dockerized environment with a bitcoin and liquid node, with a block explorer and developer tools" + app.Flags = append(app.Flags, &datadirFlag) + app.Commands = append( + app.Commands, + &rpc, + &stop, + &logs, + &mint, + &push, + &start, + &update, + &faucet, + &versionCmd, + ) + + app.Before = func(ctx *cli.Context) error { + + dataDir := config.DefaultDatadir + + if ctx.IsSet("datadir") { + dataDir = cleanAndExpandPath(ctx.String("datadir")) + nigiriState = state.New(filepath.Join(dataDir, config.DefaultName), config.InitialState) + } + + if err := provisionResourcesToDatadir(dataDir); err != nil { + return err + } + + return nil + } + + err := app.Run(os.Args) + if err != nil { + fatal(err) + } +} + +func fatal(err error) { + _, _ = fmt.Fprintf(os.Stderr, "[nigiri] %v\n", err) + os.Exit(1) +} + +// Provisioning Nigiri reosurces +func provisionResourcesToDatadir(datadir string) error { + + isReady, err := nigiriState.GetBool("ready") + if err != nil { + return err + } + + if isReady { + return nil + } + + // create folders in volumes/{bitcoin,elements} for node datadirs + if err := makeDirectoryIfNotExists(filepath.Join(datadir, "volumes", "bitcoin")); err != nil { + return err + } + if err := makeDirectoryIfNotExists(filepath.Join(datadir, "volumes", "elements")); err != nil { + return err + } + + // copy resources into the Nigiri data directory + if err := copyFromResourcesToDatadir( + filepath.Join("resources", config.DefaultCompose), + filepath.Join(datadir, config.DefaultCompose), + ); err != nil { + return err + } + + if err := copyFromResourcesToDatadir( + filepath.Join("resources", "bitcoin.conf"), + filepath.Join(datadir, "volumes", "bitcoin", "bitcoin.conf"), + ); err != nil { + return err + } + + if err := copyFromResourcesToDatadir( + filepath.Join("resources", "elements.conf"), + filepath.Join(datadir, "volumes", "elements", "elements.conf"), + ); err != nil { + return err + } + + if err := nigiriState.Set(map[string]string{"ready": strconv.FormatBool(true)}); err != nil { + return err + } + + return nil +} + +func formatVersion() string { + return fmt.Sprintf( + "\nVersion: %s\nCommit: %s\nDate: %s", + version, commit, date, + ) +} + +func copyFromResourcesToDatadir(src string, dest string) error { + data, err := f.ReadFile(src) + if err != nil { + return fmt.Errorf("read embed: %w", err) + } + err = ioutil.WriteFile(dest, data, 0777) + if err != nil { + return fmt.Errorf("write %s to %s: %w", src, dest, err) + } + + return nil +} + +func makeDirectoryIfNotExists(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + return os.MkdirAll(path, os.ModeDir|0755) + } + return nil +} + +// cleanAndExpandPath expands environment variables and leading ~ in the +// passed path, cleans the result, and returns it. +// This function is taken from https://github.com/btcsuite/btcd +func cleanAndExpandPath(path string) string { + if path == "" { + return "" + } + + // Expand initial ~ to OS specific home directory. + if strings.HasPrefix(path, "~") { + var homeDir string + u, err := user.Current() + if err == nil { + homeDir = u.HomeDir + } else { + homeDir = os.Getenv("HOME") + } + + path = strings.Replace(path, "~", homeDir, 1) + } + + // NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%, + // but the variables can still be expanded via POSIX-style $VARIABLE. + return filepath.Clean(os.ExpandEnv(path)) +} diff --git a/cmd/nigiri/mint.go b/cmd/nigiri/mint.go new file mode 100644 index 0000000..fb3f84c --- /dev/null +++ b/cmd/nigiri/mint.go @@ -0,0 +1,101 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "path/filepath" + "strconv" + "strings" + + "github.com/urfave/cli/v2" + "github.com/vulpemventures/nigiri/internal/config" + "github.com/vulpemventures/nigiri/internal/docker" +) + +var mint = cli.Command{ + Name: "mint", + Usage: "liquid only: issue and send a given quantity of an asset", + ArgsUsage: "
[name] [ticker]", + Action: mintAction, +} + +func mintAction(ctx *cli.Context) error { + + if isRunning, _ := nigiriState.GetBool("running"); !isRunning { + return errors.New("nigiri is not running") + } + + if ctx.NArg() < 2 || ctx.NArg() > 5 { + return errors.New("wrong number of arguments") + } + + datadir := ctx.String("datadir") + composePath := filepath.Join(datadir, config.DefaultCompose) + + serviceName := "chopsticks-liquid" + + portSlice, err := docker.GetPortsForService(composePath, serviceName) + if err != nil { + return err + } + mappedPorts := strings.Split(portSlice[0], ":") + + var request struct { + Address string `json:"address"` + Quantity int `json:"quantity"` + Name string `json:"name"` + Ticker string `json:"ticker"` + } + request.Address = ctx.Args().First() + request.Quantity, _ = strconv.Atoi(ctx.Args().Get(1)) + if ctx.Args().Len() >= 3 { + request.Name = ctx.Args().Get(2) + } + if ctx.Args().Len() == 4 { + request.Ticker = ctx.Args().Get(3) + } + + requestPort := mappedPorts[0] + payload, err := json.Marshal(request) + if err != nil { + return err + } + res, err := http.Post("http://127.0.0.1:"+requestPort+"/mint", "application/json", bytes.NewBuffer(payload)) + if err != nil { + return err + } + + data, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + if res.StatusCode != http.StatusOK { + return errors.New(string(data)) + } + var dat map[string]interface{} + var resp string + if err := json.Unmarshal([]byte(data), &dat); err != nil { + return errors.New("internal error try again") + } + if dat["txId"] == "" { + return errors.New("not successful") + } + for key, element := range dat { + if key == "issuance_txin" { + myMap := element.(map[string]interface{}) + resp += key + ":\n" + for key2, element2 := range myMap { + resp += " " + resp += key2 + ": " + fmt.Sprintf("%v", element2) + "\n" + } + continue + } + resp += key + ": " + fmt.Sprintf("%v", element) + "\n" + } + fmt.Println(resp[:len(resp)-1]) + return nil +} diff --git a/cmd/nigiri/push.go b/cmd/nigiri/push.go new file mode 100644 index 0000000..b72947a --- /dev/null +++ b/cmd/nigiri/push.go @@ -0,0 +1,71 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "net/http" + "path/filepath" + "strings" + + "github.com/urfave/cli/v2" + "github.com/vulpemventures/nigiri/internal/config" + "github.com/vulpemventures/nigiri/internal/docker" +) + +var push = cli.Command{ + Name: "push", + Usage: "broadcast raw transaction", + ArgsUsage: "", + Action: pushAction, + Flags: []cli.Flag{ + &liquidFlag, + }, +} + +func pushAction(ctx *cli.Context) error { + + if isRunning, _ := nigiriState.GetBool("running"); !isRunning { + return errors.New("nigiri is not running") + } + + if ctx.NArg() != 1 { + return errors.New("wrong number of arguments") + } + + isLiquid := ctx.Bool("liquid") + datadir := ctx.String("datadir") + composePath := filepath.Join(datadir, config.DefaultCompose) + + var serviceName string = "chopsticks" + if isLiquid { + serviceName = "chopsticks-liquid" + } + + portSlice, err := docker.GetPortsForService(composePath, serviceName) + if err != nil { + return err + } + mappedPorts := strings.Split(portSlice[0], ":") + requestPort := mappedPorts[0] + hex := []byte(ctx.Args().First()) + + res, err := http.Post("http://127.0.0.1:"+requestPort+"/tx", "application/string", bytes.NewBuffer(hex)) + if err != nil { + return err + } + data, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + if res.StatusCode != http.StatusOK { + return errors.New(string(data)) + } + + if string(data) == "" { + return errors.New("not successful") + } + fmt.Println("\ntxId: " + string(data)) + return nil +} diff --git a/resources/volumes/liquidregtest/config/bitcoin.conf b/cmd/nigiri/resources/bitcoin.conf similarity index 80% rename from resources/volumes/liquidregtest/config/bitcoin.conf rename to cmd/nigiri/resources/bitcoin.conf index c462cbc..f7e765e 100644 --- a/resources/volumes/liquidregtest/config/bitcoin.conf +++ b/cmd/nigiri/resources/bitcoin.conf @@ -4,11 +4,11 @@ dnsseed=0 upnp=0 [regtest] -port=19000 -rpcport=19001 +port=18444 +rpcport=18443 server=1 -txindex=0 +txindex=1 rpcuser=admin1 rpcpassword=123 diff --git a/resources/docker-compose-regtest-liquid.yml b/cmd/nigiri/resources/docker-compose.yml similarity index 60% rename from resources/docker-compose-regtest-liquid.yml rename to cmd/nigiri/resources/docker-compose.yml index 1d4ec8f..95c30e3 100644 --- a/resources/docker-compose-regtest-liquid.yml +++ b/cmd/nigiri/resources/docker-compose.yml @@ -1,35 +1,31 @@ -version: '3' +version: '3.7' services: - # RPC daemons + # RPC daemon bitcoin: image: ghcr.io/vulpemventures/bitcoin:latest container_name: bitcoin command: - -datadir=config - networks: - local: - ipv4_address: 10.10.0.10 ports: - - ${BITCOIN_PEER_PORT}:19000 - - ${BITCOIN_NODE_PORT}:19001 + - 18443:18433 + - 18444:18444 volumes: - - ./volumes/liquidregtest/config/:/config + - ./volumes/bitcoin/:/config restart: unless-stopped + liquid: image: ghcr.io/vulpemventures/elements:latest container_name: liquid command: - -datadir=config - networks: - local: - ipv4_address: 10.10.0.11 ports: - - ${LIQUID_NODE_PORT}:18884 - - ${LIQUID_PEER_PORT}:18886 + - 18884:18884 + - 18886:18886 volumes: - - ./volumes/liquidregtest/liquid-config/:/config + - ./volumes/elements/:/config restart: unless-stopped - # Block explorer REST servers + + # Block explorer server electrs: image: ghcr.io/vulpemventures/electrs:latest container_name: electrs @@ -42,26 +38,24 @@ services: - --daemon-dir - /config - --daemon-rpc-addr - - 10.10.0.10:19001 + - bitcoin:18443 - --cookie - admin1:123 - --http-addr - - 0.0.0.0:3002 + - 0.0.0.0:30000 - --electrum-rpc-addr - - 0.0.0.0:60401 + - 0.0.0.0:50000 - --cors - "*" - networks: - local: - ipv4_address: 10.10.0.12 depends_on: - bitcoin ports: - - ${BITCOIN_ELECTRS_RPC_PORT}:60401 - - ${BITCOIN_ELECTRS_PORT}:3002 + - 50000:50000 + - 30000:30000 volumes: - - ./volumes/liquidregtest/config/:/config + - ./volumes/bitcoin/:/config restart: unless-stopped + electrs-liquid: image: ghcr.io/vulpemventures/electrs-liquid:latest container_name: electrs-liquid @@ -76,52 +70,45 @@ services: - --daemon-dir - /config - --daemon-rpc-addr - - 10.10.0.11:18884 + - liquid:18884 - --cookie - admin1:123 - --http-addr - - 0.0.0.0:3002 + - 0.0.0.0:30001 - --electrum-rpc-addr - - 0.0.0.0:60401 + - 0.0.0.0:50001 - --cors - "*" - networks: - local: - ipv4_address: 10.10.0.13 depends_on: - liquid ports: - - ${LIQUID_ELECTRS_RPC_PORT}:60401 - - ${LIQUID_ELECTRS_PORT}:3002 + - 50001:50001 + - 30001:30001 volumes: - - ./volumes/liquidregtest/liquid-config/:/config + - ./volumes/elements/:/config restart: unless-stopped - # Block explorer frontends + + # Block explorer frontend esplora: image: ghcr.io/vulpemventures/esplora:latest container_name: esplora - networks: - local: - ipv4_address: 10.10.0.14 depends_on: - chopsticks - environment: - API_URL: ${BITCOIN_ESPLORA_URL} + environment: + API_URL: http://localhost:3000 ports: - - ${BITCOIN_ESPLORA_PORT}:5000 + - 5000:5000 restart: unless-stopped + esplora-liquid: image: ghcr.io/vulpemventures/esplora:latest container_name: esplora-liquid - networks: - local: - ipv4_address: 10.10.0.15 depends_on: - chopsticks-liquid environment: - API_URL: ${LIQUID_ESPLORA_URL} + API_URL: http://localhost:3001 ports: - - ${LIQUID_ESPLORA_PORT}:5000 + - 5001:5000 restart: unless-stopped # Chopsticks chopsticks: @@ -132,20 +119,18 @@ services: - --use-mining - --use-logger - --rpc-addr - - 10.10.0.10:19001 + - bitcoin:18443 - --electrs-addr - - 10.10.0.12:3002 + - electrs:30000 - --addr - 0.0.0.0:3000 depends_on: - bitcoin - electrs ports: - - ${BITCOIN_CHOPSTICKS_PORT}:3000 - networks: - local: - ipv4_address: 10.10.0.16 + - 3000:3000 restart: unless-stopped + chopsticks-liquid: image: ghcr.io/vulpemventures/nigiri-chopsticks:latest container_name: chopsticks-liquid @@ -154,9 +139,9 @@ services: - --use-mining - --use-logger - --rpc-addr - - 10.10.0.11:18884 + - liquid:18884 - --electrs-addr - - 10.10.0.13:3002 + - electrs-liquid:30001 - --addr - 0.0.0.0:3000 - --chain @@ -165,15 +150,9 @@ services: - liquid - electrs-liquid ports: - - ${LIQUID_CHOPSTICKS_PORT}:3000 - networks: - local: - ipv4_address: 10.10.0.17 + - 3001:3000 restart: unless-stopped networks: - local: - driver: bridge - ipam: - config: - - subnet: 10.10.0.0/24 + default: + name: nigiri diff --git a/resources/volumes/liquidregtest/liquid-config/elements.conf b/cmd/nigiri/resources/elements.conf similarity index 94% rename from resources/volumes/liquidregtest/liquid-config/elements.conf rename to cmd/nigiri/resources/elements.conf index a8732e8..32b70d5 100644 --- a/resources/volumes/liquidregtest/liquid-config/elements.conf +++ b/cmd/nigiri/resources/elements.conf @@ -10,8 +10,8 @@ rpcpassword=123 rpcallowip=0.0.0.0/0 rpcbind=0.0.0.0 -mainchainrpcport=19001 -mainchainrpchost=10.10.0.10 +mainchainrpcport=18443 +#mainchainrpchost=10.10.0.10 mainchainrpcuser=admin1 mainchainrpcpassword=123 diff --git a/cmd/nigiri/rpc.go b/cmd/nigiri/rpc.go new file mode 100644 index 0000000..55b9d65 --- /dev/null +++ b/cmd/nigiri/rpc.go @@ -0,0 +1,48 @@ +package main + +import ( + "errors" + "os" + "os/exec" + + "github.com/urfave/cli/v2" +) + +var rpc = cli.Command{ + Name: "rpc", + Usage: "invoke bitcoin-cli or elements-cli", + Action: rpcAction, + Flags: []cli.Flag{ + &liquidFlag, + &cli.StringFlag{ + Name: "rpcwallet", + Usage: "rpcwallet to be used for node JSONRPC commands", + Value: "", + }, + }, +} + +func rpcAction(ctx *cli.Context) error { + + if isRunning, _ := nigiriState.GetBool("running"); !isRunning { + return errors.New("nigiri is not running") + } + + isLiquid := ctx.Bool("liquid") + rpcWallet := ctx.String("rpcwallet") + + rpcArgs := []string{"exec", "bitcoin", "bitcoin-cli", "-datadir=config", "-rpcwallet=" + rpcWallet} + if isLiquid { + rpcArgs = []string{"exec", "liquid", "elements-cli", "-datadir=config", "-rpcwallet=" + rpcWallet} + } + cmdArgs := append(rpcArgs, ctx.Args().Slice()...) + bashCmd := exec.Command("docker", cmdArgs...) + bashCmd.Stdout = os.Stdout + bashCmd.Stderr = os.Stderr + + if err := bashCmd.Run(); err != nil { + return err + } + + return nil +} diff --git a/cmd/nigiri/start.go b/cmd/nigiri/start.go new file mode 100644 index 0000000..d006992 --- /dev/null +++ b/cmd/nigiri/start.go @@ -0,0 +1,90 @@ +package main + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "github.com/urfave/cli/v2" + "github.com/vulpemventures/nigiri/internal/config" + "github.com/vulpemventures/nigiri/internal/docker" +) + +var start = cli.Command{ + Name: "start", + Usage: "start nigiri", + Action: startAction, + Flags: []cli.Flag{ + &liquidFlag, + &cli.BoolFlag{ + Name: "ci", + Usage: "runs in headless mode without esplora for continuous integration environments", + Value: false, + }, + }, +} + +func startAction(ctx *cli.Context) error { + + if isRunning, _ := nigiriState.GetBool("running"); isRunning { + return errors.New("nigiri is already running, please stop it first") + } + + isLiquid := ctx.Bool("liquid") + datadir := ctx.String("datadir") + composePath := filepath.Join(datadir, config.DefaultCompose) + + // spin up all the services in the compose file + bashCmd := exec.Command("docker-compose", "-f", composePath, "up", "-d", "esplora") + if isLiquid { + //this will only run chopsticks & chopsticks-liquid and servives they depends on + bashCmd = exec.Command("docker-compose", "-f", composePath, "up", "-d", "esplora", "esplora-liquid") + } + + if ctx.Bool("ci") { + //this will only run chopsticks and servives it depends on + bashCmd = exec.Command("docker-compose", "-f", composePath, "up", "-d", "chopsticks") + if isLiquid { + //this will only run chopsticks & chopsticks-liquid and servives they depends on + bashCmd = exec.Command("docker-compose", "-f", composePath, "up", "-d", "chopsticks", "chopsticks-liquid") + } + } + + bashCmd.Stdout = os.Stdout + bashCmd.Stderr = os.Stderr + + if err := bashCmd.Run(); err != nil { + return err + } + + if err := nigiriState.Set(map[string]string{ + "running": strconv.FormatBool(true), + }); err != nil { + return err + } + + services, err := docker.GetServices(composePath) + if err != nil { + return err + } + + fmt.Println() + fmt.Println("ENDPOINTS") + + for _, nameAndEndpoint := range services { + name := nameAndEndpoint[0] + endpoint := nameAndEndpoint[1] + + if !isLiquid && strings.Contains(name, "liquid") { + continue + } + + fmt.Println(name + " " + endpoint) + } + + return nil +} diff --git a/cmd/nigiri/stop.go b/cmd/nigiri/stop.go new file mode 100644 index 0000000..1cfb6e7 --- /dev/null +++ b/cmd/nigiri/stop.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + + "github.com/urfave/cli/v2" + "github.com/vulpemventures/nigiri/internal/config" +) + +var stop = cli.Command{ + Name: "stop", + Usage: "stop nigiri", + Action: stopAction, + Flags: []cli.Flag{ + &liquidFlag, + &cli.BoolFlag{ + Name: "delete", + Usage: "clean node data directories", + Value: false, + }, + }, +} + +func stopAction(ctx *cli.Context) error { + + delete := ctx.Bool("delete") + datadir := ctx.String("datadir") + composePath := filepath.Join(datadir, config.DefaultCompose) + + bashCmd := exec.Command("docker-compose", "-f", composePath, "stop") + if delete { + bashCmd = exec.Command("docker-compose", "-f", composePath, "down", "--volumes") + } + bashCmd.Stdout = os.Stdout + bashCmd.Stderr = os.Stderr + + if err := bashCmd.Run(); err != nil { + return err + } + + if delete { + fmt.Println("Removing data from volumes...") + + datadir := ctx.String("datadir") + if err := os.RemoveAll(datadir); err != nil { + return err + } + + if err := provisionResourcesToDatadir(datadir); err != nil { + return err + } + + fmt.Println("Nigiri has been cleaned up successfully.") + } else { + if err := nigiriState.Set(map[string]string{ + "running": strconv.FormatBool(false), + }); err != nil { + return err + } + } + + return nil +} diff --git a/cmd/nigiri/update.go b/cmd/nigiri/update.go new file mode 100644 index 0000000..0ad9463 --- /dev/null +++ b/cmd/nigiri/update.go @@ -0,0 +1,31 @@ +package main + +import ( + "os" + "os/exec" + "path/filepath" + + "github.com/urfave/cli/v2" + "github.com/vulpemventures/nigiri/internal/config" +) + +var update = cli.Command{ + Name: "update", + Usage: "check for updates and pull new docker images", + Action: updateAction, +} + +func updateAction(ctx *cli.Context) error { + datadir := ctx.String("datadir") + composePath := filepath.Join(datadir, config.DefaultCompose) + + bashCmd := exec.Command("docker-compose", "-f", composePath, "pull") + bashCmd.Stdout = os.Stdout + bashCmd.Stderr = os.Stderr + + if err := bashCmd.Run(); err != nil { + return err + } + + return nil +} diff --git a/cmd/nigiri/version.go b/cmd/nigiri/version.go new file mode 100644 index 0000000..21c926d --- /dev/null +++ b/cmd/nigiri/version.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +var versionCmd = cli.Command{ + Name: "version", + Action: versionAction, +} + +func versionAction(ctx *cli.Context) error { + fmt.Println("nigiri CLI version") + fmt.Println(formatVersion()) + return nil +} diff --git a/go.mod b/go.mod index 0096fe1..bfc1812 100644 --- a/go.mod +++ b/go.mod @@ -1,25 +1,11 @@ module github.com/vulpemventures/nigiri -go 1.12 +go 1.16 require ( - github.com/BurntSushi/toml v0.3.1 // indirect - github.com/Microsoft/go-winio v0.4.12 // indirect - github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/docker/docker v1.13.1 - github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-units v0.3.3 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect - github.com/mitchellh/go-homedir v1.1.0 - github.com/opencontainers/go-digest v1.0.0-rc1 // indirect - github.com/pkg/errors v0.8.1 // indirect - github.com/sirupsen/logrus v1.4.0 - github.com/spf13/afero v1.2.2 // indirect - github.com/spf13/cobra v0.0.3 - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/viper v1.3.2 - golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c // indirect - golang.org/x/net v0.0.0-20190419010253-1f3472d942ba // indirect - golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc // indirect + github.com/btcsuite/btcd v0.21.0-beta // indirect + github.com/btcsuite/btcutil v1.0.2 + github.com/compose-spec/compose-go v0.0.0-20210729195839-de56f4f0cb3c + github.com/urfave/cli/v2 v2.3.0 + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect ) diff --git a/go.sum b/go.sum index 22ee2b4..5bd5dbc 100644 --- a/go.sum +++ b/go.sum @@ -1,80 +1,214 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc= -github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= +github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/compose-spec/compose-go v0.0.0-20210729195839-de56f4f0cb3c h1:lSR4wokZlq+Q8uJpgZuFMs3VoLaYVV07cJOZHa1zRBg= +github.com/compose-spec/compose-go v0.0.0-20210729195839-de56f4f0cb3c/go.mod h1:5V65rPnTvvQagtoMxTneJ2QicLq6ZRQQ7fOgPN226fo= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= -github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e h1:n81KvOMrLZa+VWHwST7dun9f0G98X3zREHS1ztYzZKU= +github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mod h1:xpWTC2KnJMiDLkoawhsPQcXjvwATEBcbq0xevG2YR9M= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.4.0 h1:yKenngtzGh+cUSSh6GWbxW2abRqhYUSR/t/6+2QqNvE= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190419010253-1f3472d942ba h1:h0zCzEL5UW1mERvwTN6AXcc75PpLkY6OcReia6Dq1BM= -golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc h1:4gbWbmmPFp4ySWICouJl6emP0MyS31yy9SrTlAGFT+g= -golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789 h1:NMiUjDZiD6qDVeBOzpImftxXzQHCp2Y2QLdmaqU9MRk= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..3456244 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,22 @@ +package config + +import ( + "path/filepath" + "strconv" + + "github.com/btcsuite/btcutil" +) + +var ( + DefaultName = "nigiri.config.json" + DefaultCompose = "docker-compose.yml" + + DefaultDatadir = btcutil.AppDataDir("nigiri", false) + DefaultPath = filepath.Join(DefaultDatadir, DefaultName) + + InitialState = map[string]string{ + "network": "regtest", + "ready": strconv.FormatBool(false), + "running": strconv.FormatBool(false), + } +) diff --git a/internal/docker/docker.go b/internal/docker/docker.go new file mode 100644 index 0000000..121d10d --- /dev/null +++ b/internal/docker/docker.go @@ -0,0 +1,76 @@ +package docker + +import ( + "errors" + "io/ioutil" + "strings" + + "github.com/compose-spec/compose-go/loader" +) + +func GetServices(composeFile string) ([][]string, error) { + + composeBytes, err := ioutil.ReadFile(composeFile) + if err != nil { + return nil, err + } + + parsed, err := loader.ParseYAML(composeBytes) + if err != nil { + return nil, err + } + + if _, ok := parsed["services"]; !ok { + return nil, errors.New("missing services in compose") + } + + serviceMap := parsed["services"].(map[string]interface{}) + + var services [][]string + for k, v := range serviceMap { + m := v.(map[string]interface{}) + i := m["ports"].([]interface{}) + for _, j := range i { + port := j.(string) + exposedPorts := strings.Split(port, ":") + endpoint := "localhost:" + exposedPorts[0] + services = append(services, []string{k, endpoint}) + } + + } + + return services, nil +} + +func GetPortsForService(composeFile string, serviceName string) ([]string, error) { + + composeBytes, err := ioutil.ReadFile(composeFile) + if err != nil { + return nil, err + } + + parsed, err := loader.ParseYAML(composeBytes) + if err != nil { + return nil, err + } + + if _, ok := parsed["services"]; !ok { + return nil, errors.New("missing services in compose") + } + + serviceMap := parsed["services"].(map[string]interface{}) + + var ports []string + for k, v := range serviceMap { + if k == serviceName { + m := v.(map[string]interface{}) + i := m["ports"].([]interface{}) + for _, j := range i { + port := j.(string) + ports = append(ports, port) + } + } + } + + return ports, nil +} diff --git a/internal/state/state.go b/internal/state/state.go new file mode 100644 index 0000000..1dca7f0 --- /dev/null +++ b/internal/state/state.go @@ -0,0 +1,104 @@ +package state + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" +) + +type State struct { + directory string + filePath string + initialState map[string]string +} + +func New(filePath string, initialState map[string]string) *State { + dir := filepath.Dir(filePath) + return &State{ + directory: dir, + filePath: filePath, + initialState: initialState, + } +} + +func (s *State) Get() (map[string]string, error) { + file, err := ioutil.ReadFile(s.filePath) + if err != nil { + if !os.IsNotExist(err) { + return nil, err + } + if err := s.Set(s.initialState); err != nil { + return nil, err + } + return s.initialState, nil + } + + data := map[string]string{} + json.Unmarshal(file, &data) + + return data, nil +} + +func (s *State) Set(data map[string]string) error { + if _, err := os.Stat(s.directory); os.IsNotExist(err) { + os.Mkdir(s.directory, os.ModeDir|0755) + } + + file, err := os.OpenFile(s.filePath, os.O_RDONLY|os.O_CREATE, 0644) + if err != nil { + return err + } + if err := file.Close(); err != nil { + return err + } + + currentData, err := s.Get() + if err != nil { + return err + } + + mergedData := merge(currentData, data) + + jsonString, err := json.Marshal(mergedData) + if err != nil { + return err + } + err = ioutil.WriteFile(s.filePath, jsonString, 0755) + if err != nil { + return fmt.Errorf("writing to file: %w", err) + } + + return nil +} + +func (s *State) GetBool(key string) (bool, error) { + stateData, err := s.Get() + if err != nil { + return false, err + } + + if _, ok := stateData[key]; !ok { + return false, errors.New("config: missing key " + key) + } + + value, err := strconv.ParseBool(stateData[key]) + if err != nil { + return false, err + } + + return value, nil +} + +func merge(maps ...map[string]string) map[string]string { + merge := make(map[string]string) + for _, m := range maps { + for k, v := range m { + merge[k] = v + } + } + return merge +} diff --git a/resources/docker-compose-regtest.yml b/resources/docker-compose-regtest.yml deleted file mode 100644 index 08391cf..0000000 --- a/resources/docker-compose-regtest.yml +++ /dev/null @@ -1,94 +0,0 @@ -version: '3' -services: - # RPC daemon - bitcoin: - image: ghcr.io/vulpemventures/bitcoin:latest - container_name: bitcoin - command: - - -datadir=config - networks: - local: - ipv4_address: 10.10.0.10 - ports: - - ${BITCOIN_PEER_PORT}:19000 - - ${BITCOIN_NODE_PORT}:19001 - volumes: - - ./volumes/regtest/config/:/config - restart: unless-stopped - # Block explorer server - electrs: - image: ghcr.io/vulpemventures/electrs:latest - container_name: electrs - entrypoint: - - /build/electrs - command: - - -vvvv - - --network - - regtest - - --daemon-dir - - /config - - --daemon-rpc-addr - - 10.10.0.10:19001 - - --cookie - - admin1:123 - - --http-addr - - 0.0.0.0:3002 - - --electrum-rpc-addr - - 0.0.0.0:60401 - - --cors - - "*" - networks: - local: - ipv4_address: 10.10.0.11 - depends_on: - - bitcoin - ports: - - ${BITCOIN_ELECTRS_RPC_PORT}:60401 - - ${BITCOIN_ELECTRS_PORT}:3002 - volumes: - - ./volumes/regtest/config/:/config - restart: unless-stopped - # Block explorer frontend - esplora: - image: ghcr.io/vulpemventures/esplora:latest - container_name: esplora - networks: - local: - ipv4_address: 10.10.0.12 - depends_on: - - chopsticks - environment: - API_URL: ${BITCOIN_ESPLORA_URL} - ports: - - ${BITCOIN_ESPLORA_PORT}:5000 - restart: unless-stopped - # Chopsticks - chopsticks: - image: ghcr.io/vulpemventures/nigiri-chopsticks:latest - container_name: chopsticks - command: - - --use-faucet - - --use-mining - - --use-logger - - --rpc-addr - - 10.10.0.10:19001 - - --electrs-addr - - 10.10.0.11:3002 - - --addr - - 0.0.0.0:3000 - networks: - local: - ipv4_address: 10.10.0.13 - depends_on: - - bitcoin - - electrs - ports: - - ${BITCOIN_CHOPSTICKS_PORT}:3000 - restart: unless-stopped - -networks: - local: - driver: bridge - ipam: - config: - - subnet: 10.10.0.0/24 diff --git a/resources/volumes/regtest/config/bitcoin.conf b/resources/volumes/regtest/config/bitcoin.conf deleted file mode 100644 index c462cbc..0000000 --- a/resources/volumes/regtest/config/bitcoin.conf +++ /dev/null @@ -1,17 +0,0 @@ -regtest=1 -testnet=0 -dnsseed=0 -upnp=0 - -[regtest] -port=19000 -rpcport=19001 - -server=1 -txindex=0 - -rpcuser=admin1 -rpcpassword=123 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0 -fallbackfee=0.00001 diff --git a/scripts/build b/scripts/build index a42b977..2901324 100755 --- a/scripts/build +++ b/scripts/build @@ -12,5 +12,5 @@ ARCH=$(eval "go env GOARCH") pushd $PARENT_PATH mkdir -p build -GO111MODULE=on go build -ldflags="-s -w" -o build/nigiri-$OS-$ARCH cli/main.go +GO111MODULE=on CGO_ENABLED=0 go build -ldflags="-s -w" -o build/nigiri-$OS-$ARCH cmd/nigiri/*.go popd diff --git a/scripts/clean b/scripts/clean deleted file mode 100755 index 0498ac9..0000000 --- a/scripts/clean +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -set -ex - -PARENT_PATH=$(dirname $(cd $(dirname $0); pwd -P)) - -pushd $PARENT_PATH - -case $(uname -s) in - Darwin) OS="darwin";; - Linux) OS="linux";; - *) echo "OS $OS not supported"; exit 1;; -esac - -case $(uname -m) in - amd64) ARCH="amd64";; - x86_64) ARCH="amd64";; - *) echo "Architecture $ARCH not supported"; exit 1;; -esac - -./build/nigiri-$OS-$ARCH stop --delete &>/dev/null -rm -rf build vendor ~/.nigiri - -popd \ No newline at end of file diff --git a/scripts/install b/scripts/install deleted file mode 100755 index 0757c19..0000000 --- a/scripts/install +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -ex - -PARENT_PATH=$(dirname $(cd $(dirname $0); pwd -P)) - -pushd $PARENT_PATH - -go generate ./... - -mkdir -p $HOME/.nigiri -cp -R resources $HOME/.nigiri -popd \ No newline at end of file diff --git a/test/start_stop_test.go b/test/start_stop_test.go new file mode 100644 index 0000000..6f7acf3 --- /dev/null +++ b/test/start_stop_test.go @@ -0,0 +1,115 @@ +package test + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + "time" + + "github.com/vulpemventures/nigiri/internal/config" + "github.com/vulpemventures/nigiri/internal/state" +) + +const ( + liquid = true + bitcoin = false + delete = true +) + +var ( + stopCmd = "stop" + // deleteCmd = append(stopCmd, "--delete") + startCmd = "start" + // liquidStartCmd = append(startCmd, "--liquid") + tmpDatadir = filepath.Join(os.TempDir(), "nigiri-tmp") + nigiriState = state.New(filepath.Join(tmpDatadir, config.DefaultName), config.InitialState) +) + +func TestStartStopLiquid(t *testing.T) { + if testing.Short() { + t.Skip("skipping testing in short mode") + } + // Start/Stop + testStart(t, liquid) + testStop(t) + // Start/Delete + testStart(t, liquid) + testDelete(t) +} + +func TestStartStopBitcoin(t *testing.T) { + if testing.Short() { + t.Skip("skipping testing in short mode") + } + // Start/Stop + testStart(t, bitcoin) + testStop(t) + // Start/Delete + testStart(t, bitcoin) + testDelete(t) +} + +func testStart(t *testing.T, flag bool) { + + if err := testCommand("start", "", flag); err != nil { + t.Fatal(err) + } + //Give some time to nigiri to be ready before calling + time.Sleep(5 * time.Second) + if isRunning, err := nigiriState.GetBool("running"); err != nil { + t.Fatal(err) + } else if !isRunning { + t.Fatal("Nigiri should have been started but services have not been found among running containers") + } +} + +func testStop(t *testing.T) { + + if err := testCommand("stop", "", !delete); err != nil { + t.Fatal(err) + } + //Give some time to nigiri to be ready before calling + time.Sleep(5 * time.Second) + if isRunning, err := nigiriState.GetBool("running"); err != nil { + t.Fatal(err) + } else if isRunning { + t.Fatal("Nigiri should have been stopped but services have not been found among stopped containers") + } +} + +func testDelete(t *testing.T) { + + if err := testCommand("stop", "", delete); err != nil { + t.Fatal(err) + } + if isRunning, err := nigiriState.GetBool("running"); err != nil { + t.Fatal(err) + } else if isRunning { + t.Fatal("Nigiri should have been terminated at this point but services have been found among stopped containers") + } +} + +func testCommand(command, arg string, flag bool) error { + + cmd := exec.Command("go", "run", "./cmd/nigiri") + env := "NIGIRI_DATADIR=" + tmpDatadir + cmd.Env = []string{env} + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + + if command == "start" { + cmd.Args = append(cmd.Args, startCmd, fmt.Sprintf("--liquid=%t", flag)) + } + if command == "stop" { + cmd.Args = append(cmd.Args, stopCmd, fmt.Sprintf("--delete=%t", flag)) + } + + err := cmd.Start() + if err != nil { + return fmt.Errorf("name: %v, args: %v, err: %v", command, arg, err.Error()) + } + + return nil +}