├── .github └── workflows │ ├── experimental_release_publisher.yml │ ├── release_publisher.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README-dev.md ├── README.md ├── cmd └── multiwerf │ ├── main.go │ └── main_test.go ├── get.sh ├── go-build.sh ├── go-get.sh ├── go.mod ├── go.sum ├── integration ├── _fixtures │ ├── gc │ │ ├── local_versions_exist │ │ │ ├── v0.0.0 │ │ │ │ └── .gitkeep │ │ │ ├── v0.0.1 │ │ │ │ └── .gitkeep │ │ │ └── v0.1.0 │ │ │ │ └── .gitkeep │ │ ├── multiwerf_json_exist │ │ │ └── multiwerf.json │ │ └── multiwerf_json_old_exist │ │ │ └── multiwerf.json.old │ └── other_commands │ │ └── multiwerf.json ├── gc_test.go ├── other_comands_test.go ├── suite_test.go ├── update_test.go └── use_test.go ├── pkg ├── app │ └── app.go ├── http │ └── client.go ├── locker │ └── locker.go ├── multiwerf │ ├── action_message.go │ ├── binary_info.go │ ├── channel_mapping.go │ ├── delay_file.go │ ├── gc.go │ ├── multiwerf.go │ ├── release_files.go │ ├── repo.go │ ├── self_update.go │ ├── semver_utils.go │ ├── semver_utils_test.go │ └── update.go ├── output │ ├── printer.go │ ├── silent.go │ └── simple.go ├── repo │ ├── bintray.go │ ├── repo.go │ └── s3.go ├── trdlexec │ └── trdlexec.go ├── util │ ├── is_writable.go │ ├── is_writable_windows.go │ └── rndstr.go └── util_test │ ├── command.go │ ├── file.go │ ├── random.go │ └── suite.go └── scripts ├── build_release.sh ├── create_release_tag.sh ├── get.sh ├── git_tag_template.md ├── lib ├── bintray.sh ├── github.sh ├── global_data.sh └── utils.sh ├── publish_experimental_release.sh ├── publish_release.sh └── tests └── multiwerf_with_coverage.sh /.github/workflows/experimental_release_publisher.yml: -------------------------------------------------------------------------------- 1 | name: Experimental release publisher 2 | on: 3 | pull_request: 4 | types: [labeled] 5 | push: 6 | branches: 7 | - experimental 8 | env: 9 | GO111MODULE: on 10 | jobs: 11 | stub: 12 | name: Greeting 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Greeting 16 | run: echo "This job is used to prevent the workflow to fail when all other jobs are skipped." 17 | unlabel: 18 | name: Unlabel 19 | runs-on: ubuntu-latest 20 | if: github.event_name == 'pull_request' && github.event.label.name == 'release experimental' 21 | steps: 22 | - uses: actions/github-script@0.3.0 23 | with: 24 | github-token: ${{secrets.GITHUB_TOKEN}} 25 | script: | 26 | github.issues.removeLabel({...context.issue, name: '${{github.event.label.name}}'}) 27 | 28 | publish_release: 29 | name: Publish multiwerf experimental release 30 | runs-on: ubuntu-latest 31 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.label.name == 'release experimental') 32 | steps: 33 | - name: Set up Go 34 | uses: actions/setup-go@v2 35 | with: 36 | go-version: 1.17 37 | 38 | - name: Checkout code 39 | uses: actions/checkout@v1 40 | - name: Publish 41 | run: | 42 | echo DISABLED ./scripts/publish_experimental_release.sh 43 | shell: bash 44 | env: 45 | PUBLISH_BINTRAY_AUTH: ${{ secrets.PUBLISH_BINTRAY_AUTH }} 46 | PUBLISH_GITHUB_TOKEN: ${{ secrets.PUBLISH_GITHUB_TOKEN }} 47 | -------------------------------------------------------------------------------- /.github/workflows/release_publisher.yml: -------------------------------------------------------------------------------- 1 | name: Release publisher 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | env: 7 | GO111MODULE: on 8 | 9 | jobs: 10 | publish_release: 11 | name: Publish multiwerf release 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: 1.17 19 | 20 | - name: Checkout code 21 | uses: actions/checkout@v1 22 | - name: Publish 23 | run: | 24 | echo DISABLED ./scripts/publish_release.sh --tag ${GITHUB_REF#refs/tags/} 25 | shell: bash 26 | env: 27 | PUBLISH_BINTRAY_AUTH: ${{ secrets.PUBLISH_BINTRAY_AUTH }} 28 | PUBLISH_GITHUB_TOKEN: ${{ secrets.PUBLISH_GITHUB_TOKEN }} 29 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | schedule: 7 | - cron: '0 0 * * *' 8 | repository_dispatch: 9 | types: [master-tests] 10 | env: 11 | GO111MODULE: on 12 | 13 | jobs: 14 | 15 | tests: 16 | if: "false" # Ignore step because there is no test data previously stored in bintray 17 | name: Go Test (with coverage) 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: [ubuntu-latest, macOS-latest, windows-latest] 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | 25 | - name: Set up Go 26 | uses: actions/setup-go@v2 27 | with: 28 | go-version: 1.17 29 | 30 | - name: Checkout code 31 | uses: actions/checkout@v1 32 | 33 | - name: Download modules 34 | run: go mod download 35 | shell: bash 36 | 37 | - name: Prepare environment 38 | run: | 39 | export MULTIWERF_TEST_COVERAGE_DIR=$GITHUB_WORKSPACE/tests_coverage/${{ matrix.os }} 40 | mkdir -p $MULTIWERF_TEST_COVERAGE_DIR 41 | echo "MULTIWERF_TEST_COVERAGE_DIR=$MULTIWERF_TEST_COVERAGE_DIR" >> $GITHUB_ENV 42 | 43 | ./scripts/tests/multiwerf_with_coverage.sh 44 | shell: bash 45 | 46 | - name: Run tests (!windows-latest) 47 | run: MULTIWERF_TEST_BINARY_PATH=$GITHUB_WORKSPACE/bin/tests/multiwerf_with_coverage go test ./... 48 | shell: bash 49 | if: matrix.os != 'windows-latest' 50 | 51 | - name: Run tests (windows-latest) 52 | run: MULTIWERF_TEST_BINARY_PATH=$GITHUB_WORKSPACE/bin/tests/multiwerf_with_coverage.exe go test ./... 53 | shell: bash 54 | if: matrix.os == 'windows-latest' 55 | 56 | - name: Upload coverage artifact 57 | uses: actions/upload-artifact@master 58 | with: 59 | name: tests_coverage 60 | path: tests_coverage 61 | 62 | use_command: 63 | name: Use Command 64 | strategy: 65 | fail-fast: false 66 | matrix: 67 | os: [ubuntu-latest, macOS-latest, windows-latest] 68 | shell: [sh, bash, cmd, powershell] 69 | exclude: 70 | - os: ubuntu-latest 71 | shell: cmd 72 | - os: ubuntu-latest 73 | shell: powershell 74 | - os: macOS-latest 75 | shell: cmd 76 | - os: macOS-latest 77 | shell: powershell 78 | - os: windows-latest 79 | shell: sh 80 | runs-on: ${{ matrix.os }} 81 | steps: 82 | 83 | - name: Set up Go 84 | uses: actions/setup-go@v2 85 | with: 86 | go-version: 1.17 87 | 88 | - name: Checkout code 89 | uses: actions/checkout@v1 90 | 91 | - name: Build multiwerf 92 | run: | 93 | export GOBIN=$GITHUB_WORKSPACE 94 | echo "$GOBIN" >> $GITHUB_PATH 95 | ./go-build.sh 96 | shell: bash 97 | 98 | - name: Setup environment 99 | run: | 100 | export MULTIWERF_STORAGE_DIR=$PWD/.multiwerf 101 | mkdir $MULTIWERF_STORAGE_DIR 102 | echo "MULTIWERF_STORAGE_DIR=$MULTIWERF_STORAGE_DIR" >> $GITHUB_ENV 103 | shell: bash 104 | if: matrix.os == 'windows-latest' 105 | 106 | - name: Install werf (sh) 107 | run: | 108 | . $(multiwerf use 1.0 stable --as-file --self-update=no --shell default) 109 | werf 110 | shell: sh 111 | if: matrix.shell == 'sh' 112 | 113 | - name: Install werf (bash) 114 | run: | 115 | . $(multiwerf use 1.0 stable --as-file --self-update=no --shell default) 116 | werf 117 | shell: bash 118 | if: matrix.shell == 'bash' 119 | 120 | - name: Install werf (cmd) 121 | run: | 122 | FOR /F "tokens=*" %%g IN ('multiwerf use 1.0 stable --as-file --self-update=no --shell cmdexe') do (SET WERF_USE_SCRIPT_PATH=%%g) 123 | %WERF_USE_SCRIPT_PATH% 124 | werf 125 | shell: cmd 126 | if: matrix.shell == 'cmd' 127 | 128 | - name: Install werf (powershell) 129 | run: | 130 | Invoke-Expression -Command "multiwerf use 1.0 stable --as-file --self-update=no --shell powershell" | Out-String -OutVariable WERF_USE_SCRIPT_PATH 131 | . $WERF_USE_SCRIPT_PATH.Trim() 132 | werf 133 | shell: powershell 134 | if: matrix.shell == 'powershell' 135 | 136 | - name: Self-update in the background (sh) 137 | run: | 138 | . $(multiwerf use 1.0 stable --as-file --self-update=yes --try-trdl=no --shell default) 139 | old_multiwerf_version=$(multiwerf version) 140 | 141 | werf 142 | 143 | ps aux | grep '[m]ultiwerf' 144 | while ps aux | grep '[m]ultiwerf' 145 | do 146 | echo "sleep for 1 second" 147 | sleep 1 148 | done 149 | 150 | current_multiwerf_version=$(multiwerf version) 151 | echo "old: $old_multiwerf_version, current: $current_multiwerf_version" 152 | [ "$old_multiwerf_version" != "$current_multiwerf_version" ] || exit 1 153 | shell: sh 154 | if: matrix.shell == 'sh' 155 | 156 | - name: Self-update in the background (bash) 157 | run: | 158 | . $(multiwerf use 1.0 stable --as-file --self-update=yes --try-trdl=no --shell default) 159 | old_multiwerf_version=$(multiwerf version) 160 | 161 | werf 162 | 163 | ps aux | grep '[m]ultiwerf' 164 | while ps aux | grep '[m]ultiwerf' 165 | do 166 | echo "sleep for 1 second" 167 | sleep 1 168 | done 169 | 170 | current_multiwerf_version=$(multiwerf version) 171 | echo "old: $old_multiwerf_version, current: $current_multiwerf_version" 172 | [ "$old_multiwerf_version" != "$current_multiwerf_version" ] || exit 1 173 | shell: bash 174 | if: matrix.shell == 'bash' && matrix.os != 'windows-latest' 175 | 176 | - name: Self-update in the background (bash, windows-latest) 177 | run: | 178 | . $(multiwerf use 1.0 stable --as-file --self-update=yes --try-trdl=no --shell default) 179 | old_multiwerf_version=$(multiwerf version) 180 | 181 | werf 182 | 183 | sleep 10 184 | 185 | current_multiwerf_version=$(multiwerf version) 186 | echo "old: $old_multiwerf_version, current: $current_multiwerf_version" 187 | [ "$old_multiwerf_version" != "$current_multiwerf_version" ] || exit 1 188 | shell: bash 189 | if: matrix.shell == 'bash' && matrix.os == 'windows-latest' 190 | 191 | - name: Self-update in the background (cmd) 192 | run: | 193 | FOR /F "tokens=*" %%g IN ('multiwerf use 1.0 stable --as-file --self-update=yes --try-trdl=no --shell cmdexe') do (SET WERF_USE_SCRIPT_PATH=%%g) 194 | %WERF_USE_SCRIPT_PATH% 195 | FOR /F "tokens=*" %%g IN ('multiwerf version') do (SET old_multiwerf_version=%%g) 196 | 197 | werf 198 | 199 | timeout 10 200 | 201 | FOR /F "tokens=*" %%g IN ('multiwerf version') do (SET current_multiwerf_version=%%g) 202 | echo "old: %old_multiwerf_version%, current: %current_multiwerf_version%" 203 | if "%old_multiwerf_version%" != "%current_multiwerf_version%" (exit 1) 204 | shell: cmd 205 | if: matrix.shell == 'cmd' 206 | 207 | - name: Self-update in the background (powershell) 208 | run: | 209 | Invoke-Expression -Command "multiwerf use 1.0 stable --as-file --self-update=yes --try-trdl=no --shell powershell" | Out-String -OutVariable WERF_USE_SCRIPT_PATH 210 | . $WERF_USE_SCRIPT_PATH.Trim() 211 | Invoke-Expression -Command "multiwerf version" | Out-String -OutVariable old_multiwerf_version 212 | 213 | werf 214 | 215 | Start-Sleep -s 10 216 | 217 | Invoke-Expression -Command "multiwerf version" | Out-String -OutVariable current_multiwerf_version 218 | 219 | echo "old: $old_multiwerf_version" 220 | echo "current: $current_multiwerf_version" 221 | if ("$old_multiwerf_version" -eq "$current_multiwerf_version") { exit 1 } 222 | shell: powershell 223 | if: matrix.shell == 'powershell' 224 | 225 | upload_coverage: 226 | name: Upload coverage 227 | needs: 228 | - tests 229 | - use_command 230 | runs-on: ubuntu-latest 231 | steps: 232 | 233 | - name: Checkout code 234 | uses: actions/checkout@v1 235 | 236 | - name: Download coverage artifact 237 | uses: actions/download-artifact@master 238 | with: 239 | name: tests_coverage 240 | path: tests_coverage 241 | 242 | - name: Prepare environment 243 | run: | 244 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 245 | chmod +x ./cc-test-reporter 246 | 247 | go build github.com/wadey/gocovmerge 248 | chmod +x ./gocovmerge 249 | 250 | echo "MULTIWERF_TEST_COVERAGE_DIR=tests_coverage" >> $GITHUB_ENV 251 | 252 | # FIXME: determine problems with coverage records and remove seds 253 | - name: Prepare coverage file 254 | run: | 255 | find $MULTIWERF_TEST_COVERAGE_DIR -type f -exec \ 256 | sed -i -e "s|/home/runner/work/multiwerf/multiwerf|github.com/werf/multiwerf|g" {} + 257 | 258 | find $MULTIWERF_TEST_COVERAGE_DIR -type f -exec \ 259 | sed -i -e "s|/Users/runner/work/multiwerf/multiwerf|github.com/werf/multiwerf|g" {} + 260 | 261 | find $MULTIWERF_TEST_COVERAGE_DIR -type f -exec \ 262 | sed -i -e 's|D:\\a\\multiwerf\\multiwerf\\cmd\\multiwerf\\main.go|github.com/werf/multiwerf/cmd/multiwerf/main.go|g' {} + 263 | 264 | coverage_files=$(find $MULTIWERF_TEST_COVERAGE_DIR -name '*.out') 265 | ./gocovmerge ${coverage_files[@]} > coverage.out 266 | 267 | - name: Format and upload 268 | run: | 269 | export GIT_BRANCH=${GITHUB_REF:11} 270 | export GIT_COMMIT_SHA=$GITHUB_SHA 271 | 272 | ./cc-test-reporter format-coverage \ 273 | -t=gocov \ 274 | -p=github.com/werf/multiwerf/ \ 275 | coverage.out 276 | 277 | ./cc-test-reporter upload-coverage 278 | env: 279 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 280 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | /multiwerf 15 | /release-build 16 | /.idea 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Flant JSC <256@flant.com> 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README-dev.md: -------------------------------------------------------------------------------- 1 | # How to create ordinary release 2 | 3 | Use `scripts/create_release_tag.sh VERSION` script. 4 | 5 | For example to create version `v1.1.8` run following script: `scripts/create_release_tag.sh v1.1.8`. 6 | 7 | Script will ask for release message and push a new git tag, which will be published shortly by the "release publisher" github actions workflow. 8 | 9 | # How to create experimental release 10 | 11 | Merge your branch into `experimental` branch and push, then github actions workflow "experimental releaser" will create a new tag in the form `vYEAR.MONTH.DAY-HOUR.MINUTE.SECOND` (for example `v19.12.25-13.02.39`) and then release will be published by the "release publisher" github actions workflow. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Report Card](https://goreportcard.com/badge/github.com/werf/multiwerf)](https://goreportcard.com/report/github.com/werf/multiwerf) 2 | [![Test coverage](https://api.codeclimate.com/v1/badges/fab032c3d6836d768af4/test_coverage)](https://codeclimate.com/github/werf/multiwerf/test_coverage) 3 | [![Download from Github](https://img.shields.io/github/tag-date/werf/multiwerf.svg?logo=github&label=latest)](https://github.com/werf/multiwerf/releases/latest) 4 | 5 | # ⛔️ DEPRECATED! This project repo is no longer being maintained. Use the new update manager by following [the instructions on the werf site](https://werf.io/installation.html). 6 | 7 | **multiwerf** is a self-updatable [werf](https://github.com/werf/werf) manager with the awareness of release channels, allowed stability levels. multiwerf follows werf [Backward Compatibility Promise](https://github.com/werf/werf#backward-compatibility-promise). 8 | 9 | General usage of multiwerf is managing werf binaries and providing the actual binary for `MAJOR.MINOR` version and `CHANNEL` in the shell session. 10 | 11 | ## Contents 12 | 13 | - [Installation](#installation) 14 | - [Common Usage](#common-usage) 15 | - [Commands](#commands) 16 | - [License](#license) 17 | 18 | ## Installation 19 | 20 | ### Unix shell (sh, bash, zsh) 21 | 22 | ```bash 23 | # add ~/bin into PATH 24 | export PATH=$PATH:$HOME/bin 25 | echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc 26 | 27 | # install multiwerf into ~/bin directory 28 | mkdir -p ~/bin 29 | cd ~/bin 30 | curl -L https://raw.githubusercontent.com/werf/multiwerf/master/get.sh | bash 31 | ``` 32 | 33 | ### Windows 34 | 35 | Choose a release from [GitHub releases](https://github.com/werf/multiwerf/releases) and use one of the following approaches with the chosen binary URL. 36 | 37 | #### PowerShell 38 | 39 | ```shell 40 | $MULTIWERF_BIN_PATH = "C:\ProgramData\multiwerf\bin" 41 | mkdir $MULTIWERF_BIN_PATH 42 | 43 | Invoke-WebRequest -Uri https://storage.yandexcloud.net/multiwerf/targets/releases/v1.4.7/multiwerf-windows-amd64-v1.4.7.exe -OutFile $MULTIWERF_BIN_PATH\multiwerf.exe 44 | 45 | [Environment]::SetEnvironmentVariable( 46 | "Path", 47 | [Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::Machine) + "$MULTIWERF_BIN_PATH", 48 | [EnvironmentVariableTarget]::Machine) 49 | 50 | $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") 51 | ``` 52 | 53 | #### cmd.exe (run as Administrator) 54 | 55 | ```shell 56 | set MULTIWERF_BIN_PATH="C:\ProgramData\multiwerf\bin" 57 | mkdir %MULTIWERF_BIN_PATH% 58 | bitsadmin.exe /transfer "multiwerf" https://storage.yandexcloud.net/multiwerf/targets/releases/v1.4.7/multiwerf-windows-amd64-v1.4.7.exe %MULTIWERF_BIN_PATH%\multiwerf.exe 59 | setx /M PATH "%PATH%;%MULTIWERF_BIN_PATH%" 60 | 61 | # after that open new cmd.exe session and start using multiwerf 62 | ``` 63 | 64 | ## Common Usage 65 | 66 | ### Unix shell (sh, bash, zsh) 67 | 68 | #### Add werf alias to the current shell session 69 | 70 | ```bash 71 | . $(multiwerf use 1.1 stable --as-file) 72 | ``` 73 | 74 | #### Run command on terminal startup 75 | 76 | ```bash 77 | echo '. $(multiwerf use 1.1 stable --as-file)' >> ~/.bashrc 78 | ``` 79 | 80 | #### CI usage tip 81 | 82 | `source` with `Process Substitution` can lead to errors If multiwerf is used in shell scenarios without possibility to enter custom commands after execution, for example, in CI environments. The recommendation is to use `type` to ensure that multiwerf 83 | is exist and executable: 84 | 85 | ```shell 86 | type multiwerf && . $(multiwerf use 1.1 stable --as-file) 87 | ``` 88 | 89 | This command will print a message to stderr in case if multiwerf is not found, so diagnostic in CI environment should be simple. 90 | 91 | ### Windows 92 | 93 | #### PowerShell 94 | 95 | ##### Add werf alias to the current shell session 96 | 97 | ```shell 98 | Invoke-Expression -Command "multiwerf use 1.1 stable --as-file --shell powershell" | Out-String -OutVariable WERF_USE_SCRIPT_PATH 99 | . $WERF_USE_SCRIPT_PATH.Trim() 100 | ``` 101 | 102 | #### cmd.exe 103 | 104 | ##### Add werf alias to the current shell session 105 | 106 | ```shell 107 | FOR /F "tokens=*" %g IN ('multiwerf use 1.1 stable --as-file --shell cmdexe') do (SET WERF_USE_SCRIPT_PATH=%g) 108 | %WERF_USE_SCRIPT_PATH% 109 | ``` 110 | 111 | ## Commands 112 | 113 | - `multiwerf update []`: Perform self-update and download the actual channel werf binary. 114 | 115 | - `multiwerf use []`: Generate the shell script that should be sourced to use the actual channel werf binary in the current shell session based on the local channel mapping. 116 | 117 | - `multiwerf werf-path []`: Print the actual channel werf binary path based on the local channel mapping.. 118 | 119 | - `multiwerf werf-exec [] [...]`: Exec the actual channel werf binary based on the local channel mapping. 120 | 121 | The first positional argument is the version in the form of `MAJOR.MINOR`. `CHANNEL` is one of the following channels: alpha, beta, ea, stable, rock-solid. Read more about it in [Backward Compatibility Promise](https://github.com/werf/werf#backward-compatibility-promise) section. 122 | 123 | multiwerf download werf binary to a directory like `$HOME/.multiwerf/VERSION/`. 124 | For example, the werf version `1.0.1-ea.3` for the user `gitlab-runner` will be stored as: 125 | 126 | ``` 127 | /home/gitlab-runner/.multiwerf 128 | |-- 1.0.1-ea.3 129 | | |-- SHA256SUMS 130 | | |-- SHA256SUMS.sig 131 | | `-- werf-linux-amd64-1.0.1-ea.3 132 | | 133 | ... 134 | ``` 135 | 136 | > `multiwerf update` checks for the latest version of multiwerf and performs self-update if it is needed. This can be disabled with `--self-update=no` flag. 137 | 138 | ## Self-update 139 | 140 | Before downloading the actual channel werf binary multiwerf performs self-update process. If the new version is available multiwerf downloads it and starts the new process with the same environment and arguments. 141 | 142 | `--self-update=no` flag and `MULTIWERF_SELF_UPDATE=no` environment variable are available to turn off self-updates. 143 | 144 | Self-update is disabled if `multiwerf` binary is not owned by user that runs it and if the binary file is not writable by owner. 145 | 146 | ## License 147 | 148 | Apache License 2.0, see [LICENSE](LICENSE) 149 | -------------------------------------------------------------------------------- /cmd/multiwerf/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | 9 | "gopkg.in/alecthomas/kingpin.v2" 10 | 11 | "github.com/werf/multiwerf/pkg/app" 12 | "github.com/werf/multiwerf/pkg/multiwerf" 13 | ) 14 | 15 | var ( 16 | groupHelp = "Selector of a release series. Examples: 1.0, 1.1, 1.2." 17 | groupHintOptions = []string{"1.0", "1.1", "1.2"} 18 | 19 | channels = []string{ 20 | "alpha", 21 | "beta", 22 | "ea", 23 | "stable", 24 | "rock-solid", 25 | } 26 | channelHelp = fmt.Sprintf("The minimum acceptable level of stability. One of: %s.", strings.Join(channels, "|")) 27 | channelEnum = []string{ 28 | "alpha", 29 | "beta", 30 | "ea", 31 | "early-access", 32 | "rc", // legacy 33 | "stable", 34 | "rock-solid", 35 | } 36 | 37 | updateDefault = "yes" 38 | updateHelp = "Try to download remote channel mapping and sync channel werf version. To disable set to 'no'." 39 | 40 | selfUpdateDefault = "yes" 41 | selfUpdateHelp = "Perform multiwerf self-update. To disable set to 'no'." 42 | 43 | withGCDefault = "yes" 44 | withGCHelp = "Run GC before update." 45 | 46 | tryTrdlDefault = "yes" 47 | tryTrdlHelp = "Try to use system trdl package manager instad of multiwerf. Multiwerf is DEPRECATED, more info about trdl: https://github.com/werf/trdl. To disable trdl set to 'no'." 48 | 49 | autoInstallTrdlDefault = "on-self-update" 50 | autoInstallTrdlHelp = "Automatically download trdl package manager and install into the system. Multiwerf is DEPRECATED, more info about trdl: https://github.com/werf/trdl. To disable auto download set to 'no'. Multiwerf will auto-download trdl by default unless self-updates are disabled by the --self-update='no' flag. It is possible to enable auto-download of trdl even if self-updates are disabled by setting option to 'yes' explicitly." 51 | 52 | shellDefault = "default" 53 | ) 54 | 55 | func main() { 56 | kpApp := kingpin.New(app.AppName, fmt.Sprintf("%s %s: %s", app.AppName, app.Version, app.AppDescription)) 57 | 58 | app.SetupGlobalSettings(kpApp) 59 | 60 | updateCommand(kpApp) 61 | selfUpdateCommand(kpApp) 62 | useCommand(kpApp) 63 | werfPathCommand(kpApp) 64 | werfExecCommand(kpApp) 65 | werfGCCommand(kpApp) 66 | versionCommand(kpApp) 67 | 68 | command, err := kpApp.Parse(os.Args[1:]) 69 | if err != nil { 70 | kingpin.MustParse(command, err) 71 | os.Exit(1) 72 | } 73 | } 74 | 75 | func getAutoInstallTrdlOption(rawInput string, tryTrdlOption, skipSelfUpdateOption bool) (bool, error) { 76 | switch rawInput { 77 | case "yes": 78 | return tryTrdlOption, nil 79 | case "on-self-update": 80 | return tryTrdlOption && !skipSelfUpdateOption, nil 81 | case "no": 82 | return false, nil 83 | default: 84 | return false, fmt.Errorf("bad --auto-install-trdl=%s option given, expected 'yes', 'no' or 'on-self-update'", rawInput) 85 | } 86 | } 87 | 88 | func getTryTrdlOption(rawInput string) (bool, error) { 89 | switch rawInput { 90 | case "yes": 91 | return true, nil 92 | case "no": 93 | return false, nil 94 | default: 95 | return false, fmt.Errorf("bad --try-trdl=%s option given, expected 'yes' or 'no'", rawInput) 96 | } 97 | } 98 | 99 | func selfUpdateCommand(kpApp *kingpin.Application) { 100 | var ( 101 | updateInBackground bool 102 | outputFile string 103 | ) 104 | 105 | selfUpdateCmd := kpApp. 106 | Command("self-update", "Perform self-update of multiwerf."). 107 | Action(func(c *kingpin.ParseContext) error { 108 | if updateInBackground { 109 | var args []string 110 | for _, arg := range os.Args[1:] { 111 | if arg == "--in-background" || strings.HasPrefix(arg, "--in-background=") { 112 | continue 113 | } 114 | args = append(args, arg) 115 | } 116 | 117 | cmd := exec.Command(os.Args[0], args...) 118 | if err := cmd.Start(); err != nil { 119 | fmt.Printf("command '%s' start failed: %s\n", strings.Join(append(os.Args[:0], args...), " "), err.Error()) 120 | os.Exit(1) 121 | } 122 | 123 | if err := cmd.Process.Release(); err != nil { 124 | fmt.Printf("process release failed: %s\n", err.Error()) 125 | return err 126 | } 127 | 128 | os.Exit(0) 129 | } 130 | 131 | if err := multiwerf.SelfUpdate(multiwerf.SelfUpdateOptions{ 132 | OutputFile: outputFile, 133 | }); err != nil { 134 | fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) 135 | os.Exit(1) 136 | } 137 | 138 | return nil 139 | }) 140 | 141 | selfUpdateCmd.Flag("in-background", "Enable running process in background"). 142 | BoolVar(&updateInBackground) 143 | selfUpdateCmd.Flag("output-file", "Save command output in file"). 144 | StringVar(&outputFile) 145 | } 146 | 147 | func updateCommand(kpApp *kingpin.Application) { 148 | var ( 149 | groupStr string 150 | channelStr string 151 | update string 152 | selfUpdate string 153 | withCache bool 154 | withGC string 155 | updateInBackground bool 156 | updateOutputFile string 157 | tryTrdl string 158 | autoInstallTrdl string 159 | ) 160 | 161 | // multiwerf update 162 | updateCmd := kpApp. 163 | Command("update", "Perform self-update and download the actual channel werf binary."). 164 | Action(func(c *kingpin.ParseContext) error { 165 | channelStr = normalizeChannel(channelStr) 166 | 167 | options := multiwerf.UpdateOptions{ 168 | SkipSelfUpdate: selfUpdate == "no", 169 | WithCache: withCache, 170 | WithGC: withGC == "yes", 171 | TryRemoteChannelMapping: update == "yes", 172 | OutputFile: updateOutputFile, 173 | } 174 | 175 | if value, err := getTryTrdlOption(tryTrdl); err != nil { 176 | return err 177 | } else { 178 | options.TryTrdl = value 179 | } 180 | 181 | if value, err := getAutoInstallTrdlOption(autoInstallTrdl, options.TryTrdl, options.SkipSelfUpdate); err != nil { 182 | return err 183 | } else { 184 | options.AutoInstallTrdl = value 185 | } 186 | 187 | if updateInBackground { 188 | var args []string 189 | for _, arg := range os.Args[1:] { 190 | if arg == "--in-background" || strings.HasPrefix(arg, "--in-background=") { 191 | continue 192 | } 193 | args = append(args, arg) 194 | } 195 | 196 | cmd := exec.Command(os.Args[0], args...) 197 | if err := cmd.Start(); err != nil { 198 | fmt.Printf("command '%s' start failed: %s\n", strings.Join(append(os.Args[:0], args...), " "), err.Error()) 199 | os.Exit(1) 200 | } 201 | 202 | if err := cmd.Process.Release(); err != nil { 203 | fmt.Printf("process release failed: %s\n", err.Error()) 204 | return err 205 | } 206 | 207 | os.Exit(0) 208 | } 209 | 210 | // TODO add special error to exit with 1 and not print error message with kingpin 211 | if err := multiwerf.Update(groupStr, channelStr, options); err != nil { 212 | os.Exit(1) 213 | } 214 | 215 | return nil 216 | }) 217 | updateCmd.Arg("MAJOR.MINOR", groupHelp). 218 | HintOptions(groupHintOptions...). 219 | Required(). 220 | StringVar(&groupStr) 221 | updateCmd.Arg("CHANNEL", channelHelp). 222 | HintOptions(channels...). 223 | Default("stable"). 224 | EnumVar(&channelStr, channelEnum...) 225 | updateCmd.Flag("with-cache", "Cache remote channel mapping between updates."). 226 | BoolVar(&withCache) 227 | updateCmd.Flag("self-update", selfUpdateHelp). 228 | Envar("MULTIWERF_SELF_UPDATE"). 229 | Default(selfUpdateDefault). 230 | StringVar(&selfUpdate) 231 | updateCmd.Flag("try-trdl", tryTrdlHelp). 232 | Envar("MULTIWERF_TRY_TRDL"). 233 | Default(tryTrdlDefault). 234 | StringVar(&tryTrdl) 235 | updateCmd.Flag("auto-install-trdl", autoInstallTrdlHelp). 236 | Envar("MULTIWERF_AUTO_INSTALL_TRDL"). 237 | Default(autoInstallTrdlDefault). 238 | StringVar(&autoInstallTrdl) 239 | updateCmd.Flag("with-gc", withGCHelp). 240 | Envar("MULTIWERF_WITH_GC"). 241 | Default(withGCDefault). 242 | StringVar(&withGC) 243 | updateCmd.Flag("update", updateHelp). 244 | Envar("MULTIWERF_UPDATE"). 245 | Default(updateDefault). 246 | StringVar(&update) 247 | updateCmd.Flag("in-background", "Enable running process in background"). 248 | BoolVar(&updateInBackground) 249 | updateCmd.Flag("output-file", "Save command output in file"). 250 | StringVar(&updateOutputFile) 251 | } 252 | 253 | func useCommand(kpApp *kingpin.Application) { 254 | var ( 255 | groupStr string 256 | channelStr string 257 | update string 258 | selfUpdate string 259 | withGC string 260 | forceRemoteCheck bool 261 | shell string 262 | asFile bool 263 | tryTrdl string 264 | autoInstallTrdl string 265 | ) 266 | 267 | useCmd := kpApp. 268 | Command("use", "Generate the shell script that should be sourced to use the actual channel werf binary in the current shell session based on the local channel mapping."). 269 | Action(func(c *kingpin.ParseContext) error { 270 | channelStr = normalizeChannel(channelStr) 271 | options := multiwerf.UseOptions{ 272 | ForceRemoteCheck: forceRemoteCheck, 273 | AsFile: asFile, 274 | SkipSelfUpdate: selfUpdate == "no", 275 | TryRemoteChannelMapping: update == "yes", 276 | WithGC: withGC == "yes", 277 | } 278 | 279 | if value, err := getTryTrdlOption(tryTrdl); err != nil { 280 | return err 281 | } else { 282 | options.TryTrdl = value 283 | } 284 | 285 | if value, err := getAutoInstallTrdlOption(autoInstallTrdl, options.TryTrdl, options.SkipSelfUpdate); err != nil { 286 | return err 287 | } else { 288 | options.AutoInstallTrdl = value 289 | } 290 | 291 | if err := multiwerf.Use(groupStr, channelStr, shell, options); err != nil { 292 | os.Exit(1) 293 | } 294 | 295 | return nil 296 | }) 297 | useCmd.Arg("MAJOR.MINOR", groupHelp). 298 | HintOptions(groupHintOptions...). 299 | Required(). 300 | StringVar(&groupStr) 301 | useCmd.Arg("CHANNEL", channelHelp). 302 | HintOptions(channels...). 303 | Default("stable"). 304 | EnumVar(&channelStr, channelEnum...) 305 | useCmd.Flag("force-remote-check", "Do not use '--with-cache' option with background multiwerf update command."). 306 | BoolVar(&forceRemoteCheck) 307 | useCmd.Flag("shell", "Set to 'cmdexe', 'powershell' or use the default behaviour that is compatible with any unix shell."). 308 | Default(shellDefault). 309 | EnumVar(&shell, []string{"default", "cmdexe", "powershell"}...) 310 | useCmd.Flag("as-file", "Create the script and print the path for sourcing."). 311 | BoolVar(&asFile) 312 | useCmd.Flag("self-update", selfUpdateHelp). 313 | Envar("MULTIWERF_SELF_UPDATE"). 314 | Default(selfUpdateDefault). 315 | StringVar(&selfUpdate) 316 | useCmd.Flag("try-trdl", tryTrdlHelp). 317 | Envar("MULTIWERF_TRY_TRDL"). 318 | Default(tryTrdlDefault). 319 | StringVar(&tryTrdl) 320 | useCmd.Flag("auto-install-trdl", autoInstallTrdlHelp). 321 | Envar("MULTIWERF_AUTO_INSTALL_TRDL"). 322 | Default(autoInstallTrdlDefault). 323 | StringVar(&autoInstallTrdl) 324 | useCmd.Flag("with-gc", withGCHelp). 325 | Envar("MULTIWERF_WITH_GC"). 326 | Default(withGCDefault). 327 | StringVar(&withGC) 328 | useCmd.Flag("update", updateHelp). 329 | Envar("MULTIWERF_UPDATE"). 330 | Default(updateDefault). 331 | StringVar(&update) 332 | } 333 | 334 | func werfPathCommand(kpApp *kingpin.Application) { 335 | var ( 336 | groupStr string 337 | channelStr string 338 | tryTrdl string 339 | ) 340 | 341 | werfPathCmd := kpApp. 342 | Command("werf-path", "Print the actual channel werf binary path based on the local channel mapping."). 343 | Action(func(c *kingpin.ParseContext) error { 344 | channelStr = normalizeChannel(channelStr) 345 | 346 | tryTrdlOption, err := getTryTrdlOption(tryTrdl) 347 | if err != nil { 348 | return err 349 | } 350 | 351 | if err := multiwerf.WerfPath(groupStr, channelStr, tryTrdlOption); err != nil { 352 | os.Exit(1) 353 | } 354 | return nil 355 | }) 356 | werfPathCmd.Arg("MAJOR.MINOR", groupHelp). 357 | HintOptions(groupHintOptions...). 358 | Required(). 359 | StringVar(&groupStr) 360 | werfPathCmd.Arg("CHANNEL", channelHelp). 361 | HintOptions(channels...). 362 | Default("stable"). 363 | EnumVar(&channelStr, channelEnum...) 364 | werfPathCmd.Flag("try-trdl", tryTrdlHelp). 365 | Envar("MULTIWERF_TRY_TRDL"). 366 | Default(tryTrdlDefault). 367 | StringVar(&tryTrdl) 368 | } 369 | 370 | func werfExecCommand(kpApp *kingpin.Application) { 371 | var ( 372 | groupStr string 373 | channelStr string 374 | werfArgs []string 375 | tryTrdl string 376 | ) 377 | 378 | werfExecCmd := kpApp. 379 | Command("werf-exec", "Exec the actual channel werf binary based on the local channel mapping."). 380 | Action(func(c *kingpin.ParseContext) error { 381 | channelStr = normalizeChannel(channelStr) 382 | 383 | tryTrdlOption, err := getTryTrdlOption(tryTrdl) 384 | if err != nil { 385 | return err 386 | } 387 | 388 | if err := multiwerf.WerfExec(groupStr, channelStr, werfArgs, tryTrdlOption); err != nil { 389 | os.Exit(1) 390 | } 391 | return nil 392 | }) 393 | werfExecCmd.Arg("MAJOR.MINOR", groupHelp). 394 | HintOptions(groupHintOptions...). 395 | Required(). 396 | StringVar(&groupStr) 397 | werfExecCmd.Arg("CHANNEL", channelHelp). 398 | HintOptions(channels...). 399 | Default("stable"). 400 | EnumVar(&channelStr, channelEnum...) 401 | werfExecCmd.Arg("WERF_ARGS", "Pass args to werf binary."). 402 | StringsVar(&werfArgs) 403 | werfExecCmd.Flag("try-trdl", tryTrdlHelp). 404 | Envar("MULTIWERF_TRY_TRDL"). 405 | Default(tryTrdlDefault). 406 | StringVar(&tryTrdl) 407 | } 408 | 409 | func werfGCCommand(kpApp *kingpin.Application) { 410 | kpApp. 411 | Command("gc", "Run garbage collection."). 412 | Action(func(c *kingpin.ParseContext) error { 413 | err := multiwerf.GC() 414 | if err != nil { 415 | _, _ = fmt.Fprintln(os.Stderr, err) 416 | os.Exit(1) 417 | } 418 | return nil 419 | }) 420 | } 421 | 422 | func versionCommand(kpApp *kingpin.Application) *kingpin.CmdClause { 423 | return kpApp.Command("version", "Show version.").Action(func(c *kingpin.ParseContext) error { 424 | fmt.Printf("%s %s\n", app.AppName, app.Version) 425 | return nil 426 | }) 427 | } 428 | 429 | func normalizeChannel(value string) string { 430 | switch value { 431 | case "rc", "early-access": 432 | return "ea" 433 | default: 434 | return value 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /cmd/multiwerf/main_test.go: -------------------------------------------------------------------------------- 1 | // +build integration_coverage 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "strings" 9 | "testing" 10 | 11 | "bou.ke/monkey" 12 | ) 13 | 14 | var exitCode int 15 | 16 | func TestMain(m *testing.M) { 17 | m.Run() 18 | os.Exit(exitCode) 19 | } 20 | 21 | func TestRunMain(t *testing.T) { 22 | // catch os.Exit 23 | fakeOsExit := func(code int) { 24 | exitCode = code 25 | panic(fmt.Sprintf("exit code %d", code)) 26 | } 27 | patch := monkey.Patch(os.Exit, fakeOsExit) 28 | defer patch.Unpatch() 29 | 30 | // catch and ignore fakeOsExit panic 31 | defer func() { 32 | if r := recover(); r != nil { 33 | if strings.HasPrefix(fmt.Sprint(r), "exit code") { 34 | return 35 | } 36 | 37 | panic(r) 38 | } 39 | }() 40 | 41 | // ignore test options 42 | oldArgs := os.Args 43 | var newArgs []string 44 | for _, arg := range os.Args { 45 | if strings.HasPrefix(arg, "-test.") { 46 | continue 47 | } 48 | newArgs = append(newArgs, arg) 49 | } 50 | os.Args = newArgs 51 | defer func() { 52 | os.Args = oldArgs 53 | }() 54 | 55 | // ignore test summary 56 | // PASS 57 | // coverage: 6.6% of statements in ./... 58 | defer discardStdOut() 59 | 60 | main() 61 | } 62 | 63 | func discardStdOut() { 64 | w, err := os.Open(os.DevNull) 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | os.Stdout = w 70 | } 71 | -------------------------------------------------------------------------------- /get.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Invoking this script: 4 | # 5 | # curl https://raw.githubusercontent.com/flant/multiwerf/master/get.sh | sh 6 | # 7 | # Actions: 8 | # - check os and arch 9 | # - detect curl or wget 10 | # - check bintary for latest available release of multiwerf binary 11 | # - download multiwerf, check SHA256 12 | # - making sure multiwerf is executable 13 | # - print brief usage 14 | 15 | set -e -o nounset 16 | 17 | http_client="curl" 18 | 19 | detect_downloader() { 20 | if tmp=$(curl --version 2>&1 >/dev/null) ; then return ; fi 21 | if tmp=$(wget --help 2>&1 >/dev/null) ; then http_client="wget" ; return ; fi 22 | echo "Cannot detect curl or wget. Install one of them and run again." 23 | exit 2 24 | } 25 | 26 | # download_file URL OUTPUT_FILE_PATH 27 | download_file() { 28 | if [ "${http_client}" = "curl" ] ; then 29 | if ! curl -Ls "$1" -o "$2" ; then 30 | echo "curl error for file $1" 31 | return 1 32 | fi 33 | return 34 | fi 35 | if [ "${http_client}" = "wget" ] ; then 36 | if ! wget -q -O "$2" "$1" ; then 37 | echo "wget error for file $1" 38 | return 1 39 | fi 40 | fi 41 | } 42 | 43 | # get_location_header URL 44 | get_location_header() { 45 | if [ "${http_client}" = "curl" ] ; then 46 | if ! curl -s "$1" -w "%{redirect_url}" ; then 47 | echo "curl error for $1" 48 | return 1 49 | fi 50 | return 51 | fi 52 | if [ "${http_client}" = "wget" ] ; then 53 | if ! wget -S -q -O - "$1" 2>&1 | grep -m 1 'Location:' | tr -d '\r\n' ; then 54 | echo "wget error for $1" 55 | return 1 56 | fi 57 | fi 58 | } 59 | 60 | check_os_arch() { 61 | supported="linux-amd64 linux-arm64 darwin-amd64 darwin-arm64" 62 | 63 | if ! echo "${supported}" | tr ' ' '\n' | grep -q "${OS}-${ARCH}"; then 64 | cat <&2 80 | return 1 81 | fi 82 | 83 | url_decode "${version}" 84 | } 85 | 86 | url_decode() { 87 | echo "$1" | sed 's@+@ @g;s@%@\\x@g' | xargs -0 printf '%b' 88 | } 89 | 90 | # emulate missing option --ignore-missing of sha256sum for alpine and centos 91 | # use shasum on MacOS 92 | sha256check() { 93 | BIN_FILE=$1 94 | SHA_FILE=$2 95 | SHA_SUM="${SHA_FILE}.sum" 96 | 97 | grep "$BIN_FILE" "$SHA_FILE" > "$SHA_SUM" 98 | 99 | sha_cmd="sha256sum" 100 | if [ "$OS" = "darwin" ] ; then 101 | sha_cmd="shasum -a 256" 102 | fi 103 | 104 | if ! $sha_cmd -c "${SHA_SUM}" ; then 105 | rm -f "${SHA_SUM}" "${SHA_FILE}" "${BIN_FILE}" 106 | return 1 107 | fi 108 | 109 | rm -f "${SHA_SUM}" "${SHA_FILE}" 110 | } 111 | 112 | PROGRAM="multiwerf" 113 | OS="$(uname | tr '[:upper:]' '[:lower:]')" 114 | ARCH="$(uname -m)" 115 | DL_URL_BASE="https://storage.yandexcloud.net/multiwerf/targets/releases" 116 | 117 | if [ "${ARCH}" = "x86_64" ] ; then 118 | ARCH="amd64" 119 | fi 120 | 121 | check_os_arch 122 | 123 | detect_downloader 124 | 125 | VERSION="latest" 126 | MULTIWERF_BIN_NAME="multiwerf-${OS}-${ARCH}-${VERSION}" 127 | 128 | echo "Downloading ${MULTIWERF_BIN_NAME}..." 129 | if ! download_file "${DL_URL_BASE}/${VERSION}/${MULTIWERF_BIN_NAME}" "${MULTIWERF_BIN_NAME}" 130 | then 131 | exit 2 132 | fi 133 | 134 | # check hash 135 | echo "Checking hash sum..." 136 | if ! download_file "${DL_URL_BASE}/${VERSION}/SHA256SUMS" "${PROGRAM}.sha256sums" 137 | then 138 | exit 2 139 | fi 140 | 141 | if ! sha256check "${MULTIWERF_BIN_NAME}" "${PROGRAM}.sha256sums" 142 | then 143 | echo "${MULTIWERF_BIN_NAME} sha256 hash is not verified. Please download and check hash manually." 144 | exit 1 145 | fi 146 | 147 | mv "${MULTIWERF_BIN_NAME}" "${PROGRAM}" 148 | chmod +x "${PROGRAM}" 149 | 150 | cat <