├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── build-and-test.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── LICENSE.txt ├── Makefile ├── README.md ├── cmd └── cli │ └── main.go ├── go.mod ├── go.sum ├── internal ├── command.go └── logger.go ├── pkg └── client │ └── main.go ├── scripts ├── atf_result.sh ├── atf_run.sh ├── atf_scenario.sh └── install.sh └── tmp └── .keep /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | ## Describe the bug 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | ## To Reproduce 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | ## Actual behavior 23 | 24 | A clear and concise description of what actual behavior is. 25 | 26 | ## Expected behavior 27 | 28 | A clear and concise description of what you expected to happen. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | ## Describe the feature. 10 | 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | ## Describe the solution you'd like. 14 | 15 | A clear and concise description of what you want to happen. 16 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | A clear and concise description of what the PR is. Please put the url when associated with an issue 4 | 5 | ## Checklist 6 | 7 | A clear and concise description of what you have tested. 8 | 9 | - [ ] Running the `atf version` command printed the version and revision. 10 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: build-and-test 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | go-version: [1.16.x, 1.17.x] 11 | os: [ubuntu-latest] 12 | 13 | steps: 14 | - name: Install Go 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | - name: Build CLI 21 | run: make build-cli 22 | - name: Run CLI version command 23 | run: ./dist/atf version 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "v[0-9]+.[0-9]+.[0-9]+" 6 | 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Install Go 13 | uses: actions/setup-go@v2 14 | with: 15 | go-version: 1.13 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | - name: Run GoReleaser 19 | uses: goreleaser/goreleaser-action@v2 20 | with: 21 | distribution: goreleaser 22 | version: latest 23 | args: release --rm-dist 24 | env: 25 | # Scope that access public repository 26 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN_GITHUB }} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .envrc 2 | atf 3 | 4 | /dist 5 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: autify-cli 2 | env: 3 | - GO111MODULE=on 4 | before: 5 | hooks: 6 | - go mod tidy 7 | builds: 8 | - main: ./cmd/cli/main.go 9 | binary: atf 10 | ldflags: 11 | - -s -w 12 | - -X main.Version={{.Version}} 13 | - -X main.Revision={{.ShortCommit}} 14 | env: 15 | - CGO_ENABLED=0 16 | archives: 17 | - name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 18 | replacements: 19 | darwin: darwin 20 | linux: linux 21 | windows: windows 22 | 386: i386 23 | amd64: x86_64 24 | format_overrides: 25 | - goos: windows 26 | format: zip 27 | release: 28 | prerelease: auto 29 | brews: 30 | - tap: 31 | owner: koukikitamura 32 | name: homebrew-autify-cli 33 | folder: Formula 34 | homepage: "https://github.com/koukikitamura/autify-client" 35 | description: "Autify CLI client" 36 | license: "MIT" 37 | install: | 38 | bin.install "atf" 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2021] [koki kitamura] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REVISION := $(shell git rev-parse --short HEAD) 2 | LDFLAGS := "-X main.Revision=$(REVISION)" 3 | 4 | build-cli: 5 | go build -ldflags $(LDFLAGS) -o dist/atf ./cmd/cli/main.go 6 | 7 | test: 8 | go test ./... 9 | 10 | test-scenario: 11 | @./scripts/atf_scenario.sh 12 | 13 | test-result: 14 | @./scripts/atf_result.sh 15 | 16 | test-run: 17 | @./scripts/atf_run.sh 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autify-client 2 | 3 | The autify-client provides [Autify](https://autify.com/) client go package & CLI. 4 | 5 | ## CLI Installation 6 | 7 | ### Homebrew 8 | 9 | ``` 10 | brew tap koukikitamura/autify-cli 11 | brew install autify-cli 12 | ``` 13 | 14 | ### Download a binary 15 | 16 | Download TAR archive from [Github release page](https://github.com/koukikitamura/autify-client/releases). 17 | 18 | ``` 19 | curl -LSfs https://raw.githubusercontent.com/koukikitamura/autify-client/main/scripts/install.sh | \ 20 | sh -s -- \ 21 | --git koukikitamura/autify-client \ 22 | --target autify-cli_linux_x86_64 \ 23 | --to /usr/local/bin 24 | ``` 25 | 26 | ## CLI Configuration 27 | 28 | Before using the autify-cli, you need to configure your credentials. You can use environment variable. 29 | 30 | ``` 31 | export AUTIFY_PERSONAL_ACCESS_TOKEN= 32 | ``` 33 | 34 | ## CLI Basic Commands 35 | 36 | An autify-cli command has the following structure: 37 | 38 | ``` 39 | $ atf [options] 40 | ``` 41 | 42 | To run test plan and wait to finish, the command would be: 43 | 44 | ``` 45 | $ atf run --project-id=999 --plan-id=999 46 | {"id":999,"status":"passed","duration":26251,"started_at":"2021-03-28T11:03:31.288Z","finished_at":"2021-03-28T11:03:57.54Z","created_at":"2021-03-28T11:03:04.716Z","updated_at":"2021-03-28T11:04:00.738Z","test_plan":{"id":999,"name":"main flow","created_at":"2021-03-26T08:25:12.987Z","updated_at":"2021-03-26T08:33:45.462Z"}} 47 | ``` 48 | 49 | To fetch scenario, the command would be: 50 | 51 | ``` 52 | $ atf scenario --project-id=999 --scenario-id=999 53 | {"id":999,"name":"login","created_at":"2021-03-26T07:53:20.039Z","updated_at":"2021-03-26T08:20:51.86Z"} 54 | ``` 55 | 56 | To fetch test plan excution result, the command would be: 57 | 58 | ``` 59 | $ atf result --project-id=999 --result-id=999 60 | {"id":999,"status":"waiting","duration":26621,"started_at":"2021-03-26T10:09:12.915Z","finished_at":"2021-03-26T10:09:39.537Z","created_at":"2021-03-26T10:08:54.769Z","updated_at":"2021-03-26T10:09:44.542Z","test_plan":{"id":999,"name":"main flow","created_at":"2021-03-26T08:25:12.987Z","updated_at":"2021-03-26T08:33:45.462Z"}} 61 | ``` 62 | 63 | ## Terms 64 | 65 | ### project-id 66 | 67 | The path of the Autify dashboard home is `/projects/[project-id]`. This path parameter is the project-id. 68 | 69 | ### plan-id 70 | 71 | The path of the test plan's detail page is `/projects/[project-id]/test_plans/[plan-id]`. This path parameter is the plan-id. 72 | 73 | ### scenario-id 74 | 75 | The path of the scenario's detail page is `/projects/[project-id]/scenarios/[scenario-id]`. This path parameter is the scenario-id. 76 | 77 | ### result-id 78 | 79 | The path of the result's detail page is `/projects/[project-id]/results/[result-id]`. This path parameter is the result-id. 80 | 81 | ## Go package 82 | 83 | The following is the code to run the test plan and poll its status. 84 | 85 | ```go 86 | package main 87 | 88 | import ( 89 | "encoding/json" 90 | "fmt" 91 | "os" 92 | "time" 93 | 94 | "github.com/koukikitamura/autify-client/pkg/client" 95 | ) 96 | 97 | const ( 98 | ExitCodeOk int = 0 99 | ExitCodeError int = 1 100 | ) 101 | 102 | func main() { 103 | var projectId = 999 104 | var planId = 999 105 | 106 | autify := client.NewAutfiy(client.GetAccessToken()) 107 | 108 | runResult, err := autify.RunTestPlan(planId) 109 | if err != nil { 110 | fmt.Println("Error: Failed to run the test plan") 111 | os.Exit(ExitCodeError) 112 | } 113 | 114 | ticker := time.NewTicker(time.Duration(1) * time.Second) 115 | defer ticker.Stop() 116 | 117 | var testResult *client.TestPlanResult 118 | for { 119 | select { 120 | case <-ticker.C: 121 | testResult, err = autify.FetchResult(projectId, runResult.Attributes.Id) 122 | if err != nil { 123 | fmt.Println("Error: Failed to fetch the result") 124 | os.Exit(ExitCodeError) 125 | } 126 | 127 | if testResult.Status != client.TestPlanStatuWaiting && 128 | testResult.Status != client.TestPlanStatusQueuing && 129 | testResult.Status != client.TestPlanStatusRunning { 130 | jsonStr, err := json.Marshal(*testResult) 131 | if err != nil { 132 | fmt.Println("Error: Failed to marshal the test result") 133 | os.Exit(ExitCodeError) 134 | } 135 | 136 | fmt.Println((string(jsonStr))) 137 | os.Exit(ExitCodeOk) 138 | } 139 | 140 | case <-time.After(time.Duration(5) * time.Minute): 141 | fmt.Println("Error: Timeout") 142 | os.Exit(1) 143 | } 144 | } 145 | } 146 | ``` 147 | -------------------------------------------------------------------------------- /cmd/cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/koukikitamura/autify-client/internal" 7 | "github.com/mitchellh/cli" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | // These variables are set in build step 12 | var ( 13 | Version = "unset" 14 | Revision = "unset" 15 | ) 16 | 17 | func main() { 18 | c := cli.NewCLI("atf", Version) 19 | c.Args = os.Args[1:] 20 | 21 | c.Commands = map[string]cli.CommandFactory{ 22 | internal.VersionCommandName: func() (cli.Command, error) { 23 | return &internal.VersionCommand{Version: Version, Revision: Revision}, nil 24 | }, 25 | internal.RunCommandName: func() (cli.Command, error) { 26 | return &internal.RunCommand{}, nil 27 | }, 28 | internal.ScenarioCommandName: func() (cli.Command, error) { 29 | return &internal.ScenarioCommand{}, nil 30 | }, 31 | internal.ResultCommandName: func() (cli.Command, error) { 32 | return &internal.ResultCommand{}, nil 33 | }, 34 | } 35 | 36 | exitCode, err := c.Run() 37 | if err != nil { 38 | logrus.Errorf("%+v", err) 39 | } 40 | 41 | os.Exit(exitCode) 42 | } 43 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/koukikitamura/autify-client 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/briandowns/spinner v1.12.0 7 | github.com/mitchellh/cli v1.1.2 8 | github.com/pkg/errors v0.9.1 9 | github.com/sirupsen/logrus v1.8.1 10 | ) 11 | 12 | require ( 13 | github.com/Masterminds/goutils v1.1.0 // indirect 14 | github.com/Masterminds/semver v1.5.0 // indirect 15 | github.com/Masterminds/sprig v2.22.0+incompatible // indirect 16 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 // indirect 17 | github.com/bgentry/speakeasy v0.1.0 // indirect 18 | github.com/fatih/color v1.7.0 // indirect 19 | github.com/google/uuid v1.1.2 // indirect 20 | github.com/hashicorp/errwrap v1.0.0 // indirect 21 | github.com/hashicorp/go-multierror v1.0.0 // indirect 22 | github.com/huandu/xstrings v1.3.2 // indirect 23 | github.com/imdario/mergo v0.3.11 // indirect 24 | github.com/mattn/go-colorable v0.1.2 // indirect 25 | github.com/mattn/go-isatty v0.0.8 // indirect 26 | github.com/mitchellh/copystructure v1.0.0 // indirect 27 | github.com/mitchellh/reflectwalk v1.0.0 // indirect 28 | github.com/posener/complete v1.1.1 // indirect 29 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect 30 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= 2 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 3 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= 4 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 5 | github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= 6 | github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= 7 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= 8 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 9 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 10 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 11 | github.com/briandowns/spinner v1.12.0 h1:72O0PzqGJb6G3KgrcIOtL/JAGGZ5ptOMCn9cUHmqsmw= 12 | github.com/briandowns/spinner v1.12.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= 13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 17 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 18 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 19 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 20 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 21 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 22 | github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= 23 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 24 | github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= 25 | github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 26 | github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= 27 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 28 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 29 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 30 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 31 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 32 | github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= 33 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 34 | github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw= 35 | github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= 36 | github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= 37 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 38 | github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= 39 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 40 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 41 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 42 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 43 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 44 | github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= 45 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 46 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 47 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 48 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 49 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 50 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 51 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 52 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 53 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= 54 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 55 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 56 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 57 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 58 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 59 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= 60 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 61 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 62 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 63 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 64 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 65 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 66 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 67 | -------------------------------------------------------------------------------- /internal/command.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/briandowns/spinner" 10 | "github.com/koukikitamura/autify-client/pkg/client" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | const ( 15 | ExitCodeOk int = 0 16 | ExitCodeError int = 1 17 | ) 18 | 19 | const ( 20 | VersionCommandName = "version" 21 | ScenarioCommandName = "scenario" 22 | ResultCommandName = "result" 23 | RunCommandName = "run" 24 | ) 25 | 26 | func RequireCredential() bool { 27 | if ok := client.CheckAccessToken(); !ok { 28 | fmt.Printf("Require %s environment variable\n", client.AccessTokenEnvName) 29 | return false 30 | } 31 | 32 | return true 33 | } 34 | 35 | type VersionCommand struct { 36 | Version string 37 | Revision string 38 | } 39 | 40 | func (v *VersionCommand) Help() string { 41 | return "Print cli version & revision" 42 | } 43 | 44 | func (v *VersionCommand) Run(args []string) int { 45 | fmt.Printf("Version: %s\nRevision: %s\n", v.Version, v.Revision) 46 | return ExitCodeOk 47 | } 48 | 49 | func (r *VersionCommand) Synopsis() string { 50 | return "Print cli version" 51 | } 52 | 53 | type RunCommand struct{} 54 | 55 | func (r *RunCommand) Help() string { 56 | return "Run test plan" 57 | } 58 | 59 | func (r *RunCommand) Run(args []string) int { 60 | if ok := RequireCredential(); !ok { 61 | return ExitCodeError 62 | } 63 | 64 | var projectId, planId, interval, timeout int 65 | var debug, showSpinner bool 66 | 67 | flags := flag.NewFlagSet(RunCommandName, flag.ContinueOnError) 68 | flags.IntVar(&projectId, "project-id", -1, "Specify project id") 69 | flags.IntVar(&planId, "plan-id", -1, "Specify plan id") 70 | flags.IntVar(&interval, "interval", 3, "Specify interval, unit is second") 71 | flags.IntVar(&timeout, "timeout", 3, "Specify interval, unit is minute") 72 | flags.BoolVar(&debug, "debug", false, "Print excution logs") 73 | flags.BoolVar(&showSpinner, "spinner", true, "Show spinner for waiting to finnish test") 74 | 75 | if err := flags.Parse(args); err != nil { 76 | return ExitCodeError 77 | } 78 | 79 | if projectId < 0 || planId < 0 { 80 | logrus.Error("project-id and plan-id is greater than or equal to zero.") 81 | return ExitCodeError 82 | } 83 | 84 | if debug { 85 | logrus.SetLevel(logrus.DebugLevel) 86 | } 87 | 88 | autify := client.NewAutfiy(client.GetAccessToken()) 89 | 90 | runResult, err := autify.RunTestPlan(planId) 91 | if err != nil { 92 | logrus.Errorf("%+v", err) 93 | return ExitCodeError 94 | } 95 | 96 | ticker := time.NewTicker(time.Duration(interval) * time.Second) 97 | defer ticker.Stop() 98 | 99 | if showSpinner { 100 | s := spinner.New(spinner.CharSets[12], 100*time.Millisecond) 101 | s.Prefix = "Waiting for test plan to finish. " 102 | s.Start() 103 | defer s.Stop() 104 | } 105 | 106 | var testResult *client.TestPlanResult 107 | 108 | for { 109 | select { 110 | case <-ticker.C: 111 | testResult, err = autify.FetchResult(projectId, runResult.Attributes.Id) 112 | 113 | if err != nil { 114 | fmt.Print("\r\033[K") 115 | logrus.Errorf("%+v", err) 116 | return ExitCodeError 117 | } 118 | if testResult.Status != client.TestPlanStatuWaiting && 119 | testResult.Status != client.TestPlanStatusQueuing && 120 | testResult.Status != client.TestPlanStatusRunning { 121 | jsonStr, err := json.Marshal(*testResult) 122 | 123 | fmt.Print("\r\033[K") 124 | if err != nil { 125 | logrus.Errorf("%+v", err) 126 | return ExitCodeError 127 | } 128 | 129 | fmt.Println(string(jsonStr)) 130 | return ExitCodeOk 131 | } 132 | 133 | case <-time.After(time.Duration(timeout) * time.Minute): 134 | return ExitCodeOk 135 | } 136 | } 137 | } 138 | 139 | func (r *RunCommand) Synopsis() string { 140 | return "Run test plan" 141 | } 142 | 143 | type ScenarioCommand struct{} 144 | 145 | func (s *ScenarioCommand) Help() string { 146 | return "Get scenario" 147 | } 148 | 149 | func (s *ScenarioCommand) Run(args []string) int { 150 | if ok := RequireCredential(); !ok { 151 | return ExitCodeError 152 | } 153 | 154 | var projectId, scenarioId int 155 | var debug bool 156 | 157 | flags := flag.NewFlagSet(ScenarioCommandName, flag.ContinueOnError) 158 | flags.IntVar(&projectId, "project-id", -1, "Specify project id") 159 | flags.IntVar(&scenarioId, "scenario-id", -1, "Specify project id") 160 | flags.BoolVar(&debug, "debug", false, "Print excution logs") 161 | 162 | if err := flags.Parse(args); err != nil { 163 | return ExitCodeError 164 | } 165 | 166 | if projectId < 0 || scenarioId < 0 { 167 | logrus.Error("project-id and scenario-id is greater than or equal to zero.") 168 | return ExitCodeError 169 | } 170 | 171 | if debug { 172 | logrus.SetLevel(logrus.DebugLevel) 173 | } 174 | 175 | autify := client.NewAutfiy(client.GetAccessToken()) 176 | 177 | scenario, err := autify.FetchScenario(projectId, scenarioId) 178 | if err != nil { 179 | logrus.Errorf("%+v", err) 180 | return ExitCodeError 181 | } 182 | 183 | jsonStr, err := json.Marshal(scenario) 184 | if err != nil { 185 | logrus.Errorf("%+v", err) 186 | return ExitCodeError 187 | } 188 | 189 | fmt.Println(string(jsonStr)) 190 | return ExitCodeOk 191 | } 192 | 193 | func (s *ScenarioCommand) Synopsis() string { 194 | return "Get scenario" 195 | } 196 | 197 | type ResultCommand struct{} 198 | 199 | func (r *ResultCommand) Help() string { 200 | return "Get result" 201 | } 202 | 203 | func (r *ResultCommand) Run(args []string) int { 204 | if ok := RequireCredential(); !ok { 205 | return ExitCodeError 206 | } 207 | 208 | var projectId, resultId int 209 | var debug bool 210 | 211 | flags := flag.NewFlagSet(ResultCommandName, flag.ContinueOnError) 212 | flags.IntVar(&projectId, "project-id", -1, "Specify project id") 213 | flags.IntVar(&resultId, "result-id", -1, "Specify project id") 214 | flags.BoolVar(&debug, "debug", false, "Print excution logs") 215 | 216 | if err := flags.Parse(args); err != nil { 217 | return ExitCodeError 218 | } 219 | 220 | if projectId < 0 || resultId < 0 { 221 | logrus.Error("project-id and result-id is greater than or equal to zero.") 222 | return ExitCodeError 223 | } 224 | 225 | if debug { 226 | logrus.SetLevel(logrus.DebugLevel) 227 | } 228 | 229 | autify := client.NewAutfiy(client.GetAccessToken()) 230 | 231 | result, err := autify.FetchResult(projectId, resultId) 232 | if err != nil { 233 | logrus.Errorf("%+v", err) 234 | return ExitCodeError 235 | } 236 | 237 | jsonStr, err := json.Marshal(result) 238 | if err != nil { 239 | logrus.Errorf("%+v", err) 240 | return ExitCodeError 241 | } 242 | 243 | fmt.Println(string(jsonStr)) 244 | return ExitCodeOk 245 | } 246 | 247 | func (r *ResultCommand) Synopsis() string { 248 | return "Get result" 249 | } 250 | -------------------------------------------------------------------------------- /internal/logger.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "os" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func init() { 10 | // Output to stdout instead of the default stderr 11 | // Can be any io.Writer, see below for File example 12 | log.SetOutput(os.Stdout) 13 | 14 | // Only log the warning severity or above. 15 | log.SetLevel(log.ErrorLevel) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/client/main.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | "strings" 10 | "time" 11 | 12 | "github.com/pkg/errors" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | const AccessTokenEnvName = "AUTIFY_PERSONAL_ACCESS_TOKEN" 17 | const defaultBaseUrl = "https://app.autify.com/api/v1" 18 | 19 | type Autify struct { 20 | httpClient *http.Client 21 | accessToken string 22 | baseUrl string 23 | } 24 | 25 | type AutifyOption func(a *Autify) 26 | 27 | func AutifyOptionBaseUrl(baseUrl string) AutifyOption { 28 | return func(autify *Autify) { 29 | autify.baseUrl = baseUrl 30 | } 31 | } 32 | 33 | func AutifyOptionHTTPClient(c *http.Client) AutifyOption { 34 | return func(autify *Autify) { 35 | autify.httpClient = c 36 | } 37 | } 38 | 39 | func NewAutfiy(accessToken string, options ...AutifyOption) *Autify { 40 | autify := &Autify{ 41 | accessToken: accessToken, 42 | httpClient: http.DefaultClient, 43 | baseUrl: defaultBaseUrl, 44 | } 45 | 46 | for _, option := range options { 47 | option(autify) 48 | } 49 | 50 | return autify 51 | } 52 | 53 | type RunResult struct { 54 | TestPlanResultId string `json:"id"` 55 | Type string `json:"type"` 56 | Attributes struct { 57 | Id int `json:"id"` 58 | } `json:"attributes"` 59 | } 60 | 61 | type Scenario struct { 62 | Id int `json:"id"` 63 | Name string `json:"name"` 64 | CreatedAt time.Time `json:"created_at"` 65 | UpdatedAt time.Time `json:"updated_at"` 66 | } 67 | 68 | type TestPlan struct { 69 | Id int `json:"id"` 70 | Name string `json:"name"` 71 | CreatedAt time.Time `json:"created_at"` 72 | UpdatedAt time.Time `json:"updated_at"` 73 | } 74 | 75 | type TestPlanResult struct { 76 | Id int `json:"id"` 77 | Status string `json:"status"` 78 | Duration int `json:"duration"` 79 | StartedAt time.Time `json:"started_at"` 80 | FinishedAt time.Time `json:"finished_at"` 81 | CreatedAt time.Time `json:"created_at"` 82 | UpdatedAt time.Time `json:"updated_at"` 83 | TestPlan `json:"test_plan"` 84 | } 85 | 86 | const ( 87 | TestPlanStatusWarning = "warning" 88 | TestPlanStatusQueuing = "queuing" 89 | TestPlanStatuWaiting = "waiting" 90 | TestPlanStatusRunning = "running" 91 | TestPlanStatusPassed = "passed" 92 | TestPlanStatusFailed = "failed" 93 | ) 94 | 95 | type RuntTestPlanResponse struct { 96 | Data RunResult `json:"data"` 97 | } 98 | 99 | func (a *Autify) RunTestPlan(planId int) (*RunResult, error) { 100 | path := fmt.Sprintf("/schedules/%d", planId) 101 | 102 | request, err := http.NewRequest(http.MethodPost, strings.Join([]string{a.baseUrl, path}, "/"), nil) 103 | if err != nil { 104 | return nil, errors.WithStack(err) 105 | } 106 | request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", a.accessToken)) 107 | 108 | logrus.WithFields(logrus.Fields{ 109 | "method": request.Method, 110 | "url": request.URL, 111 | }).Debug("Request to autify") 112 | 113 | response, err := a.httpClient.Do(request) 114 | if err != nil { 115 | return nil, errors.WithStack(err) 116 | } 117 | defer response.Body.Close() 118 | 119 | body, err := ioutil.ReadAll(response.Body) 120 | if err != nil { 121 | return nil, errors.WithStack(err) 122 | } 123 | logrus.WithFields(logrus.Fields{ 124 | "method": response.Request.Method, 125 | "url": response.Request.URL, 126 | "status": response.Status, 127 | }).Debug("Respond from autify") 128 | logrus.Debug("Body is ", string(body)) 129 | 130 | if response.StatusCode == http.StatusUnauthorized { 131 | return nil, errors.New("Unauthorized: Bad credentials") 132 | } 133 | 134 | if response.StatusCode == http.StatusNotFound { 135 | return nil, errors.New("Not found test plan") 136 | } 137 | 138 | var result RuntTestPlanResponse 139 | if err := json.Unmarshal(body, &result); err != nil { 140 | return nil, errors.WithStack(err) 141 | } 142 | 143 | return &result.Data, nil 144 | } 145 | 146 | func (a *Autify) FetchScenario(projectId, scenarioId int) (*Scenario, error) { 147 | path := fmt.Sprintf("projects/%d/scenarios/%d", projectId, scenarioId) 148 | 149 | request, err := http.NewRequest(http.MethodGet, strings.Join([]string{a.baseUrl, path}, "/"), nil) 150 | if err != nil { 151 | return nil, errors.WithStack(err) 152 | } 153 | request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", a.accessToken)) 154 | 155 | logrus.WithFields(logrus.Fields{ 156 | "method": request.Method, 157 | "url": request.URL, 158 | }).Debug("Request to autify") 159 | 160 | response, err := a.httpClient.Do(request) 161 | if err != nil { 162 | return nil, errors.WithStack(err) 163 | } 164 | defer response.Body.Close() 165 | 166 | body, err := ioutil.ReadAll(response.Body) 167 | if err != nil { 168 | return nil, errors.WithStack(err) 169 | } 170 | logrus.WithFields(logrus.Fields{ 171 | "method": response.Request.Method, 172 | "url": response.Request.URL, 173 | "status": response.Status, 174 | }).Debug("Respond from autify") 175 | logrus.Debug("Body is ", string(body)) 176 | 177 | if response.StatusCode == http.StatusUnauthorized { 178 | return nil, errors.New("Unauthorized: Bad credentials") 179 | } 180 | 181 | if response.StatusCode == http.StatusNotFound { 182 | return nil, errors.New("Not found scenario") 183 | } 184 | 185 | var scenario Scenario 186 | if err := json.Unmarshal(body, &scenario); err != nil { 187 | return nil, errors.WithStack(err) 188 | } 189 | 190 | return &scenario, nil 191 | } 192 | 193 | func (a *Autify) FetchResult(projectId, resultId int) (*TestPlanResult, error) { 194 | path := fmt.Sprintf("/projects/%d/results/%d", projectId, resultId) 195 | 196 | request, err := http.NewRequest(http.MethodGet, strings.Join([]string{a.baseUrl, path}, "/"), nil) 197 | if err != nil { 198 | return nil, errors.WithStack(err) 199 | } 200 | request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", a.accessToken)) 201 | 202 | logrus.WithFields(logrus.Fields{ 203 | "method": request.Method, 204 | "url": request.URL, 205 | }).Debug("Request to autify") 206 | 207 | response, err := a.httpClient.Do(request) 208 | if err != nil { 209 | return nil, errors.WithStack(err) 210 | } 211 | defer response.Body.Close() 212 | 213 | body, err := ioutil.ReadAll(response.Body) 214 | if err != nil { 215 | return nil, errors.WithStack(err) 216 | } 217 | logrus.WithFields(logrus.Fields{ 218 | "method": response.Request.Method, 219 | "url": response.Request.URL, 220 | "status": response.Status, 221 | }).Debug("Respond from autify") 222 | logrus.Debug("Body is ", string(body)) 223 | 224 | if response.StatusCode == http.StatusUnauthorized { 225 | return nil, errors.New("Unauthorized: Bad credentials") 226 | } 227 | 228 | if response.StatusCode == http.StatusNotFound { 229 | return nil, errors.New("Result is not found") 230 | } 231 | 232 | var result TestPlanResult 233 | if err := json.Unmarshal(body, &result); err != nil { 234 | return nil, errors.WithStack(err) 235 | } 236 | 237 | return &result, nil 238 | } 239 | 240 | func CheckAccessToken() bool { 241 | return len(GetAccessToken()) > 0 242 | } 243 | 244 | func GetAccessToken() string { 245 | return os.Getenv(AccessTokenEnvName) 246 | } 247 | -------------------------------------------------------------------------------- /scripts/atf_result.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR=$(cd $(dirname $BASH_SOURCE); pwd) 4 | ROOT_DIR="${DIR}/.." 5 | cd ${ROOT_DIR} 6 | 7 | go build -o ./tmp/atf cmd/cli/main.go 8 | 9 | ./tmp/atf result --project-id=$AUTIFY_TEST_PROJECT_ID --result-id=$AUTIFY_TEST_RESULT_ID 10 | 11 | rm ./tmp/atf 12 | -------------------------------------------------------------------------------- /scripts/atf_run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR=$(cd $(dirname $BASH_SOURCE); pwd) 4 | ROOT_DIR="${DIR}/.." 5 | cd ${ROOT_DIR} 6 | 7 | go build -o ./tmp/atf cmd/cli/main.go 8 | 9 | ./tmp/atf run --project-id=$AUTIFY_TEST_PROJECT_ID --plan-id=$AUTIFY_TEST_PLAN_ID 10 | 11 | rm ./tmp/atf 12 | -------------------------------------------------------------------------------- /scripts/atf_scenario.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR=$(cd $(dirname $BASH_SOURCE); pwd) 4 | ROOT_DIR="${DIR}/.." 5 | cd ${ROOT_DIR} 6 | 7 | go build -o ./tmp/atf cmd/cli/main.go 8 | 9 | ./tmp/atf scenario --project-id=$AUTIFY_TEST_PROJECT_ID --scenario-id=$AUTIFY_TEST_SCENRIO_ID 10 | 11 | rm ./tmp/atf 12 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Original: https://github.com/japaric/trust/blob/gh-pages/install.sh 4 | 5 | # ./install.sh \ 6 | # --git koukikitamura/autify-client \ 7 | # --target autify-cli_darwin_x86_64 \ 8 | # --to ./usr/local/bin 9 | 10 | set -e 11 | 12 | help() { 13 | cat <<'EOF' 14 | Install a binary release of a Rust crate hosted on GitHub 15 | 16 | Usage: 17 | install.sh [options] 18 | 19 | Options: 20 | -h, --help Display this message 21 | --git SLUG Get the crate from "https://github/$SLUG" 22 | -f, --force Force overwriting an existing binary 23 | --crate NAME Name of the crate to install (default ) 24 | --tag TAG Tag (version) of the crate to install (default ) 25 | --target TARGET Install the release compiled for $TARGET (default <`rustc` host>) 26 | --to LOCATION Where to install the binary (default ~/.cargo/bin) 27 | EOF 28 | } 29 | 30 | say() { 31 | echo "install.sh: $1" 32 | } 33 | 34 | say_err() { 35 | say "$1" >&2 36 | } 37 | 38 | err() { 39 | if [ ! -z $td ]; then 40 | rm -rf $td 41 | fi 42 | 43 | say_err "ERROR $1" 44 | exit 1 45 | } 46 | 47 | need() { 48 | if ! command -v $1 > /dev/null 2>&1; then 49 | err "need $1 (command not found)" 50 | fi 51 | } 52 | 53 | force=false 54 | while test $# -gt 0; do 55 | case $1 in 56 | --crate) 57 | crate=$2 58 | shift 59 | ;; 60 | --force | -f) 61 | force=true 62 | ;; 63 | --git) 64 | git=$2 65 | shift 66 | ;; 67 | --help | -h) 68 | help 69 | exit 0 70 | ;; 71 | --tag) 72 | tag=$2 73 | shift 74 | ;; 75 | --target) 76 | target=$2 77 | shift 78 | ;; 79 | --to) 80 | dest=$2 81 | shift 82 | ;; 83 | *) 84 | ;; 85 | esac 86 | shift 87 | done 88 | 89 | # Dependencies 90 | need basename 91 | need curl 92 | need install 93 | need mkdir 94 | need mktemp 95 | need tar 96 | 97 | # Optional dependencies 98 | if [ -z $crate ] || [ -z $tag ] || [ -z $target ]; then 99 | need cut 100 | fi 101 | 102 | if [ -z $tag ]; then 103 | need rev 104 | fi 105 | 106 | if [ -z $target ]; then 107 | need grep 108 | need rustc 109 | fi 110 | 111 | if [ -z $git ]; then 112 | err 'must specify a git repository using `--git`. Example: `install.sh --git japaric/cross`' 113 | fi 114 | 115 | url="https://github.com/$git" 116 | say_err "GitHub repository: $url" 117 | 118 | if [ -z $crate ]; then 119 | crate=$(echo $git | cut -d'/' -f2) 120 | fi 121 | 122 | say_err "Crate: $crate" 123 | 124 | url="$url/releases" 125 | 126 | if [ -z $tag ]; then 127 | tag=$(curl -s "$url/latest" | cut -d'"' -f2 | rev | cut -d'/' -f1 | rev) 128 | say_err "Tag: latest ($tag)" 129 | else 130 | say_err "Tag: $tag" 131 | fi 132 | 133 | if [ -z $target ]; then 134 | target=$(rustc -Vv | grep host | cut -d' ' -f2) 135 | fi 136 | 137 | say_err "Target: $target" 138 | 139 | if [ -z $dest ]; then 140 | dest="$HOME/.cargo/bin" 141 | fi 142 | 143 | say_err "Installing to: $dest" 144 | 145 | url="$url/download/$tag/$target.tar.gz" 146 | 147 | 148 | td=$(mktemp -d || mktemp -d -t tmp) 149 | curl -sL $url | tar -C $td -xz 150 | 151 | for f in $(ls $td); do 152 | test -x $td/$f || continue 153 | 154 | if [ -e "$dest/$f" ] && [ $force = false ]; then 155 | err "$f already exists in $dest" 156 | else 157 | mkdir -p $dest 158 | install -m 755 $td/$f $dest 159 | fi 160 | done 161 | 162 | rm -rf $td 163 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koukikitamura/autify-client/1a0fc580309f50af84b781c71a0a1f56e0626e36/tmp/.keep --------------------------------------------------------------------------------