diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f08aaf..bec54ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,18 +1,16 @@ -name: build +name: Build on: push: branches: - "main" - tags: - - "v*" pull_request: permissions: contents: write jobs: - goreleaser: + build: runs-on: ubuntu-latest steps: - name: Checkout @@ -24,11 +22,3 @@ jobs: with: go-version: "1.20" check-latest: true - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 - with: - distribution: goreleaser - version: latest - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c9f4643 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,30 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: "1.20" + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v4 + with: + distribution: goreleaser + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 54c32f9..0f208bb 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -28,17 +28,22 @@ builds: ldflags: - "-s -w -X 'github.com/hyperledger/firefly-perf-cli/internal/version.Version={{.Version}}' -X 'github.com/hyperledger/firefly-perf-cli/internal/version.Commit={{.Commit}}' -X 'github.com/hyperledger/firefly-perf-cli/internal/version.Date={{.Date}}'" archives: - - replacements: - darwin: Darwin - linux: Linux - amd64: x86_64 + - name_template: >- + {{ .ProjectName }}_{{ .Version }}_ + {{- if eq .Os "darwin" }}macOS + {{- else if eq .Os "linux" }}Linux + {{- else }}{{ .Os }}{{ end }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else }}{{ .Arch }}{{ end }} checksum: - name_template: 'checksums.txt' + name_template: "checksums.txt" snapshot: name_template: "{{ incpatch .Tag }}-next" changelog: sort: asc filters: exclude: - - '^docs:' - - '^test:' + - "^docs:" + - "^test:" +release: + prerelease: auto diff --git a/cmd/run.go b/cmd/run.go index 2cb4e78..d754775 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/url" "path" "time" @@ -212,6 +213,13 @@ func generateRunnerConfigFromInstance(instance *conf.InstanceConfig, perfConfig runnerConfig.DelinquentAction = deliquentAction runnerConfig.FFNamespace = instance.FFNamespace runnerConfig.APIPrefix = instance.APIPrefix + if instance.FFNamespaceBasePath != "" { + basePath, err := url.JoinPath(instance.APIPrefix, instance.FFNamespaceBasePath) + if err != nil { + return nil, err + } + runnerConfig.FFNamespacePath = basePath + } runnerConfig.MaxTimePerAction = instance.MaxTimePerAction runnerConfig.MaxActions = instance.MaxActions runnerConfig.RampLength = instance.RampLength @@ -235,6 +243,15 @@ func setDefaults(runnerConfig *conf.RunnerConfig) { if runnerConfig.FFNamespace == "" { runnerConfig.FFNamespace = "default" } + + if runnerConfig.FFNamespacePath == "" { + basePath, err := url.JoinPath(runnerConfig.APIPrefix, "api/v1/namespaces", runnerConfig.FFNamespace) + if err != nil { + log.Error(err.Error()) + } + runnerConfig.FFNamespacePath = basePath + } + if runnerConfig.TokenOptions.TokenPoolConnectorName == "" { runnerConfig.TokenOptions.TokenPoolConnectorName = "erc20_erc721" } diff --git a/internal/conf/conf.go b/internal/conf/conf.go index 36ed9b1..d05bc93 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -44,8 +44,9 @@ type RunnerConfig struct { Daemon bool LogEvents bool SenderURL string - FFNamespace string APIPrefix string + FFNamespace string + FFNamespacePath string MaxTimePerAction time.Duration MaxActions int64 RampLength time.Duration @@ -76,6 +77,7 @@ type InstanceConfig struct { ContractOptions ContractOptions `json:"contractOptions,omitempty" yaml:"contractOptions,omitempty"` APIPrefix string `json:"apiPrefix,omitempty" yaml:"apiPrefix,omitempty"` FFNamespace string `json:"fireflyNamespace,omitempty" yaml:"fireflyNamespace,omitempty"` + FFNamespaceBasePath string `json:"namespaceBasePath,omitempty" yaml:"namespaceBasePath,omitempty"` MaxTimePerAction time.Duration `json:"maxTimePerAction,omitempty" yaml:"maxTimePerAction,omitempty"` MaxActions int64 `json:"maxActions,omitempty" yaml:"maxActions,omitempty"` RampLength time.Duration `json:"rampLength,omitempty" yaml:"rampLength,omitempty"` diff --git a/internal/perf/blob_broadcast_msg.go b/internal/perf/blob_broadcast_msg.go index 4069c64..ca41f1e 100644 --- a/internal/perf/blob_broadcast_msg.go +++ b/internal/perf/blob_broadcast_msg.go @@ -3,6 +3,7 @@ package perf import ( "fmt" "math/big" + "net/url" "github.com/hyperledger/firefly-perf-cli/internal/conf" "github.com/hyperledger/firefly/pkg/core" @@ -52,6 +53,10 @@ func (tc *blobBroadcast) RunOnce() (string, error) { }`, dataID, fmt.Sprintf("blob_%s_%d", tc.pr.tagPrefix, tc.workerID)) var resMessage core.Message var resError fftypes.RESTError + fullPath, err := url.JoinPath(tc.pr.client.BaseURL, tc.pr.cfg.FFNamespacePath, "messages/broadcast") + if err != nil { + return "", err + } res, err := tc.pr.client.R(). SetHeaders(map[string]string{ "Accept": "application/json", @@ -60,7 +65,7 @@ func (tc *blobBroadcast) RunOnce() (string, error) { SetBody([]byte(payload)). SetResult(&resMessage). SetError(&resError). - Post(fmt.Sprintf("%s/%sapi/v1/namespaces/%s/messages/broadcast", tc.pr.client.BaseURL, tc.pr.cfg.APIPrefix, tc.pr.cfg.FFNamespace)) + Post(fullPath) if err != nil || res.IsError() { return "", fmt.Errorf("Error sending broadcast message with blob attachment [%d]: %s (%+v)", resStatus(res), err, &resError) } diff --git a/internal/perf/blob_private_msg.go b/internal/perf/blob_private_msg.go index de97ec5..7d7e788 100644 --- a/internal/perf/blob_private_msg.go +++ b/internal/perf/blob_private_msg.go @@ -3,6 +3,7 @@ package perf import ( "fmt" "math/big" + "net/url" "github.com/hyperledger/firefly-perf-cli/internal/conf" "github.com/hyperledger/firefly/pkg/core" @@ -59,6 +60,10 @@ func (tc *blobPrivate) RunOnce() (string, error) { }`, dataID, tc.pr.cfg.RecipientOrg, fmt.Sprintf("blob_%s_%d", tc.pr.tagPrefix, tc.workerID)) var resMessage core.Message var resError fftypes.RESTError + fullPath, err := url.JoinPath(tc.pr.client.BaseURL, tc.pr.cfg.FFNamespacePath, "messages/private") + if err != nil { + return "", err + } res, err := tc.pr.client.R(). SetHeaders(map[string]string{ "Accept": "application/json", @@ -67,7 +72,7 @@ func (tc *blobPrivate) RunOnce() (string, error) { SetBody([]byte(payload)). SetResult(&resMessage). SetError(&resError). - Post(fmt.Sprintf("%s/%sapi/v1/namespaces/%s/messages/private", tc.pr.client.BaseURL, tc.pr.cfg.APIPrefix, tc.pr.cfg.FFNamespace)) + Post(fullPath) if err != nil || res.IsError() { return "", fmt.Errorf("Error sending private message with blob attachment [%d]: %s (%+v)", resStatus(res), err, &resError) } diff --git a/internal/perf/broadcast_msg.go b/internal/perf/broadcast_msg.go index 44ca3b0..8e8b1c6 100644 --- a/internal/perf/broadcast_msg.go +++ b/internal/perf/broadcast_msg.go @@ -2,6 +2,7 @@ package perf import ( "fmt" + "net/url" "github.com/hyperledger/firefly-perf-cli/internal/conf" "github.com/hyperledger/firefly/pkg/core" @@ -47,6 +48,10 @@ func (tc *broadcast) RunOnce() (string, error) { }`, tc.getMessageString(tc.pr.cfg.MessageOptions.LongMessage), fmt.Sprintf("%s_%d", tc.pr.tagPrefix, tc.workerID)) var resMessage core.Message var resError fftypes.RESTError + fullPath, err := url.JoinPath(tc.pr.client.BaseURL, tc.pr.cfg.FFNamespacePath, "messages/broadcast") + if err != nil { + return "", err + } res, err := tc.pr.client.R(). SetHeaders(map[string]string{ "Accept": "application/json", @@ -55,7 +60,7 @@ func (tc *broadcast) RunOnce() (string, error) { SetBody([]byte(payload)). SetResult(&resMessage). SetError(&resError). - Post(fmt.Sprintf("%s/%sapi/v1/namespaces/%s/messages/broadcast", tc.pr.client.BaseURL, tc.pr.cfg.APIPrefix, tc.pr.cfg.FFNamespace)) + Post(fullPath) if err != nil || res.IsError() { return "", fmt.Errorf("Error sending broadcast message [%d]: %s (%+v)", resStatus(res), err, &resError) } diff --git a/internal/perf/custom_ethereum_contract.go b/internal/perf/custom_ethereum_contract.go index e36defc..a945437 100644 --- a/internal/perf/custom_ethereum_contract.go +++ b/internal/perf/custom_ethereum_contract.go @@ -19,6 +19,7 @@ package perf import ( "encoding/json" "fmt" + "net/url" "strconv" "github.com/hyperledger/firefly-perf-cli/internal/conf" @@ -85,6 +86,10 @@ func (tc *customEthereum) RunOnce() (string, error) { }`, tc.pr.cfg.ContractOptions.Address, tc.workerID, idempotencyKey, invokeOptionsJSON) var resContractCall map[string]interface{} var resError fftypes.RESTError + fullPath, err := url.JoinPath(tc.pr.client.BaseURL, tc.pr.cfg.FFNamespacePath, "contracts/invoke") + if err != nil { + return "", err + } res, err := tc.pr.client.R(). SetHeaders(map[string]string{ "Accept": "application/json", @@ -93,7 +98,7 @@ func (tc *customEthereum) RunOnce() (string, error) { SetBody([]byte(payload)). SetResult(&resContractCall). SetError(&resError). - Post(fmt.Sprintf("%s/%sapi/v1/namespaces/%s/contracts/invoke", tc.pr.client.BaseURL, tc.pr.cfg.APIPrefix, tc.pr.cfg.FFNamespace)) + Post(fullPath) if err != nil || res.IsError() { if res.StatusCode() == 409 { log.Warnf("Request already received by FireFly: %+v", &resError) diff --git a/internal/perf/custom_fabric_contract.go b/internal/perf/custom_fabric_contract.go index 21300a1..a0e2dc4 100644 --- a/internal/perf/custom_fabric_contract.go +++ b/internal/perf/custom_fabric_contract.go @@ -19,6 +19,7 @@ package perf import ( "encoding/json" "fmt" + "net/url" "strconv" "github.com/hyperledger/firefly-perf-cli/internal/conf" @@ -126,6 +127,10 @@ func (tc *customFabric) RunOnce() (string, error) { }`, tc.pr.cfg.ContractOptions.Channel, tc.pr.cfg.ContractOptions.Chaincode, idempotencyKey, tc.workerID, tc.pr.cfg.SigningKey, idempotencyKey, invokeOptionsJSON) var resContractCall map[string]interface{} var resError fftypes.RESTError + fullPath, err := url.JoinPath(tc.pr.client.BaseURL, tc.pr.cfg.FFNamespacePath, "contracts/invoke") + if err != nil { + return "", err + } res, err := tc.pr.client.R(). SetHeaders(map[string]string{ "Accept": "application/json", @@ -134,7 +139,7 @@ func (tc *customFabric) RunOnce() (string, error) { SetBody([]byte(payload)). SetResult(&resContractCall). SetError(&resError). - Post(fmt.Sprintf("%s/%sapi/v1/namespaces/%s/contracts/invoke", tc.pr.client.BaseURL, tc.pr.cfg.APIPrefix, tc.pr.cfg.FFNamespace)) + Post(fullPath) if err != nil || res.IsError() { return "", fmt.Errorf("Error invoking contract [%d]: %s (%+v)", resStatus(res), err, &resError) } diff --git a/internal/perf/perf.go b/internal/perf/perf.go index d4bbb68..2378835 100644 --- a/internal/perf/perf.go +++ b/internal/perf/perf.go @@ -822,7 +822,7 @@ func (pr *perfRunner) createMsgConfirmSub(nodeURL, name, tag string) (subID stri }, Transport: TRANSPORT_TYPE, } - fullPath, err := url.JoinPath(nodeURL, pr.cfg.APIPrefix, "api/v1/namespaces", pr.cfg.FFNamespace, "subscriptions") + fullPath, err := url.JoinPath(nodeURL, pr.cfg.FFNamespacePath, "subscriptions") if err != nil { return "", "", err } @@ -1045,7 +1045,7 @@ func (pr *perfRunner) createEthereumContractListener(nodeURL string) (string, er var errResponse fftypes.RESTError var responseBody map[string]interface{} - fullPath, err := url.JoinPath(nodeURL, pr.cfg.APIPrefix, "api/v1/namespaces", pr.cfg.FFNamespace, "contracts/listeners") + fullPath, err := url.JoinPath(nodeURL, pr.cfg.FFNamespacePath, "contracts/listeners") if err != nil { return "", err } @@ -1085,7 +1085,7 @@ func (pr *perfRunner) createFabricContractListener(nodeURL string) (string, erro var errResponse fftypes.RESTError var responseBody map[string]interface{} - fullPath, err := url.JoinPath(nodeURL, pr.cfg.APIPrefix, "api/v1/namespaces", pr.cfg.FFNamespace, "contracts/listeners") + fullPath, err := url.JoinPath(nodeURL, pr.cfg.FFNamespacePath, "contracts/listeners") if err != nil { return "", err } @@ -1116,7 +1116,7 @@ func (pr *perfRunner) createFabricContractListener(nodeURL string) (string, erro } func (pr *perfRunner) deleteSubscription(nodeURL string, subscriptionID string) error { - fullPath, err := url.JoinPath(nodeURL, pr.cfg.APIPrefix, "api/v1/namespaces", pr.cfg.FFNamespace, "subscriptions", subscriptionID) + fullPath, err := url.JoinPath(nodeURL, pr.cfg.FFNamespacePath, "subscriptions", subscriptionID) if err != nil { return err } @@ -1129,7 +1129,7 @@ func (pr *perfRunner) deleteSubscription(nodeURL string, subscriptionID string) } func (pr *perfRunner) deleteContractListener(nodeURL string, listenerID string) error { - fullPath, err := url.JoinPath(nodeURL, pr.cfg.APIPrefix, "api/v1/namespaces", pr.cfg.FFNamespace, "contracts/listeners", listenerID) + fullPath, err := url.JoinPath(nodeURL, pr.cfg.FFNamespacePath, "contracts/listeners", listenerID) _, err = pr.client.R(). SetHeaders(map[string]string{ "Accept": "application/json", @@ -1162,7 +1162,7 @@ func (pr *perfRunner) createContractsSub(nodeURL, listenerID string) (subID stri }, Transport: TRANSPORT_TYPE, } - fullPath, err := url.JoinPath(nodeURL, pr.cfg.APIPrefix, "api/v1/namespaces", pr.cfg.FFNamespace, "subscriptions") + fullPath, err := url.JoinPath(nodeURL, pr.cfg.FFNamespacePath, "subscriptions") if err != nil { return "", "", err } @@ -1206,7 +1206,7 @@ func (pr *perfRunner) createTokenMintSub(nodeURL string) (subID string, subName }, Transport: TRANSPORT_TYPE, } - fullPath, err := url.JoinPath(nodeURL, pr.cfg.APIPrefix, "api/v1/namespaces", pr.cfg.FFNamespace, "subscriptions") + fullPath, err := url.JoinPath(nodeURL, pr.cfg.FFNamespacePath, "subscriptions") if err != nil { return "", "", err } @@ -1238,7 +1238,7 @@ func (pr *perfRunner) getMintRecipientBalance() (int, error) { var response PaginatedResponse var resError fftypes.RESTError - fullPath, err := url.JoinPath(pr.client.BaseURL, pr.cfg.APIPrefix, "api/v1/namespaces", pr.cfg.FFNamespace, "tokens/balances") + fullPath, err := url.JoinPath(pr.client.BaseURL, pr.cfg.FFNamespacePath, "tokens/balances") if err != nil { return 0, nil } diff --git a/internal/perf/private_msg.go b/internal/perf/private_msg.go index a1c518a..3f1646a 100644 --- a/internal/perf/private_msg.go +++ b/internal/perf/private_msg.go @@ -2,6 +2,7 @@ package perf import ( "fmt" + "net/url" "github.com/hyperledger/firefly-perf-cli/internal/conf" "github.com/hyperledger/firefly/pkg/core" @@ -54,6 +55,10 @@ func (tc *private) RunOnce() (string, error) { }`, tc.getMessageString(tc.pr.cfg.MessageOptions.LongMessage), tc.pr.cfg.RecipientOrg, fmt.Sprintf("%s_%d", tc.pr.tagPrefix, tc.workerID)) var resMessage core.Message var resError fftypes.RESTError + fullPath, err := url.JoinPath(tc.pr.client.BaseURL, tc.pr.cfg.FFNamespacePath, "messages/private") + if err != nil { + return "", err + } res, err := tc.pr.client.R(). SetHeaders(map[string]string{ "Accept": "application/json", @@ -62,7 +67,7 @@ func (tc *private) RunOnce() (string, error) { SetBody([]byte(payload)). SetResult(&resMessage). SetError(&resError). - Post(fmt.Sprintf("%s/%sapi/v1/namespaces/%s/messages/private", tc.pr.client.BaseURL, tc.pr.cfg.APIPrefix, tc.pr.cfg.FFNamespace)) + Post(fullPath) if err != nil || res.IsError() { return "", fmt.Errorf("Error sending private message [%d]: %s (%+v)", resStatus(res), err, &resError) } diff --git a/internal/perf/test_base.go b/internal/perf/test_base.go index 3344700..6e9c6fa 100644 --- a/internal/perf/test_base.go +++ b/internal/perf/test_base.go @@ -6,6 +6,7 @@ import ( "crypto/sha256" "fmt" "math/big" + "net/url" "github.com/go-resty/resty/v2" "github.com/hyperledger/firefly-common/pkg/fftypes" @@ -63,12 +64,15 @@ func (t *testBase) uploadBlob(blob []byte, hash [32]byte, nodeURL string) (strin // If there's no datatype, tell FireFly to automatically add a data payload formData["autometa"] = "true" formData["metadata"] = `{"mymeta": "data"}` - + fullPath, err := url.JoinPath(nodeURL, t.pr.cfg.FFNamespacePath, "data") + if err != nil { + return "", err + } resp, err := t.pr.client.R(). SetFormData(formData). SetFileReader("file", "myfile.txt", bytes.NewReader(blob)). SetResult(&data). - Post(fmt.Sprintf("%s/api/v1/namespaces/%s/data", nodeURL, t.pr.cfg.FFNamespace)) + Post(fullPath) if err != nil { return "", nil } @@ -83,13 +87,17 @@ func (t *testBase) uploadBlob(blob []byte, hash [32]byte, nodeURL string) (strin func (t *testBase) downloadAndVerifyBlob(nodeURL, id string, expectedHash [32]byte) error { var blob []byte + fullPath, err := url.JoinPath(nodeURL, t.pr.cfg.FFNamespacePath, "data", id, "blob") + if err != nil { + return err + } res, err := t.pr.client.R(). SetHeaders(map[string]string{ "Accept": "application/octet", "Content-Type": "application/json", }). SetResult(&blob). - Get(fmt.Sprintf("%s/api/v1/namespaces/%s/data/%s/blob", nodeURL, t.pr.cfg.FFNamespace, id)) + Get(fullPath) if err != nil { return err } diff --git a/internal/perf/token_mint.go b/internal/perf/token_mint.go index fd01f7c..64ccb48 100644 --- a/internal/perf/token_mint.go +++ b/internal/perf/token_mint.go @@ -18,6 +18,7 @@ package perf import ( "fmt" + "net/url" "github.com/hyperledger/firefly-perf-cli/internal/conf" "github.com/hyperledger/firefly/pkg/core" @@ -98,6 +99,10 @@ func (tc *tokenMint) RunOnce() (string, error) { var resTransfer core.TokenTransfer var resError fftypes.RESTError + fullPath, err := url.JoinPath(tc.pr.client.BaseURL, tc.pr.cfg.FFNamespacePath, "tokens/mint") + if err != nil { + return "", err + } res, err := tc.pr.client.R(). SetHeaders(map[string]string{ "Accept": "application/json", @@ -106,7 +111,7 @@ func (tc *tokenMint) RunOnce() (string, error) { SetBody([]byte(payload)). SetResult(&resTransfer). SetError(&resError). - Post(fmt.Sprintf("%s/%sapi/v1/namespaces/%s/tokens/mint", tc.pr.client.BaseURL, tc.pr.cfg.APIPrefix, tc.pr.cfg.FFNamespace)) + Post(fullPath) sentMintsCounter.Inc() if err != nil || res.IsError() { sentMintErrorCounter.Inc() diff --git a/internal/perf/token_pool.go b/internal/perf/token_pool.go index c621746..45d8835 100644 --- a/internal/perf/token_pool.go +++ b/internal/perf/token_pool.go @@ -18,7 +18,7 @@ package perf import ( "errors" - "fmt" + "net/url" "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/pkg/core" @@ -43,10 +43,13 @@ func (pr *perfRunner) CreateTokenPool() error { if pr.cfg.TokenOptions.Config.PoolBlockNumber != "" { config["blockNumber"] = pr.cfg.TokenOptions.Config.PoolBlockNumber } - + fullPath, err := url.JoinPath(pr.client.BaseURL, pr.cfg.FFNamespacePath, "tokens/pools?confirm=true") + if err != nil { + return err + } res, err := pr.client.R(). SetBody(&body). - Post(fmt.Sprintf("/%sapi/v1/namespaces/%s/tokens/pools?confirm=true", pr.cfg.APIPrefix, pr.cfg.FFNamespace)) + Post(fullPath) if err != nil || !res.IsSuccess() { return errors.New("Failed to create token pool")