├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CONTRIBUTING.md
├── Dockerfile.build
├── ISSUE_TEMPLATE.md
├── LICENSE
├── Makefile
├── README.md
├── bash_completion
├── dvm-helper
├── build
├── checksum
│ └── checksum.go
├── dockerversion
│ ├── dockerversion.go
│ ├── dockerversion.nix.go
│ ├── dockerversion_386.go
│ ├── dockerversion_amd64.go
│ ├── dockerversion_arm64.go
│ ├── dockerversion_darwin.go
│ ├── dockerversion_linux.go
│ ├── dockerversion_test.go
│ ├── dockerversion_windows.go
│ ├── edge_version.go
│ ├── edge_version_test.go
│ ├── query.go
│ └── testdata
│ │ └── edge_releases.html
├── dvm-helper.386.go
├── dvm-helper.amd64.go
├── dvm-helper.arm64.go
├── dvm-helper.darwin.go
├── dvm-helper.go
├── dvm-helper.linux.go
├── dvm-helper.nix.go
├── dvm-helper.windows.go
├── dvm-helper_test.go
├── internal
│ ├── config
│ │ └── config.go
│ ├── downloader
│ │ └── downloader.go
│ └── test
│ │ └── testdata.go
├── path.go
├── testdata
│ └── github-docker-releases.json
├── url
│ └── url.go
└── util.go
├── dvm.cmd
├── dvm.ps1
├── dvm.sh
├── go.mod
├── go.sum
├── install.ps1
├── install.sh
└── script
├── deploy-gh-pages.sh
└── travis.deploy.pem.enc
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on: push
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v3
10 | - name: Install Go
11 | uses: actions/setup-go@v3
12 | with:
13 | go-version: 1.18
14 | - name: Build
15 | run: go build -v ./...
16 | - name: Test
17 | run: go test ./...
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 | _deploy
10 |
11 | # Architecture specific extensions/prefixes
12 | *.[568vq]
13 | [568vq].out
14 |
15 | *.cgo1.go
16 | *.cgo2.c
17 | _cgo_defun.c
18 | _cgo_gotypes.go
19 | _cgo_export.*
20 |
21 | _testmain.go
22 |
23 | *.exe
24 | *.test
25 | *.prof
26 |
27 | # Temporary files
28 | .tmp
29 | .DS_Store
30 |
31 | # Downloads
32 | bin
33 | dvm-helper/dvm-helper
34 |
35 | # Aliases
36 | alias
37 | *.pem
38 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | 1. Clone this repository
4 |
5 | ```
6 | go get -d github.com/howtowhale/dvm
7 | cd $GOPATH/src/github.com/howtowhale/dvm
8 | ```
9 |
10 | 1. Add your fork as a remote:
11 |
12 | ```
13 | git remote add fork git@github.com:USERNAME/dvm.git
14 | ```
15 |
16 | 1. Build `dvm`:
17 |
18 | ```
19 | make
20 | ```
21 |
22 | 1. Run tests:
23 |
24 | ```
25 | make test
26 | ```
27 |
28 | 1. Try out your local build:
29 |
30 | ```
31 | source ./dvm.sh
32 | dvm --version
33 | ```
34 |
--------------------------------------------------------------------------------
/Dockerfile.build:
--------------------------------------------------------------------------------
1 | FROM golang:1.18
2 |
3 | ADD . $GOPATH/src/github.com/howtowhale/dvm/
4 | WORKDIR $GOPATH/src/github.com/howtowhale/dvm/
5 |
6 | RUN make get-deps
7 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | If you are reporting a bug, please use the template below:
2 |
3 | ### What didn't work?
4 | Run the command(s) that failed (or did not behave properly) again with the `--debug` flag and include the output.
5 |
6 | ```
7 | $ dvm --debug [REPLACE WITH YOUR COMMANDS]
8 | [PASTE YOUR OUTPUT HERE]
9 | ```
10 |
11 | ### What should it have done?
12 | Let us know what you expected the command above to do.
13 |
14 | ### Helpful Context
15 | If you can provide the following info, it will help us figure things out more quickly! :smile:
16 |
17 | What version of dvm are you using?
18 |
19 | ```
20 | $ dvm --version
21 | [PASTE YOUR OUTPUT HERE]
22 | ```
23 |
24 | What is your Operating System?
25 |
26 | What is your shell (bash/powershell/cmd/fish)?
27 |
28 | How did you install dvm (command-line,chocolatey,homebrew)?
29 |
30 | Do you like kittens? :smile_cat:
31 |
--------------------------------------------------------------------------------
/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 {yyyy} {name of copyright owner}
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 |
203 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL := /bin/bash
2 |
3 | COMMIT = $(shell git rev-parse --verify --short HEAD)
4 | VERSION = $(shell git describe --tags --dirty='-dev' 2> /dev/null)
5 | BREW_VERSION = $(shell git describe --tags --abbrev=0 2> /dev/null)
6 | PERMALINK = $(shell if [[ $(VERSION) =~ [^-]*-([^.]+).* ]]; then echo $${BASH_REMATCH[1]}; else echo "latest"; fi)
7 |
8 | GITHUB_ORG = howtowhale
9 | GITHUB_REPO = dvm
10 | PACKAGE = github.com/${GITHUB_ORG}/${GITHUB_REPO}/dvm-helper
11 | UPGRADE_DISABLED = false
12 |
13 | LDFLAGS = -w -X main.dvmCommit=${COMMIT} -X main.dvmVersion=${VERSION} -X main.upgradeDisabled=${UPGRADE_DISABLED}
14 |
15 | GOCMD = go
16 | GOBUILD = $(GOCMD) build -a -tags netgo -ldflags '$(LDFLAGS)'
17 |
18 | BINDIR = bin/dvm/$(VERSION)
19 | GOFILES = dvm-helper/*.go
20 | GOFILES_NOVENDOR = $(shell go list ./... | grep -v /vendor/)
21 |
22 | default: local
23 |
24 | homebrew:
25 | brew bump-formula-pr --strict --url=https://github.com/howtowhale/dvm/archive/$(BREW_VERSION).tar.gz dvm
26 |
27 | validate:
28 | go fmt $(GOFILES_NOVENDOR)
29 | go vet $(GOFILES_NOVENDOR)
30 | -go list ./... | grep -v /vendor/ | xargs -L1 golint --set_exit_status
31 |
32 | test: local
33 | go test -v $(GOFILES_NOVENDOR)
34 | eval "$( ./dvm-helper --bash-completion )"
35 | ./dvm-helper/dvm-helper --version
36 |
37 | cross-build: local linux linux32 darwin windows windows32
38 | cp dvm.sh dvm.ps1 dvm.cmd install.sh install.ps1 README.md LICENSE bash_completion $(BINDIR)/
39 | find $(BINDIR) -maxdepth 1 -name "install.*" -exec sed -i -e 's/$(PERMALINK)/$(VERSION)/g' {} \;
40 | cp -R $(BINDIR) bin/dvm/$(PERMALINK)
41 |
42 | local: $(GOFILES)
43 | CGO_ENABLED=0 $(GOBUILD) -o dvm-helper/dvm-helper $(PACKAGE)
44 |
45 | linux: $(GOFILES)
46 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINDIR)/Linux/x86_64/dvm-helper $(PACKAGE)
47 | cd $(BINDIR)/Linux/x86_64 && shasum -a 256 dvm-helper > dvm-helper.sha256
48 |
49 | linux32: $(GOFILES)
50 | CGO_ENABLED=0 GOOS=linux GOARCH=386 $(GOBUILD) -o $(BINDIR)/Linux/i686/dvm-helper $(PACKAGE)
51 | cd $(BINDIR)/Linux/i686 && shasum -a 256 dvm-helper > dvm-helper.sha256
52 |
53 | darwin: $(GOFILES)
54 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BINDIR)/Darwin/x86_64/dvm-helper $(PACKAGE)
55 | cd $(BINDIR)/Darwin/x86_64 && shasum -a 256 dvm-helper > dvm-helper.sha256
56 |
57 | windows: $(GOFILES)
58 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(BINDIR)/Windows/x86_64/dvm-helper.exe $(PACKAGE)
59 | cd $(BINDIR)/Windows/x86_64 && shasum -a 256 dvm-helper.exe > dvm-helper.exe.sha256
60 |
61 | windows32: $(GOFILES)
62 | CGO_ENABLED=0 GOOS=windows GOARCH=386 $(GOBUILD) -o $(BINDIR)/Windows/i686/dvm-helper.exe $(PACKAGE)
63 | cd $(BINDIR)/Windows/i686 && shasum -a 256 dvm-helper.exe > dvm-helper.exe.sha256
64 |
65 | # To make a release, push a tag to master, e.g. git tag 0.2.0 -a -m ""
66 |
67 | .PHONY: clean deploy
68 |
69 | clean:
70 | -rm -fr bin/*
71 | -rm -fr _deploy/
72 | -rm dvm-helper/dvm-helper
73 | -rm dvm-helper/dvm-helper.exe
74 |
75 | deploy:
76 | VERSION=$(VERSION) PERMALINK=$(PERMALINK) ./script/deploy-gh-pages.sh
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Docker Version Manager
2 |
3 |
howtowhale.github.io/dvm/
4 |
5 | 
6 |
7 | Version management for your Docker client. Escape from this error for a little bit longer:
8 |
9 | ```
10 | Error response from daemon: client and server don't have same version (client : 1.18, server: 1.16)
11 | ```
12 |
13 | # Contributing
14 | See our [Contributing Guide](CONTRIBUTING.md).
15 |
--------------------------------------------------------------------------------
/bash_completion:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # bash completion for Docker Version Manager (DVM)
4 |
5 | __dvm_generate_completion()
6 | {
7 | declare current_word
8 | current_word="${COMP_WORDS[COMP_CWORD]}"
9 | COMPREPLY=($(compgen -W "$1" -- "$current_word"))
10 | return 0
11 | }
12 |
13 | __dvm_commands ()
14 | {
15 | declare current_word
16 | declare command
17 |
18 | current_word="${COMP_WORDS[COMP_CWORD]}"
19 |
20 | COMMANDS='\
21 | help install uninstall use \
22 | alias unalias upgrade \
23 | current list ls list-remote ls-remote \
24 | list-alias ls-alias deactivate unload \
25 | version which'
26 |
27 | if [ ${#COMP_WORDS[@]} == 4 ]; then
28 |
29 | command="${COMP_WORDS[COMP_CWORD-2]}"
30 | case "${command}" in
31 | alias) __dvm_installed_dockers ;;
32 | esac
33 |
34 | else
35 |
36 | case "${current_word}" in
37 | -*) __dvm_options ;;
38 | *) __dvm_generate_completion "$COMMANDS" ;;
39 | esac
40 |
41 | fi
42 | }
43 |
44 | __dvm_options ()
45 | {
46 | OPTIONS=''
47 | __dvm_generate_completion "$OPTIONS"
48 | }
49 |
50 | __dvm_installed_dockers ()
51 | {
52 | __dvm_generate_completion "$(__dvm_ls) $(__dvm_aliases)"
53 | }
54 |
55 | __dvm_ls ()
56 | {
57 | declare installed
58 | installed=""
59 | if [ -d $DVM_DIR/bin/docker ]; then
60 | installed="`cd $DVM_DIR/bin/docker && command ls`"
61 | fi
62 | echo "${installed}"
63 | }
64 |
65 | __dvm_aliases ()
66 | {
67 | declare aliases
68 | aliases=""
69 | if [ -d $DVM_DIR/alias ]; then
70 | aliases="`cd $DVM_DIR/alias && command ls`"
71 | fi
72 | echo "${aliases}"
73 | }
74 |
75 | __dvm_alias ()
76 | {
77 | __dvm_generate_completion "$(__dvm_aliases)"
78 | }
79 |
80 | __dvm ()
81 | {
82 | declare previous_word
83 | previous_word="${COMP_WORDS[COMP_CWORD-1]}"
84 |
85 | case "$previous_word" in
86 | use|ls|list|uninstall) __dvm_installed_dockers ;;
87 | alias|unalias) __dvm_alias ;;
88 | *) __dvm_commands ;;
89 | esac
90 |
91 | return 0
92 | }
93 |
94 | # complete is a bash builtin, but recent versions of ZSH come with a function
95 | # called bashcompinit that will create a complete in ZSH. If the user is in
96 | # ZSH, load and run bashcompinit before calling the complete function.
97 | if [[ -n ${ZSH_VERSION-} ]]; then
98 | autoload -U +X bashcompinit && bashcompinit
99 | fi
100 |
101 | complete -o default -o nospace -F __dvm dvm
102 |
--------------------------------------------------------------------------------
/dvm-helper/build:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # build - builds for the current OS and ARCH
3 | # build all - builds entire matrix
4 |
5 | cd $( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
6 |
7 | get_commit() {
8 | COMMIT=$(git rev-parse --verify --short HEAD)
9 | RETURN_CODE=$?
10 | export COMMIT
11 | return $RETURN_CODE
12 | }
13 |
14 | get_version() {
15 | # Version will be the most recent tag + "-dev" if working tree is dirty
16 | # e.g.: "1.0.1"" or "1.0.1-dev"
17 | VERSION=$(git describe --tags --dirty='-dev' 2> /dev/null)
18 | export VERSION
19 | return 0
20 | }
21 |
22 | build() {
23 | # Detect the current OS and ARCH if not specified
24 | [ -z "$1" ] && local GOOS=$(uname -s | tr '[:upper:]' '[:lower:]') || local GOOS=$1
25 | [ -z "$2" ] && local GOARCH=$([ $(getconf LONG_BIT) == 64 ] && echo amd64 || echo 386) || local GOARCH=$2
26 | [ "$GOOS" == "windows" ] && local FILE_EXT=".exe" || local FILE_EXT=""
27 | local BINARY="dvm-helper-${GOOS}-${GOARCH}${FILE_EXT}"
28 |
29 | GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "${LDFLAGS}" -o bin/$BINARY
30 | $(cd bin && shasum -a 256 $BINARY > $BINARY.sha256)
31 | }
32 |
33 | # Remove previously downloaded dvm-helper binaries
34 | if [ -e 'dvm-helper' ]; then
35 | rm dvm-helper
36 | fi
37 | if [ -e 'dvm-helper.exe' ]; then
38 | rm dvm-helper.exe
39 | fi
40 |
41 | # Use information about git repo to set the binary version and commit
42 | get_commit
43 | get_version
44 |
45 | LDFLAGS="-X main.dvmCommit=${COMMIT} \
46 | -X main.dvmVersion=${VERSION}"
47 |
48 | # Build ALL THE THINGS!
49 | if [ "$1" != "all" ]; then
50 | build
51 | else
52 | build windows amd64
53 | build windows 386
54 | build darwin amd64
55 | build darwin 386
56 | build linux amd64
57 | build linux 386
58 | fi
59 |
--------------------------------------------------------------------------------
/dvm-helper/checksum/checksum.go:
--------------------------------------------------------------------------------
1 | package checksum
2 |
3 | import (
4 | "crypto/sha256"
5 | "fmt"
6 | "io"
7 | "io/ioutil"
8 | "os"
9 | "strings"
10 | )
11 |
12 | // CompareChecksum validates the SHA256 checksum for a binary against its checksum file
13 | func CompareChecksum(filePath string, checksumPath string) (bool, error) {
14 | knownChecksum, err := readChecksum(checksumPath)
15 | if err != nil {
16 | return false, err
17 | }
18 |
19 | checksum, err := calculateChecksum(filePath)
20 | if err != nil {
21 | return false, err
22 | }
23 |
24 | return strings.Compare(knownChecksum, checksum) == 0, nil
25 | }
26 |
27 | func readChecksum(checksumPath string) (string, error) {
28 | contents, err := ioutil.ReadFile(checksumPath)
29 | if err != nil {
30 | return "", err
31 | }
32 | checksum := strings.Split(string(contents), " ")[0]
33 | return checksum, nil
34 | }
35 |
36 | func calculateChecksum(filePath string) (string, error) {
37 | file, err := os.Open(filePath)
38 | if err != nil {
39 | return "", err
40 | }
41 | defer file.Close()
42 |
43 | hash := sha256.New()
44 | if _, err := io.Copy(hash, file); err != nil {
45 | return "", err
46 | }
47 |
48 | checksum := hash.Sum(nil)
49 |
50 | // convert from bytes to ascii hex string
51 | return fmt.Sprintf("%x", checksum), nil
52 | }
53 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/dockerversion.go:
--------------------------------------------------------------------------------
1 | package dockerversion
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "path/filepath"
7 | "sort"
8 | "strings"
9 |
10 | "github.com/Masterminds/semver"
11 | "github.com/howtowhale/dvm/dvm-helper/internal/config"
12 | "github.com/howtowhale/dvm/dvm-helper/internal/downloader"
13 | "github.com/pkg/errors"
14 | )
15 |
16 | const SystemAlias = "system"
17 | const EdgeAlias = "edge"
18 |
19 | type Version struct {
20 | // Since Docker versions aren't valid versions, (it has leading zeroes)
21 | // We must save the original string representation
22 | raw string
23 | semver *semver.Version
24 | alias string
25 | }
26 |
27 | func NewAlias(alias string, value string) Version {
28 | v := Parse(value)
29 | v.alias = alias
30 | return v
31 | }
32 |
33 | func Parse(value string) Version {
34 | v := Version{raw: value}
35 | semver, err := semver.NewVersion(value)
36 | if err == nil {
37 | v.semver = &semver
38 | } else {
39 | v.alias = value
40 | }
41 | return v
42 | }
43 |
44 | func (version Version) buildDownloadURL(mirror string, forcePrerelease bool) (url string, archived bool, checksumed bool, err error) {
45 | var releaseSlug, versionSlug, extSlug string
46 |
47 | var edgeVersion Version
48 | if version.IsEdge() {
49 | edgeVersion, err = findLatestEdgeVersion(mirror)
50 | if err != nil {
51 | return
52 | }
53 | }
54 |
55 | // Docker Store Download
56 | if version.shouldBeInDockerStore() {
57 | archived = true
58 | checksumed = false
59 | extSlug = archiveFileExt
60 | if mirror == "" {
61 | mirror = "download.docker.com"
62 | }
63 | if version.IsEdge() {
64 | releaseSlug = "edge"
65 | versionSlug = edgeVersion.String()
66 | } else if version.IsPrerelease() || forcePrerelease {
67 | releaseSlug = "test"
68 | versionSlug = version.String()
69 | } else {
70 | releaseSlug = "stable"
71 | versionSlug = version.String()
72 | }
73 |
74 | url = fmt.Sprintf("https://%s/%s/static/%s/%s/docker-%s%s",
75 | mirror, mobyOS, releaseSlug, dockerArch, versionSlug, extSlug)
76 | return
77 | } else { // Original Download
78 | archived = version.shouldBeArchived()
79 | checksumed = true
80 | versionSlug = version.String()
81 | if archived {
82 | extSlug = archiveFileExt
83 | } else {
84 | extSlug = binaryFileExt
85 | }
86 | if mirror == "" {
87 | mirror = "docker.com"
88 | }
89 | if version.IsPrerelease() {
90 | releaseSlug = "test"
91 | } else {
92 | releaseSlug = "get"
93 | }
94 |
95 | url = fmt.Sprintf("https://%s.%s/builds/%s/%s/docker-%s%s",
96 | releaseSlug, mirror, dockerOS, dockerArch, versionSlug, extSlug)
97 | return
98 | }
99 | }
100 |
101 | // Download a Docker release.
102 | // version - the desired version.
103 | // mirrorURL - optional alternate download location.
104 | // binaryPath - full path to where the Docker client binary should be saved.
105 | func (version Version) Download(opts config.DvmOptions, binaryPath string) error {
106 | err := version.download(false, opts, binaryPath)
107 | if err != nil && !version.IsPrerelease() && version.shouldBeInDockerStore() {
108 | // Docker initially publishes non-rc version versions to the test location
109 | // and then later republishes to the stable location
110 | // Retry stable versions against test to find "unstable" stable versions. :-)
111 | opts.Logger.Printf("Could not find a stable release for %s, checking for a test release\n", version)
112 | retryErr := version.download(true, opts, binaryPath)
113 | return errors.Wrapf(retryErr, "Attempted to fallback to downloading from the prerelease location after downloading from the stable location failed: %s", err.Error())
114 | }
115 | return err
116 | }
117 |
118 | func (version Version) download(forcePrerelease bool, opts config.DvmOptions, binaryPath string) error {
119 | url, archived, checksumed, err := version.buildDownloadURL(opts.MirrorURL, forcePrerelease)
120 | if err != nil {
121 | return errors.Wrapf(err, "Unable to determine the download URL for %s", version)
122 | }
123 |
124 | opts.Logger.Printf("Checking if %s can be found at %s", version, url)
125 | head, err := http.Head(url)
126 | if err != nil {
127 | return errors.Wrapf(err, "Unable to determine if %s is a valid version", version)
128 | }
129 | if head.StatusCode >= 400 {
130 | return errors.Errorf("Version %s not found (%v) - try `dvm ls-remote` to browse available versions", version, head.StatusCode)
131 | }
132 |
133 | d := downloader.New(opts)
134 | binaryName := filepath.Base(binaryPath)
135 |
136 | if archived {
137 | archivedFile := filepath.Join("docker", binaryName)
138 | if checksumed {
139 | return d.DownloadArchivedFileWithChecksum(url, archivedFile, binaryPath)
140 | }
141 | return d.DownloadArchivedFile(url, archivedFile, binaryPath)
142 | }
143 |
144 | if checksumed {
145 | return d.DownloadFileWithChecksum(url, binaryPath)
146 | }
147 | return d.DownloadFile(url, binaryPath)
148 | }
149 |
150 | func (version Version) shouldBeInDockerStore() bool {
151 | if version.IsEdge() {
152 | return true
153 | }
154 |
155 | dockerStoreCutoff, _ := semver.NewVersion("17.06.0-ce")
156 |
157 | return version.semver != nil && !version.semver.LessThan(dockerStoreCutoff)
158 | }
159 |
160 | func (version Version) shouldBeArchived() bool {
161 | archivedReleaseCutoff, _ := semver.NewVersion("1.11.0-rc1")
162 | return version.semver != nil && !version.semver.LessThan(archivedReleaseCutoff)
163 | }
164 |
165 | func (version Version) IsPrerelease() bool {
166 | if version.semver == nil {
167 | return false
168 | }
169 |
170 | tag := version.semver.Prerelease()
171 |
172 | preTags := []string{"rc", "alpha", "beta"}
173 | for i := 0; i < len(preTags); i++ {
174 | if strings.Contains(tag, preTags[i]) {
175 | return true
176 | }
177 | }
178 |
179 | return false
180 | }
181 |
182 | func (version Version) IsEmpty() bool {
183 | return version.semver == nil
184 | }
185 |
186 | func (version Version) IsAlias() bool {
187 | return version.alias != ""
188 | }
189 |
190 | func (version Version) IsSystem() bool {
191 | return version.alias == SystemAlias
192 | }
193 |
194 | func (version *Version) SetAsSystem() {
195 | version.alias = SystemAlias
196 | }
197 |
198 | func (version Version) IsEdge() bool {
199 | return version.alias == EdgeAlias
200 | }
201 |
202 | func (version *Version) SetAsEdge() {
203 | version.alias = EdgeAlias
204 | }
205 |
206 | func (version Version) String() string {
207 | if version.alias != "" && version.semver != nil {
208 | return fmt.Sprintf("%s (%s)", version.alias, version.formatRaw())
209 | }
210 | return version.formatRaw()
211 | }
212 |
213 | func (version Version) Value() string {
214 | if version.semver == nil {
215 | return ""
216 | }
217 | return version.formatRaw()
218 | }
219 |
220 | // Slug is the path segment under DVM_DIR where the binary is located
221 | func (version Version) Slug() string {
222 | if version.IsSystem() {
223 | return ""
224 | }
225 | if version.IsEdge() {
226 | return version.alias
227 | }
228 | return version.formatRaw()
229 | }
230 |
231 | func (version Version) Name() string {
232 | if version.alias != "" {
233 | return version.alias
234 | }
235 | return version.formatRaw()
236 | }
237 |
238 | func (version Version) formatRaw() string {
239 | value := version.raw
240 | if strings.HasPrefix(strings.ToLower(value), "v") {
241 | value = value[1:]
242 | }
243 | return value
244 | }
245 |
246 | func (version Version) InRange(r string) (bool, error) {
247 | c, err := semver.NewConstraint(r)
248 | if err != nil {
249 | return false, errors.Wrapf(err, "Unable to parse range constraint: %s", r)
250 | }
251 | if version.semver == nil {
252 | return false, nil
253 | }
254 | return c.Matches(*version.semver) == nil, nil
255 | }
256 |
257 | // Compare compares Versions v to o:
258 | // -1 == v is less than o
259 | // 0 == v is equal to o
260 | // 1 == v is greater than o
261 | func (v Version) Compare(o Version) int {
262 | if v.semver != nil && o.semver != nil {
263 | return v.semver.Compare(*o.semver)
264 | }
265 |
266 | return strings.Compare(v.alias, o.alias)
267 | }
268 |
269 | // Equals checks if v is equal to o.
270 | func (v Version) Equals(o Version) bool {
271 | semverMatch := v.Compare(o) == 0
272 | // Enables distinguishing between X.Y.Z and system (X.Y.Z)
273 | systemMatch := v.IsSystem() == o.IsSystem()
274 | aliasmatch := v.alias != "" && v.alias == o.alias
275 | return (semverMatch && systemMatch) || aliasmatch
276 | }
277 |
278 | type Versions []Version
279 |
280 | func (s Versions) Len() int {
281 | return len(s)
282 | }
283 |
284 | func (s Versions) Swap(i, j int) {
285 | s[i], s[j] = s[j], s[i]
286 | }
287 |
288 | func (s Versions) Less(i, j int) bool {
289 | return s[i].Compare(s[j]) == -1
290 | }
291 |
292 | func Sort(versions []Version) {
293 | sort.Sort(Versions(versions))
294 | }
295 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/dockerversion.nix.go:
--------------------------------------------------------------------------------
1 | // +build !windows
2 |
3 | package dockerversion
4 |
5 | const archiveFileExt string = ".tgz"
6 | const binaryFileExt string = ""
7 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/dockerversion_386.go:
--------------------------------------------------------------------------------
1 | package dockerversion
2 |
3 | const dockerArch string = "i386"
4 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/dockerversion_amd64.go:
--------------------------------------------------------------------------------
1 | package dockerversion
2 |
3 | const dockerArch string = "x86_64"
4 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/dockerversion_arm64.go:
--------------------------------------------------------------------------------
1 | package dockerversion
2 |
3 | const dockerArch string = "aarch64"
4 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/dockerversion_darwin.go:
--------------------------------------------------------------------------------
1 | package dockerversion
2 |
3 | const dockerOS string = "Darwin"
4 | const mobyOS string = "mac"
5 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/dockerversion_linux.go:
--------------------------------------------------------------------------------
1 | package dockerversion
2 |
3 | const dockerOS string = "Linux"
4 | const mobyOS string = "linux"
5 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/dockerversion_test.go:
--------------------------------------------------------------------------------
1 | package dockerversion
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "net/http"
7 | "path/filepath"
8 | "testing"
9 |
10 | "github.com/howtowhale/dvm/dvm-helper/internal/config"
11 | "github.com/pkg/errors"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func TestStripLeadingV(t *testing.T) {
16 | v := Parse("v1.0.0")
17 | assert.Equal(t, "1.0.0", v.String(), "Leading v should be stripped from the string representation")
18 | assert.Equal(t, "1.0.0", v.Name(), "Leading v should be stripped from the name")
19 | assert.Equal(t, "1.0.0", v.Value(), "Leading v should be stripped from the version value")
20 | }
21 |
22 | func TestIsPrerelease(t *testing.T) {
23 | var v Version
24 |
25 | v = Parse("17.3.0-ce-rc1")
26 | assert.True(t, v.IsPrerelease(), "%s should be a prerelease", v)
27 |
28 | v = Parse("1.12.4-rc1")
29 | assert.True(t, v.IsPrerelease(), "%s should be a prerelease", v)
30 |
31 | v = Parse("1.12.4-beta.1")
32 | assert.True(t, v.IsPrerelease(), "%s should be a prerelease", v)
33 |
34 | v = Parse("1.12.4-alpha-2")
35 | assert.True(t, v.IsPrerelease(), "%s should be a prerelease", v)
36 |
37 | v = Parse("17.3.0-ce")
38 | assert.False(t, v.IsPrerelease(), "%s should NOT be a prerelease", v)
39 | }
40 |
41 | func TestLeadingZeroInVersion(t *testing.T) {
42 | v := Parse("v17.03.0-ce")
43 |
44 | assert.Equal(t, "17.03.0-ce", v.String(), "Leading zeroes in the version should be preserved")
45 | }
46 |
47 | func TestSystemAlias(t *testing.T) {
48 | v := Parse(SystemAlias)
49 | assert.Empty(t, v.Slug(),
50 | "The system alias should not have a slug")
51 | assert.Equal(t, SystemAlias, v.String(),
52 | "An empty alias should only print the alias")
53 | assert.Equal(t, SystemAlias, v.Name(),
54 | "The name for an aliased version should be its alias")
55 | assert.Equal(t, "", v.Value(),
56 | "The value for an empty aliased version should be empty")
57 | }
58 |
59 | func TestEdgeAlias(t *testing.T) {
60 | v := Parse(EdgeAlias)
61 | assert.Equal(t, EdgeAlias, v.Slug(),
62 | "The slug for the edge version should be 'edge'")
63 | assert.Equal(t, EdgeAlias, v.String(),
64 | "An empty alias should only print the alias")
65 | assert.Equal(t, EdgeAlias, v.Name(),
66 | "The name for an aliased version should be its alias")
67 | assert.Equal(t, "", v.Value(),
68 | "The value for an empty aliased version should be empty")
69 | }
70 |
71 | func TestEdgeAliasWithVersion(t *testing.T) {
72 | v := Parse("17.06.0-ce+02c1d87")
73 | v.SetAsEdge()
74 | assert.Equal(t, EdgeAlias, v.Slug(),
75 | "The slug for the edge version should be 'edge'")
76 | assert.Equal(t, "edge (17.06.0-ce+02c1d87)", v.String(),
77 | "The string representation should include the alias and version")
78 | assert.Equal(t, EdgeAlias, v.Name(),
79 | "The name for an aliased version should be its alias")
80 | assert.Equal(t, "17.06.0-ce+02c1d87", v.Value(),
81 | "The value for a populated alias should be the version")
82 | }
83 |
84 | func TestAlias(t *testing.T) {
85 | v := NewAlias("prod", "1.2.3")
86 | assert.Equal(t, "1.2.3", v.Slug(),
87 | "The slug for an aliased version should be its semver value")
88 | assert.Equal(t, "prod (1.2.3)", v.String(),
89 | "The string representation for an aliased version should include both alias and version")
90 | assert.Equal(t, "prod", v.Name(),
91 | "The name for an aliased version should be its alias")
92 | assert.Equal(t, "1.2.3", v.Value(),
93 | "The value for an aliased version should be its semver value")
94 | }
95 |
96 | func TestSemanticVersion(t *testing.T) {
97 | v := Parse("1.2.3")
98 | assert.Equal(t, "1.2.3", v.Slug(),
99 | "The slug for a a semantic version should be its semver value")
100 | assert.Equal(t, "1.2.3", v.String(),
101 | "The string representation for a semantic version should only include the semver value")
102 | assert.Equal(t, "1.2.3", v.Name(),
103 | "The name for a semantic version should be its semver value")
104 | assert.Equal(t, "1.2.3", v.Value(),
105 | "The value for a semantic version should be its semver value")
106 | }
107 |
108 | func TestSetAsEdge(t *testing.T) {
109 | v := Parse("1.2.3")
110 | v.SetAsEdge()
111 | assert.True(t, v.IsEdge())
112 | }
113 |
114 | func TestSetAsSystem(t *testing.T) {
115 | v := Parse("1.2.3")
116 | v.SetAsSystem()
117 | assert.True(t, v.IsSystem())
118 | }
119 |
120 | func TestVersion_BuildDownloadURL(t *testing.T) {
121 | testcases := map[Version]struct {
122 | wantURL string
123 | wantArchived bool
124 | wantChecksum bool
125 | }{
126 | // original download location, without compression
127 | Parse("1.10.3"): {
128 | wantURL: fmt.Sprintf("https://get.docker.com/builds/%s/%s/docker-1.10.3", dockerOS, dockerArch),
129 | wantArchived: false,
130 | wantChecksum: true,
131 | },
132 |
133 | // original download location, without compression, prerelease
134 | /* test.docker.com has been removed by docker
135 | Parse("1.10.0-rc1"): {
136 | wantURL: fmt.Sprintf("https://test.docker.com/builds/%s/%s/docker-1.10.0-rc1", dockerOS, dockerArch),
137 | wantArchived: false,
138 | wantChecksum: true,
139 | },
140 | */
141 |
142 | // compressed binaries
143 | /* test.docker.com has been removed by docker
144 | Parse("1.11.0-rc1"): {
145 | wantURL: fmt.Sprintf("https://test.docker.com/builds/%s/%s/docker-1.11.0-rc1.tgz", dockerOS, dockerArch),
146 | wantArchived: true,
147 | wantChecksum: true,
148 | },
149 | */
150 |
151 | // original version scheme, prerelease binaries
152 | /* test.docker.com has been removed by docker
153 | Parse("1.13.0-rc1"): {
154 | wantURL: fmt.Sprintf("https://test.docker.com/builds/%s/%s/docker-1.13.0-rc1.tgz", dockerOS, dockerArch),
155 | wantArchived: true,
156 | wantChecksum: true,
157 | },
158 | */
159 |
160 | // yearly notation, original download location, release location
161 | Parse("17.03.0-ce"): {
162 | wantURL: fmt.Sprintf("https://get.docker.com/builds/%s/%s/docker-17.03.0-ce%s", dockerOS, dockerArch, archiveFileExt),
163 | wantArchived: true,
164 | wantChecksum: true,
165 | },
166 |
167 | // docker store download (no more checksums)
168 | Parse("17.06.0-ce"): {
169 | wantURL: fmt.Sprintf("https://download.docker.com/%s/static/stable/%s/docker-17.06.0-ce.tgz", mobyOS, dockerArch),
170 | wantArchived: true,
171 | wantChecksum: false,
172 | },
173 |
174 | // docker store download, prerelease
175 | Parse("17.07.0-ce-rc1"): {
176 | wantURL: fmt.Sprintf("https://download.docker.com/%s/static/test/%s/docker-17.07.0-ce-rc1.tgz", mobyOS, dockerArch),
177 | wantArchived: true,
178 | wantChecksum: false,
179 | },
180 | }
181 |
182 | for version, testcase := range testcases {
183 | t.Run(version.String(), func(t *testing.T) {
184 | gotURL, gotArchived, gotChecksumed, err := version.buildDownloadURL("", false)
185 | if err != nil {
186 | t.Fatal(err)
187 | }
188 |
189 | if testcase.wantURL != gotURL {
190 | t.Fatalf("Expected %s to be downloaded from '%s', but got '%s'", version, testcase.wantURL, gotURL)
191 | }
192 | if testcase.wantArchived != gotArchived {
193 | t.Fatalf("Expected archive for %s to be %v, got %v", version, testcase.wantArchived, gotArchived)
194 | }
195 | if testcase.wantChecksum != gotChecksumed {
196 | t.Fatalf("Expected checksum for %s to be %v, got %v", version, testcase.wantChecksum, gotChecksumed)
197 | }
198 |
199 | response, err := http.DefaultClient.Head(gotURL)
200 | if err != nil {
201 | t.Fatalf("%#v", errors.Wrapf(err, "Unable to download release from %s", gotURL))
202 | }
203 |
204 | if response.StatusCode != 200 {
205 | t.Fatalf("Unexpected status code (%d) when downloading %s", response.StatusCode, gotURL)
206 | }
207 | })
208 | }
209 | }
210 |
211 | func TestVersion_DownloadEdgeRelease(t *testing.T) {
212 | version := Parse("edge")
213 | tempDir, _ := ioutil.TempDir("", "dvmtest")
214 | opts := config.NewDvmOptions()
215 | opts.DvmDir = filepath.Join(tempDir, ".dvm")
216 | destPath := filepath.Join(opts.DvmDir, "docker")
217 |
218 | err := version.Download(opts, destPath)
219 | if err != nil {
220 | t.Fatalf("%#v", err)
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/dockerversion_windows.go:
--------------------------------------------------------------------------------
1 | package dockerversion
2 |
3 | const dockerOS string = "Windows"
4 | const mobyOS string = "win"
5 | const archiveFileExt string = ".zip"
6 | const binaryFileExt string = ".exe"
7 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/edge_version.go:
--------------------------------------------------------------------------------
1 | package dockerversion
2 |
3 | func findLatestEdgeVersion(mirrorURL string) (Version, error) {
4 | results, err := ListVersions(mirrorURL, Edge)
5 | if err != nil {
6 | return Version{}, err
7 | }
8 |
9 | last := len(results) - 1
10 | return results[last], nil
11 | }
12 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/edge_version_test.go:
--------------------------------------------------------------------------------
1 | package dockerversion
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/howtowhale/dvm/dvm-helper/internal/test"
10 | )
11 |
12 | func TestVersion_findLatestEdgeVersion(t *testing.T) {
13 | releaseListing := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
14 | w.Header().Set("Content-Type", "text/html")
15 | fmt.Fprintln(w, test.LoadTestData("edge_releases.html"))
16 | }))
17 |
18 | v, err := findLatestEdgeVersion(releaseListing.URL)
19 | if err != nil {
20 | t.Fatalf("%#v", err)
21 | }
22 |
23 | wantV := "17.06.0-ce"
24 | gotV := v.String()
25 | if wantV != gotV {
26 | t.Fatalf("Expected '%s', got '%s'", wantV, gotV)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/query.go:
--------------------------------------------------------------------------------
1 | package dockerversion
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "net/http"
7 | "net/url"
8 |
9 | "regexp"
10 |
11 | "github.com/pkg/errors"
12 | )
13 |
14 | type ReleaseType string
15 |
16 | const (
17 | Edge ReleaseType = "edge"
18 | Test ReleaseType = "test"
19 | Stable ReleaseType = "stable"
20 | )
21 |
22 | var hrefRegex = regexp.MustCompile(fmt.Sprintf(`href="docker-(.*)\%s"`, archiveFileExt))
23 |
24 | func ListVersions(mirrorURL string, releaseType ReleaseType) ([]Version, error) {
25 | if mirrorURL == "" {
26 | mirrorURL = "https://download.docker.com"
27 | }
28 |
29 | mirror, err := url.Parse(mirrorURL)
30 | if err != nil {
31 | return nil, errors.Wrapf(err, "Unable to parse the mirror URL: %s", mirrorURL)
32 | }
33 |
34 | indexURL := fmt.Sprintf("%s://%s/%s/static/%s/%s", mirror.Scheme, mirror.Host, mobyOS, releaseType, dockerArch)
35 | response, err := http.Get(indexURL)
36 | if err != nil {
37 | return nil, errors.Wrapf(err, "Unable to list %s releases at %s", releaseType, indexURL)
38 | }
39 | defer response.Body.Close()
40 |
41 | b := bytes.Buffer{}
42 | _, err = b.ReadFrom(response.Body)
43 | if err != nil {
44 | }
45 | errors.Wrapf(err, "Unable to read the listing of %s releases at %s", releaseType, indexURL)
46 |
47 | matches := hrefRegex.FindAllStringSubmatch(b.String(), -1)
48 | var results []Version
49 | for _, match := range matches {
50 | version := Parse(match[1])
51 | if version.semver == nil {
52 | continue
53 | }
54 | results = append(results, version)
55 | }
56 |
57 | if len(results) == 0 {
58 | return nil, errors.Errorf("No valid %s versions were found at %s", releaseType, indexURL)
59 | }
60 |
61 | Sort(results)
62 |
63 | return results, nil
64 | }
65 |
--------------------------------------------------------------------------------
/dvm-helper/dockerversion/testdata/edge_releases.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Index of /linux/static/edge/x86_64/
6 |
7 |
8 | Index of /linux/static/edge/x86_64/
9 |
10 | ../
11 | docker-17.03.0-ce.tgz 2017-03-01 11:46 27M
12 | docker-17.04.0-ce.tgz 2017-04-06 01:07 26M
13 | docker-17.05.0-ce.tgz 2017-05-05 04:11 28M
14 | docker-17.06.0-ce.tgz 2017-06-28 04:51 29M
15 |
--------------------------------------------------------------------------------
/dvm-helper/dvm-helper.386.go:
--------------------------------------------------------------------------------
1 | //go:build 386
2 | // +build 386
3 |
4 | package main
5 |
6 | const dvmArch string = "i386"
7 |
--------------------------------------------------------------------------------
/dvm-helper/dvm-helper.amd64.go:
--------------------------------------------------------------------------------
1 | //go:build amd64
2 | // +build amd64
3 |
4 | package main
5 |
6 | const dvmArch string = "x86_64"
7 |
--------------------------------------------------------------------------------
/dvm-helper/dvm-helper.arm64.go:
--------------------------------------------------------------------------------
1 | //go:build arm64
2 | // +build arm64
3 |
4 | package main
5 |
6 | const dvmArch string = "aarch64"
7 |
--------------------------------------------------------------------------------
/dvm-helper/dvm-helper.darwin.go:
--------------------------------------------------------------------------------
1 | //go:build darwin
2 | // +build darwin
3 |
4 | package main
5 |
6 | const dvmOS string = "Darwin"
7 |
--------------------------------------------------------------------------------
/dvm-helper/dvm-helper.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 | neturl "net/url"
9 | "os"
10 | "os/exec"
11 | "path/filepath"
12 | "regexp"
13 | "strings"
14 |
15 | "github.com/Masterminds/semver"
16 | "github.com/codegangsta/cli"
17 | dockerclient "github.com/docker/docker/client"
18 | "github.com/fatih/color"
19 | "github.com/google/go-github/github"
20 | "github.com/howtowhale/dvm/dvm-helper/dockerversion"
21 | "github.com/howtowhale/dvm/dvm-helper/internal/config"
22 | "github.com/howtowhale/dvm/dvm-helper/url"
23 | "github.com/pkg/errors"
24 | "github.com/ryanuber/go-glob"
25 | "golang.org/x/oauth2"
26 | )
27 |
28 | // These are global command line variables
29 | var opts = config.NewDvmOptions()
30 |
31 | // This is a nasty global state flag that we flip. Bad Carolyn.
32 | var useAfterInstall bool
33 |
34 | // These are set during the build
35 | var dvmVersion string
36 | var dvmCommit string
37 | var upgradeDisabled string // Allow package managers like homebrew to disable in-place upgrades
38 | var githubUrlOverride string
39 |
40 | const (
41 | retCodeInvalidArgument = 127
42 | retCodeInvalidOperation = 3
43 | retCodeRuntimeError = 1
44 | versionEnvVar = "DOCKER_VERSION"
45 | )
46 |
47 | func main() {
48 | app := makeCliApp()
49 | app.Run(os.Args)
50 | }
51 |
52 | func makeCliApp() *cli.App {
53 | app := cli.NewApp()
54 | app.Name = "Docker Version Manager"
55 | app.Usage = "Manage multiple versions of the Docker client"
56 | app.Version = fmt.Sprintf("%s (%s)", dvmVersion, dvmCommit)
57 | app.EnableBashCompletion = true
58 | app.Flags = []cli.Flag{
59 | cli.StringFlag{Name: "github-token", EnvVar: "GITHUB_TOKEN", Usage: "Increase the github api rate limit by specifying your github personal access token."},
60 | cli.StringFlag{Name: "dvm-dir", EnvVar: "DVM_DIR", Usage: "Specify an alternate DVM home directory, defaults to ~/.dvm."},
61 | cli.StringFlag{Name: "shell", EnvVar: "SHELL", Usage: "Specify the shell format in which environment variables should be output, e.g. powershell, cmd or sh/bash. Defaults to sh/bash."},
62 | cli.BoolFlag{Name: "debug", Usage: "Print additional debug information."},
63 | cli.BoolFlag{Name: "silent", EnvVar: "DVM_SILENT", Usage: "Suppress output. Errors will still be displayed."},
64 | }
65 | app.Commands = []cli.Command{
66 | {
67 | Name: "detect",
68 | Usage: "Detect the appropriate Docker client version",
69 | Action: func(c *cli.Context) error {
70 | setGlobalVars(c)
71 |
72 | writeDebug("dvm detect")
73 | detect()
74 | return nil
75 | },
76 | },
77 | {
78 | Name: "install",
79 | Aliases: []string{"i"},
80 | Usage: "dvm install [], dvm install edge\n\tInstall a Docker version, using $DOCKER_VERSION if the version is not specified.",
81 | Flags: []cli.Flag{
82 | cli.StringFlag{Name: "mirror-url", EnvVar: "DVM_MIRROR_URL", Usage: "Specify an alternate URL from which to download the Docker client. Defaults to https://get.docker.com/builds"},
83 | },
84 | Action: func(c *cli.Context) error {
85 | setGlobalVars(c)
86 |
87 | value := c.Args().First()
88 | if value == "" {
89 | value = getDockerVersionVar()
90 |
91 | if value == "" {
92 | die("The install command requires that a version is specified or the DOCKER_VERSION environment variable is set.", nil, retCodeInvalidArgument)
93 | }
94 | }
95 |
96 | writeDebug("dvm install %s", value)
97 | install(dockerversion.Parse(value))
98 | return nil
99 | },
100 | },
101 | {
102 | Name: "uninstall",
103 | Usage: "dvm uninstall \n\tUninstall a Docker version.",
104 | Action: func(c *cli.Context) error {
105 | setGlobalVars(c)
106 |
107 | value := c.Args().First()
108 | if value == "" {
109 | die("The uninstall command requires that a version is specified.", nil, retCodeInvalidArgument)
110 | }
111 |
112 | writeDebug("dvm uninstall %s", value)
113 | uninstall(dockerversion.Parse(value))
114 | return nil
115 | },
116 | },
117 | {
118 | Name: "use",
119 | Usage: "dvm use [], dvm use system, dvm use edge\n\tUse a Docker version, using $DOCKER_VERSION if the version is not specified.",
120 | Flags: []cli.Flag{
121 | cli.StringFlag{Name: "mirror-url", EnvVar: "DVM_MIRROR_URL", Usage: "Specify an alternate URL from which to download the Docker client. Defaults to https://get.docker.com/builds"},
122 | cli.BoolFlag{Name: "nocheck", EnvVar: "DVM_NOCHECK", Usage: "Do not check if version exists (use with caution)."},
123 | },
124 | Action: func(c *cli.Context) error {
125 | setGlobalVars(c)
126 |
127 | value := c.Args().First()
128 | if value == "" {
129 | value = getDockerVersionVar()
130 |
131 | if value == "" {
132 | die("The use command requires that a version is specified or the DOCKER_VERSION environment variable is set.", nil, retCodeInvalidOperation)
133 | }
134 | }
135 |
136 | writeDebug("dvm use %s", value)
137 | use(dockerversion.Parse(value))
138 | return nil
139 | },
140 | },
141 | {
142 | Name: "deactivate",
143 | Usage: "dvm deactivate\n\tUndo the effects of `dvm` on current shell.",
144 | Action: func(c *cli.Context) error {
145 | setGlobalVars(c)
146 |
147 | writeDebug("dvm deactivate")
148 | deactivate()
149 | return nil
150 | },
151 | },
152 | {
153 | Name: "current",
154 | Usage: "dvm current\n\tPrint the current Docker version.",
155 | Action: func(c *cli.Context) error {
156 | setGlobalVars(c)
157 |
158 | writeDebug("dvm current")
159 | current()
160 | return nil
161 | },
162 | },
163 | {
164 | Name: "which",
165 | Usage: "dvm which\n\tPrint the path to the current Docker version.",
166 | Action: func(c *cli.Context) error {
167 | setGlobalVars(c)
168 |
169 | writeDebug("dvm which")
170 | which()
171 | return nil
172 | },
173 | },
174 | {
175 | Name: "alias",
176 | Usage: "dvm alias \n\tCreate an alias to a Docker version.",
177 | Action: func(c *cli.Context) error {
178 | setGlobalVars(c)
179 |
180 | name := c.Args().Get(0)
181 | value := c.Args().Get(1)
182 | if name == "" || value == "" {
183 | die("The alias command requires both an alias name and a version.", nil, retCodeInvalidArgument)
184 | }
185 |
186 | writeDebug("dvm alias %s %s", name, value)
187 | alias(name, value)
188 | return nil
189 | },
190 | },
191 | {
192 | Name: "unalias",
193 | Usage: "dvm unalias \n\tRemove a Docker version alias.",
194 | Action: func(c *cli.Context) error {
195 | setGlobalVars(c)
196 |
197 | alias := c.Args().First()
198 | if alias == "" {
199 | die("The unalias command requires an alias alias.", nil, retCodeInvalidArgument)
200 | }
201 |
202 | writeDebug("dvm unalias %s", alias)
203 | unalias(alias)
204 | return nil
205 | },
206 | },
207 | {
208 | Name: "list",
209 | Aliases: []string{"ls"},
210 | Usage: "dvm list []\n\tList installed Docker versions.",
211 | Action: func(c *cli.Context) error {
212 | setGlobalVars(c)
213 |
214 | pattern := c.Args().First()
215 |
216 | writeDebug("dvm list %s", pattern)
217 | list(pattern)
218 | return nil
219 | },
220 | },
221 | {
222 | Name: "list-remote",
223 | Aliases: []string{"ls-remote"},
224 | Usage: "dvm list-remote []\n\tList available Docker versions.",
225 | Flags: []cli.Flag{
226 | cli.BoolFlag{Name: "pre", Usage: "Include pre-release versions"},
227 | },
228 | Action: func(c *cli.Context) error {
229 | setGlobalVars(c)
230 |
231 | pattern := c.Args().First()
232 |
233 | writeDebug("dvm list-remote %s", pattern)
234 | listRemote(pattern)
235 | return nil
236 | },
237 | },
238 | {
239 | Name: "list-alias",
240 | Aliases: []string{"ls-alias"},
241 | Usage: "dvm list-alias\n\tList Docker version aliases.",
242 | Action: func(c *cli.Context) error {
243 | setGlobalVars(c)
244 |
245 | writeDebug("dvm list-alias")
246 | listAlias()
247 | return nil
248 | },
249 | },
250 | }
251 |
252 | if upgradeDisabled != "true" {
253 | app.Commands = append(app.Commands, cli.Command{
254 | Name: "upgrade",
255 | Usage: "dvm upgrade\n\tUpgrade dvm to the latest release.",
256 | Flags: []cli.Flag{
257 | cli.BoolFlag{Name: "check", Usage: "Checks if an newer version of dvm is available, but does not perform the upgrade."},
258 | cli.StringFlag{Name: "version", Usage: "Upgrade to the specified version."},
259 | },
260 | Action: func(c *cli.Context) error {
261 | setGlobalVars(c)
262 | upgrade(c.Bool("check"), c.String("version"))
263 | return nil
264 | },
265 | })
266 | }
267 |
268 | return app
269 | }
270 |
271 | func setGlobalVars(c *cli.Context) {
272 | useAfterInstall = true
273 |
274 | opts.Debug = c.GlobalBool("debug")
275 | if opts.Debug {
276 | opts.Logger = log.New(color.Output, "", log.LstdFlags)
277 | } else {
278 | opts.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
279 | }
280 |
281 | opts.Token = c.GlobalString("github-token")
282 | opts.Shell = c.GlobalString("shell")
283 | validateShellFlag()
284 |
285 | opts.Silent = c.GlobalBool("silent")
286 | opts.MirrorURL = c.String("mirror-url")
287 | opts.IncludePrereleases = c.Bool("pre")
288 |
289 | opts.DvmDir = c.GlobalString("dvm-dir")
290 | if opts.DvmDir == "" {
291 | opts.DvmDir = filepath.Join(getUserHomeDir(), ".dvm")
292 | }
293 | writeDebug("The dvm home directory is: %s", opts.DvmDir)
294 | }
295 |
296 | func detect() {
297 | writeDebug("dvm detect")
298 |
299 | docker, err := dockerclient.NewEnvClient()
300 | if err != nil {
301 | die("Cannot build a docker client from environment variables", err, retCodeRuntimeError)
302 | }
303 |
304 | versionResult, err := docker.ServerVersion(context.Background())
305 | if err != nil {
306 | die("Unable to query docker version", err, retCodeRuntimeError)
307 | }
308 |
309 | writeDebug("Queried /version and got Version: %s", versionResult.Version)
310 | version := dockerversion.Parse(versionResult.Version)
311 |
312 | // Docker versions prior to 1.12 don't return a usable client version
313 | // Lookup the client version from the API version
314 | if version.IsEmpty() {
315 | writeDebug("Attempting to lookup a client version for API version: %s", versionResult.APIVersion)
316 |
317 | // api version -> client version range
318 | oldVersionMap := map[string]string{
319 | "1.23": "1.11.x",
320 | "1.22": "1.10.x",
321 | "1.21": "1.9.x",
322 | "1.20": "1.8.x",
323 | "1.19": "1.7.x",
324 | "1.18": "1.6.x",
325 | }
326 | clientRange, found := oldVersionMap[versionResult.APIVersion]
327 | if !found {
328 | die("Unable to detect the proper client version for Docker API version %s", nil, retCodeRuntimeError, versionResult.APIVersion)
329 | }
330 |
331 | // Find the highest version that satisfies the client version range
332 | availableVersions := getAvailableVersions("", true)
333 | for i := len(availableVersions) - 1; i >= 0; i-- {
334 | v := availableVersions[i]
335 |
336 | if ok, _ := v.InRange(clientRange); ok {
337 | version = v
338 | break
339 | }
340 | }
341 | if version.IsEmpty() {
342 | die("Unable to detect the proper client version for %s", nil, retCodeRuntimeError, clientRange)
343 | }
344 | }
345 | writeDebug("Detected client version: %s", version)
346 |
347 | os.Setenv(versionEnvVar, version.String())
348 | writeEnvironmentVariableScript(versionEnvVar)
349 |
350 | use(version)
351 | }
352 |
353 | func upgrade(checkOnly bool, version string) {
354 | if version != "" && dvmVersion == version {
355 | writeWarning("dvm %s is already installed.", version)
356 | return
357 | }
358 |
359 | if version == "" {
360 | shouldUpgrade, latestVersion := isUpgradeAvailable()
361 | if !shouldUpgrade {
362 | writeInfo("The latest version of dvm is already installed.")
363 | return
364 | }
365 |
366 | version = latestVersion
367 | }
368 |
369 | if checkOnly {
370 | writeInfo("dvm %s is available. Run `dvm upgrade` to install the latest version.", version)
371 | return
372 | }
373 |
374 | writeInfo("Upgrading to dvm %s...", version)
375 | upgradeSelf(version)
376 | }
377 |
378 | func buildDvmReleaseURL(version string, elem ...string) string {
379 | prefix := url.Join("https://howtowhale.github.io/dvm/downloads", version)
380 | suffix := url.Join(elem...)
381 | return url.Join(prefix, suffix)
382 | }
383 |
384 | func current() {
385 | current, err := getCurrentDockerVersion()
386 | if err != nil {
387 | writeWarning("N/A")
388 | } else {
389 | writeInfo(current.String())
390 | }
391 | }
392 |
393 | func list(pattern string) {
394 | pattern += "*"
395 | versions := getInstalledVersions(pattern)
396 | current, _ := getCurrentDockerVersion()
397 |
398 | for _, version := range versions {
399 | if current.String() == version.String() {
400 | color.Green("->\t%s", version)
401 | } else {
402 | writeInfo("\t%s", version)
403 | }
404 | }
405 | }
406 |
407 | func install(version dockerversion.Version) {
408 | versionDir := getVersionDir(version)
409 |
410 | if version.IsEdge() {
411 | // Always install latest of edge build
412 | err := os.RemoveAll(versionDir)
413 | if err != nil {
414 | die("Unable to remove edge version at %s.", err, retCodeRuntimeError, versionDir)
415 | }
416 | }
417 |
418 | if _, err := os.Stat(versionDir); err == nil {
419 | writeWarning("%s is already installed", version)
420 | use(version)
421 | return
422 | }
423 |
424 | writeInfo("Installing %s...", version)
425 |
426 | downloadRelease(version)
427 |
428 | if useAfterInstall {
429 | use(version)
430 | }
431 | }
432 |
433 | func downloadRelease(version dockerversion.Version) {
434 | destPath := filepath.Join(getVersionDir(version), getBinaryName())
435 | err := version.Download(opts, destPath)
436 | if err != nil {
437 | die("", err, retCodeRuntimeError)
438 | }
439 |
440 | writeDebug("Downloaded Docker %s to %s", version, destPath)
441 | }
442 |
443 | func uninstall(version dockerversion.Version) {
444 | current, _ := getCurrentDockerVersion()
445 | if current.Equals(version) {
446 | die("Cannot uninstall the currently active Docker version.", nil, retCodeInvalidOperation)
447 | }
448 |
449 | versionDir := getVersionDir(version)
450 | if _, err := os.Stat(versionDir); os.IsNotExist(err) {
451 | writeWarning("%s is not installed.", version)
452 | return
453 | }
454 |
455 | err := os.RemoveAll(versionDir)
456 | if err != nil {
457 | die("Unable to uninstall Docker version %s located in %s.", err, retCodeRuntimeError, version, versionDir)
458 | }
459 |
460 | writeInfo("Uninstalled Docker %s.", version)
461 | }
462 |
463 | func use(version dockerversion.Version) {
464 | if version.IsAlias() && aliasExists(version.Name()) {
465 | aliasedVersion, _ := ioutil.ReadFile(getAliasPath(version.Name()))
466 | version = dockerversion.NewAlias(version.Name(), string(aliasedVersion))
467 | writeDebug("Using alias: %s", version)
468 | }
469 |
470 | useAfterInstall = false
471 | ensureVersionIsInstalled(version)
472 |
473 | if version.IsSystem() {
474 | version, _ = getSystemDockerVersion()
475 | } else if version.IsEdge() {
476 | version, _ = getEdgeDockerVersion()
477 | }
478 |
479 | removePreviousDockerVersionFromPath()
480 | if !version.IsSystem() {
481 | prependDockerVersionToPath(version)
482 | }
483 |
484 | writeEnvironmentVariableScript(pathEnvVar)
485 | writeInfo("Now using Docker %s", version)
486 | }
487 |
488 | func which() {
489 | currentPath, err := getCurrentDockerPath()
490 | if err == nil {
491 | writeInfo(currentPath)
492 | }
493 | }
494 |
495 | func alias(alias string, value string) {
496 | version := dockerversion.NewAlias(alias, value)
497 | if !isVersionInstalled(version) {
498 | die("The aliased version, %s, is not installed.", nil, retCodeInvalidArgument, version)
499 | }
500 |
501 | aliasPath := getAliasPath(alias)
502 | if _, err := os.Stat(aliasPath); err == nil {
503 | writeDebug("Overwriting existing alias.")
504 | }
505 |
506 | writeFile(aliasPath, version.Value())
507 | writeInfo("Aliased %s to %s.", alias, value)
508 | }
509 |
510 | func unalias(alias string) {
511 | if !aliasExists(alias) {
512 | writeWarning("%s is not an alias.", alias)
513 | return
514 | }
515 |
516 | aliasPath := getAliasPath(alias)
517 | err := os.Remove(aliasPath)
518 | if err != nil {
519 | die("Unable to remove alias %s at %s.", err, retCodeRuntimeError, alias, aliasPath)
520 | }
521 |
522 | writeInfo("Removed alias %s", alias)
523 | }
524 |
525 | func listAlias() {
526 | aliases := getAliases()
527 | for alias, version := range aliases {
528 | writeInfo("\t%s -> %s", alias, version)
529 | }
530 | }
531 |
532 | func aliasExists(alias string) bool {
533 | aliasPath := getAliasPath(alias)
534 | if _, err := os.Stat(aliasPath); err == nil {
535 | return true
536 | }
537 |
538 | return false
539 | }
540 |
541 | func getAliases() map[string]string {
542 | aliases, _ := filepath.Glob(getAliasPath("*"))
543 |
544 | results := make(map[string]string)
545 | for _, aliasPath := range aliases {
546 | alias := filepath.Base(aliasPath)
547 | version, err := ioutil.ReadFile(aliasPath)
548 | if err != nil {
549 | writeDebug("Excluding alias: %s.", err, retCodeRuntimeError, alias)
550 | continue
551 | }
552 |
553 | results[alias] = string(version)
554 | }
555 |
556 | return results
557 | }
558 |
559 | func getAliasPath(alias string) string {
560 | return filepath.Join(opts.DvmDir, "alias", alias)
561 | }
562 |
563 | func getBinaryName() string {
564 | return "docker" + binaryFileExt
565 | }
566 |
567 | func deactivate() {
568 | removePreviousDockerVersionFromPath()
569 | writeEnvironmentVariableScript(pathEnvVar)
570 | }
571 |
572 | func prependDockerVersionToPath(version dockerversion.Version) {
573 | prependPath(getVersionDir(version))
574 | }
575 |
576 | func writeEnvironmentVariableScript(name string) {
577 | // Write to a shell script for the calling wrapper to execute which exports the environment variable
578 | scriptPath := buildDvmOutputScriptPath()
579 | contents := exportEnvironmentVariable(name)
580 |
581 | writeFile(scriptPath, contents)
582 | }
583 |
584 | func buildDvmOutputScriptPath() string {
585 | var fileExtension string
586 | if opts.Shell == "powershell" {
587 | fileExtension = "ps1"
588 | } else if opts.Shell == "cmd" {
589 | fileExtension = "cmd"
590 | } else { // default to bash
591 | fileExtension = "sh"
592 | }
593 | return filepath.Join(opts.DvmDir, ".tmp", "dvm-output."+fileExtension)
594 | }
595 |
596 | func removePreviousDockerVersionFromPath() {
597 | removePath(getCleanPathRegex())
598 | }
599 |
600 | func ensureVersionIsInstalled(version dockerversion.Version) {
601 | if isVersionInstalled(version) {
602 | return
603 | }
604 |
605 | writeInfo("%s is not installed. Installing now...", version)
606 | install(version)
607 | }
608 |
609 | func isVersionInstalled(version dockerversion.Version) bool {
610 | writeDebug("Checking if version is installed: %s", version)
611 | installedVersions := getInstalledVersions("*")
612 |
613 | for _, availableVersion := range installedVersions {
614 | if version.Equals(availableVersion) {
615 | writeDebug("Version %s is installed", version)
616 | return true
617 | }
618 | }
619 |
620 | return false
621 | }
622 |
623 | func getCurrentDockerPath() (string, error) {
624 | currentDockerPath, err := exec.LookPath("docker")
625 | return currentDockerPath, err
626 | }
627 |
628 | func getCurrentDockerVersion() (dockerversion.Version, error) {
629 | currentDockerPath, err := getCurrentDockerPath()
630 | if err != nil {
631 | return dockerversion.Version{}, err
632 | }
633 |
634 | systemDockerPath, _ := getSystemDockerPath()
635 | edgeVersionPath, _ := getEdgeDockerPath()
636 |
637 | isSystem := currentDockerPath == systemDockerPath
638 | isEdge := currentDockerPath == edgeVersionPath
639 |
640 | current, _ := getDockerVersion(currentDockerPath, isEdge)
641 |
642 | if isSystem {
643 | writeDebug("The current docker is the system installation")
644 | current.SetAsSystem()
645 | }
646 |
647 | if isEdge {
648 | writeDebug("The current docker is an edge version")
649 | current.SetAsEdge()
650 | }
651 |
652 | writeDebug("The current version is: %s", current)
653 | return current, nil
654 | }
655 |
656 | func getSystemDockerPath() (string, error) {
657 | originalPath := getPath()
658 | removePreviousDockerVersionFromPath()
659 | systemDockerPath, err := exec.LookPath("docker")
660 | setPath(originalPath)
661 | return systemDockerPath, err
662 | }
663 |
664 | func getSystemDockerVersion() (dockerversion.Version, error) {
665 | systemDockerPath, err := getSystemDockerPath()
666 | if err != nil {
667 | return dockerversion.Version{}, err
668 | }
669 | version, err := getDockerVersion(systemDockerPath, false)
670 | version.SetAsSystem()
671 | return version, err
672 | }
673 |
674 | func getEdgeDockerPath() (string, error) {
675 | edgeVersionPath := filepath.Join(getVersionsDir(), dockerversion.EdgeAlias, getBinaryName())
676 | _, err := os.Stat(edgeVersionPath)
677 | return edgeVersionPath, err
678 | }
679 |
680 | func getEdgeDockerVersion() (dockerversion.Version, error) {
681 | edgeDockerpath, err := getEdgeDockerPath()
682 | if err != nil {
683 | return dockerversion.Version{}, err
684 | }
685 | version, err := getDockerVersion(edgeDockerpath, true)
686 | version.SetAsEdge()
687 | return version, err
688 | }
689 |
690 | func getDockerVersion(dockerPath string, includeBuild bool) (dockerversion.Version, error) {
691 | stdout, _ := exec.Command(dockerPath, "-v").Output()
692 | rawVersion := strings.TrimSpace(string(stdout))
693 |
694 | writeDebug("%s -v output: %s", dockerPath, rawVersion)
695 |
696 | versionRegex := regexp.MustCompile(`^Docker version (.+), build (.+)?`)
697 | match := versionRegex.FindStringSubmatch(rawVersion)
698 | if len(match) < 2 {
699 | return dockerversion.Version{}, errors.New("Could not detect docker version.")
700 | }
701 |
702 | version := string(match[1][:])
703 | build := string(match[2][:])
704 | if includeBuild {
705 | version = fmt.Sprintf("%s+%s", version, build)
706 | }
707 | return dockerversion.Parse(version), nil
708 | }
709 |
710 | func listRemote(prefix string) {
711 | versions := getAvailableVersions(prefix, opts.IncludePrereleases)
712 | for _, version := range versions {
713 | writeInfo(version.String())
714 | }
715 | }
716 |
717 | func getInstalledVersions(pattern string) []dockerversion.Version {
718 | searchPath := filepath.Join(getVersionsDir(), pattern)
719 | versions, _ := filepath.Glob(searchPath)
720 |
721 | var results []dockerversion.Version
722 | for _, versionDir := range versions {
723 | version := dockerversion.Parse(filepath.Base(versionDir))
724 |
725 | if version.IsEdge() {
726 | edgeDockerPath := filepath.Join(versionDir, getBinaryName())
727 | edgeVersion, err := getDockerVersion(edgeDockerPath, true)
728 | if err != nil {
729 | writeDebug("Unable to get version of installed edge version at %s.\n%s", versionDir, err)
730 | continue
731 | }
732 | version = dockerversion.NewAlias(dockerversion.EdgeAlias, edgeVersion.Value())
733 | }
734 |
735 | results = append(results, version)
736 | }
737 |
738 | if glob.Glob(pattern, dockerversion.SystemAlias) {
739 | systemVersion, err := getSystemDockerVersion()
740 | if err == nil {
741 | results = append(results, systemVersion)
742 | }
743 | }
744 |
745 | dockerversion.Sort(results)
746 | return results
747 | }
748 |
749 | func getAvailableVersions(pattern string, includePrereleases bool) []dockerversion.Version {
750 | versions := make(map[string]dockerversion.Version)
751 |
752 | writeDebug("Retrieving legacy Docker releases")
753 | legacyVersions, err := listLegacyDockerVersions()
754 | if err != nil {
755 | die("", err, retCodeRuntimeError)
756 | }
757 | for _, v := range legacyVersions {
758 | if !includePrereleases && v.IsPrerelease() {
759 | continue
760 | }
761 | if strings.HasPrefix(v.Value(), pattern) {
762 | versions[v.String()] = v
763 | }
764 | }
765 |
766 | writeDebug("Retrieving Docker releases")
767 | stableVersions, err := dockerversion.ListVersions(opts.MirrorURL, dockerversion.Stable)
768 | if err != nil {
769 | die("", err, retCodeRuntimeError)
770 | }
771 | for _, v := range stableVersions {
772 | if strings.HasPrefix(v.Value(), pattern) {
773 | versions[v.String()] = v
774 | }
775 | }
776 |
777 | if includePrereleases {
778 | writeDebug("Retrieving Docker pre-releases")
779 | prereleaseVersions, err := dockerversion.ListVersions(opts.MirrorURL, dockerversion.Test)
780 | if err != nil {
781 | die("", err, retCodeRuntimeError)
782 | }
783 | for _, v := range prereleaseVersions {
784 | if strings.HasPrefix(v.Value(), pattern) {
785 | versions[v.String()] = v
786 | }
787 | }
788 | }
789 |
790 | results := make([]dockerversion.Version, 0, len(versions))
791 | for _, v := range versions {
792 | results = append(results, v)
793 | }
794 |
795 | dockerversion.Sort(results)
796 |
797 | return results
798 | }
799 |
800 | func listLegacyDockerVersions() ([]dockerversion.Version, error) {
801 | gh := buildGithubClient()
802 | options := &github.ListOptions{PerPage: 100}
803 |
804 | var allReleases []github.RepositoryRelease
805 | for {
806 | releases, response, err := gh.Repositories.ListReleases("moby", "moby", options)
807 | if err != nil {
808 | warnWhenRateLimitExceeded(err, response)
809 | return nil, errors.Wrap(err, "Unable to retrieve list of Docker releases from GitHub")
810 | }
811 | allReleases = append(allReleases, releases...)
812 | if response.StatusCode != 200 {
813 | return nil, errors.Errorf("Unable to retrieve list of Docker releases from GitHub (Status %v).", response.StatusCode)
814 | }
815 | if response.NextPage == 0 {
816 | break
817 | }
818 | options.Page = response.NextPage
819 | }
820 |
821 | var results []dockerversion.Version
822 | for _, release := range allReleases {
823 | version := *release.Name
824 | v := dockerversion.Parse(version)
825 | if v.IsEmpty() {
826 | writeDebug("Ignoring non-semver Docker release: %s", version)
827 | continue
828 | }
829 | results = append(results, v)
830 | }
831 |
832 | return results, nil
833 | }
834 |
835 | func isUpgradeAvailable() (bool, string) {
836 | gh := buildGithubClient()
837 | release, response, err := gh.Repositories.GetLatestRelease("howtowhale", "dvm")
838 | if err != nil {
839 | warnWhenRateLimitExceeded(err, response)
840 | writeWarning("Unable to query the latest dvm release from GitHub:")
841 | writeWarning("%s", err)
842 | return false, ""
843 | }
844 | if response.StatusCode != 200 {
845 | writeWarning("Unable to query the latest dvm release from GitHub (Status %s):", response.StatusCode)
846 | return false, ""
847 | }
848 |
849 | currentVersion, err := semver.NewVersion(dvmVersion)
850 | if err != nil {
851 | writeWarning("Unable to parse the current dvm version as a semantic version!")
852 | writeWarning("%s", err)
853 | return false, ""
854 | }
855 | latestVersion, err := semver.NewVersion(*release.TagName)
856 | if err != nil {
857 | writeWarning("Unable to parse the latest dvm version as a semantic version!")
858 | writeWarning("%s", err)
859 | return false, ""
860 | }
861 |
862 | return latestVersion.GreaterThan(currentVersion), *release.TagName
863 | }
864 |
865 | func getVersionsDir() string {
866 | return filepath.Join(opts.DvmDir, "bin", "docker")
867 | }
868 |
869 | func getVersionDir(version dockerversion.Version) string {
870 | versionPath := version.Slug()
871 | if version.IsEdge() {
872 | versionPath = dockerversion.EdgeAlias
873 | }
874 | return filepath.Join(getVersionsDir(), versionPath)
875 | }
876 |
877 | func getDockerVersionVar() string {
878 | return strings.TrimSpace(os.Getenv("DOCKER_VERSION"))
879 | }
880 |
881 | func buildGithubClient() *github.Client {
882 | if opts.Token != "" {
883 | tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: opts.Token})
884 | httpClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
885 | return github.NewClient(httpClient)
886 | }
887 |
888 | client := github.NewClient(nil)
889 | if githubUrlOverride != "" {
890 | var err error
891 | client.BaseURL, err = neturl.Parse(githubUrlOverride)
892 | if err != nil {
893 | die("Invalid github url override: %s", err, retCodeInvalidArgument, githubUrlOverride)
894 | }
895 | }
896 | return client
897 | }
898 |
899 | func warnWhenRateLimitExceeded(err error, response *github.Response) {
900 | if err == nil {
901 | return
902 | }
903 |
904 | if response != nil {
905 | if response.StatusCode == 403 {
906 | writeWarning("Your GitHub API rate limit has been exceeded. Set the GITHUB_TOKEN environment variable or use the --github-token parameter with your GitHub personal access token to authenticate and increase the rate limit.")
907 | }
908 | }
909 | }
910 |
--------------------------------------------------------------------------------
/dvm-helper/dvm-helper.linux.go:
--------------------------------------------------------------------------------
1 | //go:build linux
2 | // +build linux
3 |
4 | package main
5 |
6 | const dvmOS string = "Linux"
7 |
--------------------------------------------------------------------------------
/dvm-helper/dvm-helper.nix.go:
--------------------------------------------------------------------------------
1 | //go:build !windows
2 | // +build !windows
3 |
4 | package main
5 |
6 | import (
7 | "os"
8 | "path/filepath"
9 |
10 | "github.com/howtowhale/dvm/dvm-helper/internal/downloader"
11 | )
12 |
13 | const binaryFileExt string = ""
14 |
15 | func upgradeSelf(version string) {
16 | d := downloader.New(opts)
17 |
18 | binaryURL := buildDvmReleaseURL(version, dvmOS, dvmArch, "dvm-helper")
19 | binaryPath := filepath.Join(opts.DvmDir, "dvm-helper", "dvm-helper")
20 | err := d.DownloadFileWithChecksum(binaryURL, binaryPath)
21 | if err != nil {
22 | die("", err, retCodeRuntimeError)
23 | }
24 |
25 | scriptURL := buildDvmReleaseURL(version, "dvm.sh")
26 | scriptPath := filepath.Join(opts.DvmDir, "dvm.sh")
27 | err = d.DownloadFile(scriptURL, scriptPath)
28 | if err != nil {
29 | die("", err, retCodeRuntimeError)
30 | }
31 | }
32 |
33 | func getCleanPathRegex() string {
34 | versionDir := getVersionsDir()
35 | return versionDir + `/[^:]+:`
36 | }
37 |
38 | func validateShellFlag() {
39 | // we don't care about the shell flag on non-Windows platforms
40 | }
41 |
42 | func getUserHomeDir() string {
43 | return os.Getenv("HOME")
44 | }
45 |
--------------------------------------------------------------------------------
/dvm-helper/dvm-helper.windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package main
5 |
6 | import (
7 | "fmt"
8 | "os"
9 | "path/filepath"
10 | "strings"
11 |
12 | "github.com/howtowhale/dvm/dvm-helper/internal/downloader"
13 | )
14 |
15 | const dvmOS string = "Windows"
16 | const binaryFileExt string = ".exe"
17 |
18 | func upgradeSelf(version string) {
19 | d := downloader.New(opts)
20 |
21 | binaryURL := buildDvmReleaseURL(version, dvmOS, dvmArch, "dvm-helper.exe")
22 | binaryPath := filepath.Join(opts.DvmDir, ".tmp", "dvm-helper.exe")
23 | err := d.DownloadFileWithChecksum(binaryURL, binaryPath)
24 | if err != nil {
25 | die("", err, retCodeRuntimeError)
26 | }
27 |
28 | psScriptURL := buildDvmReleaseURL(version, "dvm.ps1")
29 | psScriptPath := filepath.Join(opts.DvmDir, "dvm.ps1")
30 | err = d.DownloadFile(psScriptURL, psScriptPath)
31 | if err != nil {
32 | die("", err, retCodeRuntimeError)
33 | }
34 |
35 | cmdScriptURL := buildDvmReleaseURL(version, "dvm.cmd")
36 | cmdScriptPath := filepath.Join(opts.DvmDir, "dvm.cmd")
37 | err = d.DownloadFile(cmdScriptURL, cmdScriptPath)
38 | if err != nil {
39 | die("", err, retCodeRuntimeError)
40 | }
41 |
42 | writeUpgradeScript()
43 | }
44 |
45 | func writeUpgradeScript() {
46 | scriptPath := buildDvmOutputScriptPath()
47 | tmpBinaryPath := filepath.Join(opts.DvmDir, ".tmp", "dvm-helper.exe")
48 | binaryPath := filepath.Join(opts.DvmDir, "dvm-helper", "dvm-helper.exe")
49 |
50 | var contents string
51 | if opts.Shell == "powershell" {
52 | contents = fmt.Sprintf("cp -force '%s' '%s'", tmpBinaryPath, binaryPath)
53 | } else { // cmd
54 | contents = fmt.Sprintf("cp /Y '%s' '%s'", tmpBinaryPath, binaryPath)
55 | }
56 |
57 | writeFile(scriptPath, contents)
58 | }
59 |
60 | func getCleanPathRegex() string {
61 | versionDir := getVersionsDir()
62 | escapedVersionDir := strings.Replace(versionDir, `\`, `\\`, -1)
63 | return escapedVersionDir + `\\[^:]+;`
64 | }
65 |
66 | func validateShellFlag() {
67 | if opts.Shell != "powershell" && opts.Shell != "cmd" {
68 | die("The --shell flag or SHELL environment variable must be set when running on Windows. Available values are powershell and cmd.", nil, retCodeInvalidArgument)
69 | }
70 | }
71 |
72 | func getUserHomeDir() string {
73 | return os.Getenv("USERPROFILE")
74 | }
75 |
--------------------------------------------------------------------------------
/dvm-helper/dvm-helper_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "net/http"
7 | "net/http/httptest"
8 | "os"
9 | "testing"
10 |
11 | "github.com/fatih/color"
12 | "github.com/howtowhale/dvm/dvm-helper/internal/test"
13 | "github.com/ryanuber/go-glob"
14 | "github.com/stretchr/testify/assert"
15 | )
16 |
17 | type requestHandler func(w http.ResponseWriter, r *http.Request)
18 |
19 | func docker1_10_3_Handler(w http.ResponseWriter, r *http.Request) {
20 | w.Header().Set("Content-Type", "application/json")
21 |
22 | switch {
23 | case glob.Glob("*/version", r.RequestURI):
24 | fmt.Fprintln(w, `{
25 | "Version": "swarm/1.2.3",
26 | "ApiVersion": "1.22"
27 | }`)
28 | default:
29 | w.WriteHeader(404)
30 | }
31 | }
32 |
33 | func docker1_12_1_Handler(w http.ResponseWriter, r *http.Request) {
34 | w.Header().Set("Content-Type", "application/json")
35 |
36 | switch {
37 | case glob.Glob("*/version", r.RequestURI):
38 | fmt.Fprintln(w, `{
39 | "Version": "1.12.1"
40 | }`)
41 | default:
42 | w.WriteHeader(404)
43 | }
44 | }
45 |
46 | func githubReleasesHandler(w http.ResponseWriter, r *http.Request) {
47 | w.Header().Set("Content-Type", "application/json")
48 |
49 | switch r.RequestURI {
50 | case "/repos/moby/moby/releases?per_page=100":
51 | fmt.Fprintln(w, test.LoadTestData("github-docker-releases.json"))
52 | default:
53 | w.WriteHeader(404)
54 | }
55 | }
56 |
57 | func createMockDVM(dockerHandler requestHandler) (docker *httptest.Server, github *httptest.Server) {
58 | github = httptest.NewServer(http.HandlerFunc(githubReleasesHandler))
59 | githubUrlOverride = github.URL + "/"
60 |
61 | if dockerHandler != nil {
62 | docker = httptest.NewServer(http.HandlerFunc(dockerHandler))
63 | }
64 | return
65 | }
66 |
67 | func TestDetectOldVersion(t *testing.T) {
68 | docker, github := createMockDVM(docker1_10_3_Handler)
69 | defer docker.Close()
70 | defer github.Close()
71 |
72 | os.Setenv("DOCKER_HOST", docker.URL)
73 | os.Setenv("DOCKER_TLS_VERIFY", "0")
74 | os.Unsetenv("DOCKER_CERT_PATH")
75 |
76 | outputCapture := &bytes.Buffer{}
77 | color.Output = outputCapture
78 |
79 | dvm := makeCliApp()
80 | dvm.Run([]string{"dvm", "--debug", "detect"})
81 |
82 | version := os.Getenv("DOCKER_VERSION")
83 | assert.Equal(t, version, "1.10.3", "Detected the wrong version")
84 |
85 | output := outputCapture.String()
86 | assert.NotEmpty(t, output, "Should have captured stdout")
87 | assert.Contains(t, output, "Detected client version: 1.10.3", "Should have printed the detected version")
88 | }
89 |
90 | func TestDetectVersion(t *testing.T) {
91 | docker, github := createMockDVM(docker1_12_1_Handler)
92 | defer docker.Close()
93 | defer github.Close()
94 |
95 | outputCapture := &bytes.Buffer{}
96 | color.Output = outputCapture
97 |
98 | os.Setenv("DOCKER_HOST", docker.URL)
99 | os.Setenv("DOCKER_TLS_VERIFY", "0")
100 | os.Unsetenv("DOCKER_CERT_PATH")
101 |
102 | dvm := makeCliApp()
103 | dvm.Run([]string{"dvm", "--debug", "detect"})
104 |
105 | version := os.Getenv("DOCKER_VERSION")
106 | assert.Equal(t, version, "1.12.1", "Detected the wrong version")
107 |
108 | output := outputCapture.String()
109 | assert.NotEmpty(t, output, "Should have captured stdout")
110 | assert.Contains(t, output, "Detected client version: 1.12.1", "Should have printed the detected version")
111 | }
112 |
113 | func TestListRemote(t *testing.T) {
114 | _, github := createMockDVM(nil)
115 | defer github.Close()
116 |
117 | outputCapture := &bytes.Buffer{}
118 | color.Output = outputCapture
119 |
120 | dvm := makeCliApp()
121 | dvm.Run([]string{"dvm", "--debug", "list-remote"})
122 |
123 | output := outputCapture.String()
124 | assert.NotEmpty(t, output, "Should have captured stdout")
125 |
126 | assert.Contains(t, output, "1.12.5", "Should have listed a legacy stable version")
127 | assert.NotContains(t, output, "1.12.5-rc1", "Should not have listed a legacy prerelease version")
128 |
129 | assert.Contains(t, output, "17.09.0-ce", "Should have listed a stable version")
130 | assert.NotContains(t, output, "17.10.0-ce-rc1", "Should not have listed a prerelease version")
131 | }
132 |
133 | func TestListRemoteWithPrereleases(t *testing.T) {
134 | _, github := createMockDVM(nil)
135 | defer github.Close()
136 |
137 | outputCapture := &bytes.Buffer{}
138 | color.Output = outputCapture
139 |
140 | dvm := makeCliApp()
141 | dvm.Run([]string{"dvm-helper", "--debug", "list-remote", "--pre"})
142 |
143 | output := outputCapture.String()
144 | assert.NotEmpty(t, output, "Should have captured stdout")
145 |
146 | assert.Contains(t, output, "1.12.5-rc1", "Should have listed a legacy prerelease version")
147 | assert.Contains(t, output, "17.10.0-ce-rc1", "Should have listed a prerelease version")
148 | }
149 |
150 | func TestInstallPrereleases(t *testing.T) {
151 | _, github := createMockDVM(nil)
152 | defer github.Close()
153 |
154 | outputCapture := &bytes.Buffer{}
155 | color.Output = outputCapture
156 |
157 | dvm := makeCliApp()
158 | dvm.Run([]string{"dvm-helper", "--debug", "install", "18.06.1-ce"})
159 |
160 | output := outputCapture.String()
161 | assert.NotEmpty(t, output, "Should have captured stdout")
162 | assert.Contains(t, output, "Now using Docker 18.06.1-ce", "Should have installed a prerelease version")
163 | }
164 |
165 | // install a version from the test location that is missing the -rc suffix
166 | func TestInstallNonPrereleaseTestRelease(t *testing.T) {
167 | _, github := createMockDVM(nil)
168 | defer github.Close()
169 |
170 | outputCapture := &bytes.Buffer{}
171 | color.Output = outputCapture
172 |
173 | dvm := makeCliApp()
174 | dvm.Run([]string{"dvm-helper", "--debug", "install", "17.10.0-ce"})
175 |
176 | output := outputCapture.String()
177 | assert.NotEmpty(t, output, "Should have captured stdout")
178 | assert.Contains(t, output, "Now using Docker 17.10.0-ce", "Should have installed a test version")
179 | }
180 |
181 | // install something that used to be a test release and is now considered stable
182 | func TestInstallStabilizedTestRelease(t *testing.T) {
183 | _, github := createMockDVM(nil)
184 | defer github.Close()
185 |
186 | outputCapture := &bytes.Buffer{}
187 | color.Output = outputCapture
188 |
189 | dvm := makeCliApp()
190 | dvm.Run([]string{"dvm-helper", "--debug", "install", "17.09.0-ce"})
191 |
192 | output := outputCapture.String()
193 | assert.NotEmpty(t, output, "Should have captured stdout")
194 | assert.Contains(t, output, "Now using Docker 17.09.0-ce", "Should have installed a stable version")
195 | }
196 |
--------------------------------------------------------------------------------
/dvm-helper/internal/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | )
7 |
8 | type DvmOptions struct {
9 | DvmDir string
10 | MirrorURL string
11 | Token string
12 | Shell string
13 | Debug bool
14 | Silent bool
15 | IncludePrereleases bool
16 | Logger *log.Logger
17 | }
18 |
19 | func NewDvmOptions() DvmOptions {
20 | return DvmOptions{
21 | Logger: log.New(ioutil.Discard, "", log.LstdFlags),
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/dvm-helper/internal/downloader/downloader.go:
--------------------------------------------------------------------------------
1 | package downloader
2 |
3 | import (
4 | "io"
5 | "log"
6 | "net/http"
7 | "os"
8 | "path"
9 | "path/filepath"
10 | "strings"
11 |
12 | "github.com/howtowhale/dvm/dvm-helper/checksum"
13 | "github.com/howtowhale/dvm/dvm-helper/internal/config"
14 | "github.com/pivotal-golang/archiver/extractor"
15 | "github.com/pkg/errors"
16 | )
17 |
18 | // Client is capable of downloading archived and checksumed files.
19 | type Client struct {
20 | log *log.Logger
21 | tmp string
22 | }
23 |
24 | // New creates a downloader client.
25 | // l - optional logger for debug output
26 | func New(opts config.DvmOptions) Client {
27 | return Client{
28 | log: opts.Logger,
29 | tmp: filepath.Join(opts.DvmDir, ".tmp"),
30 | }
31 | }
32 |
33 | func (d Client) ensureParentDirectoryExists(path string) error {
34 | err := os.MkdirAll(filepath.Dir(path), os.ModePerm)
35 | return errors.Wrapf(err, "Unable to create parent directory %s", path)
36 | }
37 |
38 | // DownloadFile saves a file without any additional processing.
39 | func (d Client) DownloadFile(url string, destPath string) error {
40 | err := d.ensureParentDirectoryExists(destPath)
41 | if err != nil {
42 | return err
43 | }
44 |
45 | destFile, err := os.Create(destPath)
46 | if err != nil {
47 | return errors.Wrapf(err, "Unable to create %s", destPath)
48 | }
49 | defer destFile.Close()
50 | os.Chmod(destPath, 0755)
51 |
52 | d.log.Printf("Downloading %s to %s\n", url, destPath)
53 |
54 | response, err := http.Get(url)
55 | if err != nil {
56 | return errors.Wrapf(err, "Unable to download %s", url)
57 | }
58 |
59 | if response.StatusCode != 200 {
60 | return errors.Wrapf(err, "Unable to download %s (Status %d)", url, response.StatusCode)
61 | }
62 | defer response.Body.Close()
63 |
64 | _, err = io.Copy(destFile, response.Body)
65 | return errors.Wrapf(err, "Unable to write to %s", destPath)
66 | }
67 |
68 | // Download file saves a file after verifying the checksum found at url + ".sh256".
69 | func (d Client) DownloadFileWithChecksum(url string, destPath string) error {
70 | fileName := filepath.Base(destPath)
71 | tmpPath := filepath.Join(d.tmp, fileName)
72 | err := d.DownloadFile(url, tmpPath)
73 | if err != nil {
74 | return err
75 | }
76 |
77 | checksumURL := url + ".sha256"
78 | checksumPath := filepath.Join(d.tmp, fileName+".sha256")
79 | err = d.DownloadFile(checksumURL, checksumPath)
80 | if err != nil {
81 | return err
82 | }
83 |
84 | isValid, err := checksum.CompareChecksum(tmpPath, checksumPath)
85 | if err != nil {
86 | return errors.Wrapf(err, "Unable to calculate checksum of %s", tmpPath)
87 | }
88 | if !isValid {
89 | return errors.Wrapf(err, "The checksum of %s failed to match %s", tmpPath, checksumPath)
90 | }
91 |
92 | // Copy to final location, if different
93 | if destPath != tmpPath {
94 | err = d.ensureParentDirectoryExists(destPath)
95 | if err != nil {
96 | return err
97 | }
98 |
99 | err = os.Rename(tmpPath, destPath)
100 | if err != nil {
101 | return errors.Wrapf(err, "Unable to copy %s to %s", tmpPath, destPath)
102 | }
103 | }
104 |
105 | // Cleanup temp files
106 | if err = os.Remove(checksumPath); err != nil {
107 | d.log.Println(errors.Wrapf(err, "Unable to remove temporary file %s", checksumPath))
108 | }
109 |
110 | return nil
111 | }
112 |
113 | // DownloadArchivedFile downloads the archive, decompresses it and saves the specified file to the destination path.
114 | // url - URL of the archived file, e.g. a gzip, zip or tar file
115 | // archivedFile - relative path to the desired file in the archive
116 | // destPath - location where the archivedFile should be saved
117 | func (d Client) DownloadArchivedFile(url string, archivedFile string, destPath string) error {
118 | archiveName := path.Base(url)
119 | tmpPath := filepath.Join(d.tmp, archiveName)
120 |
121 | err := d.DownloadFile(url, tmpPath)
122 | if err != nil {
123 | return err
124 | }
125 |
126 | return d.extractArchive(tmpPath, archiveName, archivedFile, destPath)
127 | }
128 |
129 | // DownloadArchivedFileWithChecksum first verifies the checksum found at url + ".sh256",
130 | // decompresses the archive, and then saves the specified file to the destination path.
131 | // url - URL of the archived file, e.g. a gzip, zip or tar file
132 | // archivedFile - relative path to the desired file in the archive
133 | // destPath - location where the archivedFile should be saved
134 | func (d Client) DownloadArchivedFileWithChecksum(url string, archivedFile string, destPath string) error {
135 | archiveName := path.Base(url)
136 | tmpPath := filepath.Join(d.tmp, archiveName)
137 |
138 | err := d.DownloadFileWithChecksum(url, tmpPath)
139 | if err != nil {
140 | return err
141 | }
142 |
143 | return d.extractArchive(tmpPath, archiveName, archivedFile, destPath)
144 | }
145 |
146 | func (d Client) extractArchive(tmpPath string, archiveName string, archivedFile string, destPath string) error {
147 | // Extract the archive
148 | archivePath := filepath.Join(d.tmp, strings.TrimSuffix(archiveName, filepath.Ext(archiveName)))
149 | x := extractor.NewDetectable()
150 | x.Extract(tmpPath, archivePath)
151 |
152 | // Copy the archived file to the final destination
153 | archivedFilePath := filepath.Join(archivePath, archivedFile)
154 |
155 | err := d.ensureParentDirectoryExists(destPath)
156 | if err != nil {
157 | return err
158 | }
159 |
160 | err = os.Rename(archivedFilePath, destPath)
161 | if err != nil {
162 | return errors.Wrapf(err, "Unable to copy %s to %s", archivedFilePath, destPath)
163 | }
164 |
165 | // Cleanup temp files
166 | if err = os.Remove(tmpPath); err != nil {
167 | d.log.Println(errors.Wrapf(err, "Unable to remove temporary file %s", tmpPath))
168 | }
169 | if err = os.RemoveAll(archivePath); err != nil {
170 | d.log.Println(errors.Wrapf(err, "Unable to remove temporary directory %s", archivePath))
171 | }
172 |
173 | return nil
174 | }
175 |
--------------------------------------------------------------------------------
/dvm-helper/internal/test/testdata.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 | "path/filepath"
7 | )
8 |
9 | // LoadTestData reads the relative path under the testdata directory
10 | // and returns the contents.
11 | func LoadTestData(src string) string {
12 | pwd, err := os.Getwd()
13 | if err != nil {
14 | panic(err)
15 | }
16 |
17 | testFile := filepath.Join(pwd, "testdata", src)
18 | content, err := ioutil.ReadFile(testFile)
19 | if err != nil {
20 | panic(err)
21 | }
22 | return string(content)
23 | }
24 |
--------------------------------------------------------------------------------
/dvm-helper/path.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "regexp"
7 | )
8 |
9 | const pathEnvVar string = "PATH"
10 |
11 | // Get the PATH environment variable value
12 | func getPath() string {
13 | return os.Getenv(pathEnvVar)
14 | }
15 |
16 | // Set the PATH environment variable value
17 | func setPath(value string) {
18 | os.Setenv(pathEnvVar, value)
19 | }
20 |
21 | // Prepend the specified value to the PATH environment variable
22 | func prependPath(value string) {
23 | originalPath := getPath()
24 | newPath := fmt.Sprintf("%s%c%s", value, os.PathListSeparator, originalPath)
25 | setPath(newPath)
26 | }
27 |
28 | // Remove any values which match the specified regular expression
29 | // from the PATH environment variable
30 | func removePath(regexValue string) {
31 | regex, _ := regexp.Compile(regexValue)
32 | newPath := regex.ReplaceAllString(getPath(), "")
33 | setPath(newPath)
34 | }
35 |
--------------------------------------------------------------------------------
/dvm-helper/url/url.go:
--------------------------------------------------------------------------------
1 | package url
2 |
3 | import "strings"
4 |
5 | // Join joins any number of path elements into a single path, adding a separating slash if necessary.
6 | // All empty strings are ignored.
7 | func Join(elem ...string) string {
8 | for i, e := range elem {
9 | if e != "" {
10 | return clean(strings.Join(elem[i:], "/"))
11 | }
12 | }
13 | return ""
14 | }
15 |
16 | func clean(urlPart string) string {
17 | return strings.TrimRight(urlPart, "/")
18 | }
19 |
--------------------------------------------------------------------------------
/dvm-helper/util.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "path/filepath"
8 |
9 | "github.com/fatih/color"
10 | )
11 |
12 | func exportEnvironmentVariable(name string) string {
13 | value := os.Getenv(name)
14 |
15 | if opts.Shell == "powershell" {
16 | return fmt.Sprintf("$env:%s=\"%s\"\r\n", name, value)
17 | }
18 |
19 | if opts.Shell == "cmd" {
20 | return fmt.Sprintf("%s=%s\r\n", name, value)
21 | }
22 |
23 | // default to bash
24 | return fmt.Sprintf("export %s=\"%s\"\n", name, value)
25 | }
26 |
27 | func ensureParentDirectoryExists(filePath string) {
28 | dir := filepath.Dir(filePath)
29 |
30 | err := os.MkdirAll(dir, 0777)
31 | if err != nil {
32 | die("Unable to create directory %s.", err, retCodeRuntimeError, dir)
33 | }
34 | }
35 |
36 | func writeFile(path string, contents string) {
37 | writeDebug("Writing to %s...", path)
38 | writeDebug(contents)
39 |
40 | ensureParentDirectoryExists(path)
41 |
42 | file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660)
43 | if err != nil {
44 | die("Unable to create %s", err, retCodeRuntimeError, path)
45 | }
46 |
47 | _, err = io.WriteString(file, contents)
48 | if err != nil {
49 | die("Unable to write to %s", err, retCodeRuntimeError, path)
50 | }
51 |
52 | file.Close()
53 | }
54 |
55 | func writeDebug(format string, a ...interface{}) {
56 | if !opts.Debug {
57 | return
58 | }
59 |
60 | color.Cyan(format, a...)
61 | }
62 |
63 | func writeInfo(format string, a ...interface{}) {
64 | if opts.Silent {
65 | return
66 | }
67 |
68 | color.White(format, a...)
69 | }
70 |
71 | func writeWarning(format string, a ...interface{}) {
72 | if opts.Silent {
73 | return
74 | }
75 |
76 | color.Yellow(format, a...)
77 | }
78 |
79 | func writeError(format string, err error, a ...interface{}) {
80 | color.Set(color.FgRed)
81 | fmt.Fprintf(os.Stderr, format+"\n", a...)
82 | if err != nil {
83 | fmt.Fprintln(os.Stderr, err)
84 | }
85 | color.Unset()
86 | }
87 |
88 | func die(format string, err error, exitCode int, a ...interface{}) {
89 | writeError(format, err, a...)
90 | os.Exit(exitCode)
91 | }
92 |
--------------------------------------------------------------------------------
/dvm.cmd:
--------------------------------------------------------------------------------
1 | :: Docker Version Manager CMD Wrapper
2 | :: Implemented as a POSIX-compliant function
3 | :: To use, add this script's parent directory to your path
4 |
5 | @ECHO OFF
6 | SETLOCAL
7 |
8 | SET DVM_DIR=%~dp0
9 |
10 | IF NOT EXIST "%DVM_DIR%\dvm-helper\dvm-helper.exe" (
11 | echo Installation corrupt: dvm-helper.exe is missing. Please reinstall dvm.
12 | EXIT /b 1
13 | )
14 |
15 | SET DVM_OUTPUT="%DVM_DIR%\.tmp\dvm-output.cmd"
16 |
17 | IF EXIST "%DVM_OUTPUT%" (
18 | DEL "%DVM_OUTPUT%"
19 | )
20 |
21 | %DVM_DIR%\dvm-helper\dvm-helper.exe --shell cmd %*
22 |
23 | IF EXIST "%DVM_OUTPUT%" (
24 | ENDLOCAL
25 | CALL "%DVM_OUTPUT%"
26 | )
27 |
--------------------------------------------------------------------------------
/dvm.ps1:
--------------------------------------------------------------------------------
1 | # Docker Version Manager PowerShell Wrapper
2 | # Implemented as a POSIX-compliant function
3 | # To use, source this script, `. dvm.ps1`, then type dvm help
4 |
5 | function dvm() {
6 | # Default dvm home to ~/.dvm if not set
7 | $dvmDir = $env:DVM_DIR
8 | if( $dvmDir -eq $null ) {
9 | $dvmDir = "$env:USERPROFILE\.dvm"
10 | }
11 |
12 | # Expect that dvm-helper is next to this script
13 | $dvmHelper = Join-Path $PSScriptRoot dvm-helper\dvm-helper.exe
14 | if( !(Test-Path $dvmHelper) ) {
15 | $host.ui.WriteErrorLine("Installation corrupt: dvm-helper.exe is missing. Please reinstall dvm.")
16 | return 1
17 | }
18 |
19 | # Pass dvm-helper output back to script via ~/.dvm/.tmp/dvm-output.ps1
20 | $dvmOutput = Join-Path $dvmDir .tmp\dvm-output.ps1
21 | if( Test-Path $dvmOutput ) {
22 | rm $dvmOutput
23 | }
24 |
25 | $rawArgs = $MyInvocation.Line.Substring(3).Trim()
26 | $dvmCall = "& '$dvmHelper' --shell powershell $rawArgs"
27 | iex $dvmCall
28 |
29 | if( Test-Path $dvmOutput ) {
30 | . $dvmOutput
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/dvm.sh:
--------------------------------------------------------------------------------
1 | # Docker Version Manager wrapper for *nix
2 | # Implemented as a POSIX-compliant function
3 | # Should work on sh, dash, bash, ksh, zsh
4 | # To use, source this file from your bash profile
5 |
6 | { # This ensures the entire script is downloaded
7 |
8 | DVM_SCRIPT_SOURCE="$_"
9 |
10 | __dvm_has() {
11 | type "$1" > /dev/null 2>&1
12 | }
13 |
14 | # Make zsh glob matching behave same as bash
15 | # This fixes the "zsh: no matches found" errors
16 | if __dvm_has "unsetopt"; then
17 | unsetopt nomatch 2>/dev/null
18 | DVM_CD_FLAGS="-q"
19 | fi
20 |
21 | # Default DVM_DIR to $HOME/.dvm when not set
22 | if [ -z "$DVM_DIR" ]; then
23 | DVM_DIR="$HOME/.dvm"
24 | fi
25 |
26 | # Expect that dvm-helper is next to this script
27 | if [ -n "$BASH_SOURCE" ]; then
28 | DVM_SCRIPT_SOURCE="${BASH_SOURCE[0]}"
29 | fi
30 | if command -v builtin >/dev/null 2>&1; then
31 | export DVM_HELPER="$(builtin cd $DVM_CD_FLAGS "$(dirname "${DVM_SCRIPT_SOURCE:-$0}")" > /dev/null && command pwd)/dvm-helper/dvm-helper"
32 | else
33 | export DVM_HELPER="$(cd $DVM_CD_FLAGS "$(dirname "${DVM_SCRIPT_SOURCE:-$0}")" > /dev/null && command pwd)/dvm-helper/dvm-helper"
34 | fi
35 | unset DVM_SCRIPT_SOURCE 2> /dev/null
36 |
37 | dvm() {
38 | if [ ! -f "$DVM_HELPER" ]; then
39 | echo "Installation corrupt: dvm-helper is missing. Please reinstall dvm."
40 | return 1
41 | fi
42 |
43 | # Pass dvm-helper output back to script via ~/.dvm/.tmp/dvm-output.sh
44 | DVM_OUTPUT="$DVM_DIR/.tmp/dvm-output.sh"
45 | command rm -f "$DVM_OUTPUT"
46 |
47 | $DVM_HELPER --shell sh $@
48 |
49 | # Execute any dvm-helper output
50 | if [ -e $DVM_OUTPUT ]; then
51 | source $DVM_OUTPUT
52 | fi
53 | }
54 |
55 | # Make the dvm function available to other scripts
56 | if [ -n "$ZSH_NAME" ]; then
57 | autoload dvm
58 | else
59 | export -f dvm
60 | fi
61 | }
62 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/howtowhale/dvm
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/Masterminds/semver v0.0.0-20170707023526-c2e7f6c2f49a
7 | github.com/codegangsta/cli v1.19.1
8 | github.com/docker/docker v1.13.1
9 | github.com/fatih/color v1.5.0
10 | github.com/google/go-github v0.0.0-20160619221136-1c08387e4c91
11 | github.com/pivotal-golang/archiver v0.0.0-20170206191640-59f15fd17404
12 | github.com/pkg/errors v0.8.1-0.20161029093637-248dadf4e906
13 | github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735
14 | github.com/stretchr/testify v1.1.4
15 | golang.org/x/oauth2 v0.0.0-20170214231824-b9780ec78894
16 | )
17 |
18 | require (
19 | code.cloudfoundry.org/archiver v0.0.0-20200131002800-4ca7245c29b1 // indirect
20 | github.com/Microsoft/go-winio v0.3.8 // indirect
21 | github.com/Sirupsen/logrus v0.11.3-0.20170215164324-7f4b1adc7917 // indirect
22 | github.com/cyphar/filepath-securejoin v0.2.2 // indirect
23 | github.com/davecgh/go-spew v1.0.1-0.20160907170601-6d212800a42e // indirect
24 | github.com/docker/distribution v2.6.0-rc.1.0.20170216011216-4f87c800734f+incompatible // indirect
25 | github.com/docker/go-connections v0.2.2-0.20170203235624-7da10c8c50ca // indirect
26 | github.com/docker/go-units v0.3.2 // indirect
27 | github.com/golang/protobuf v1.4.2 // indirect
28 | github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect
29 | github.com/mattn/go-colorable v0.0.8-0.20170210172801-5411d3eea597 // indirect
30 | github.com/mattn/go-isatty v0.0.12 // indirect
31 | github.com/onsi/ginkgo v1.15.0 // indirect
32 | github.com/onsi/gomega v1.10.5 // indirect
33 | github.com/opencontainers/go-digest v1.0.0-rc0 // indirect
34 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 // indirect
35 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect
36 | golang.org/x/sys v0.0.0-20210112080510-489259a85091 // indirect
37 | google.golang.org/appengine v1.0.1-0.20170206203024-2e4a801b39fc // indirect
38 | google.golang.org/protobuf v1.23.0 // indirect
39 | )
40 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | code.cloudfoundry.org/archiver v0.0.0-20200131002800-4ca7245c29b1 h1:yd4N3o8xBCMu3z6hyIMH9SoPATjRyovVUNdefRZBsws=
2 | code.cloudfoundry.org/archiver v0.0.0-20200131002800-4ca7245c29b1/go.mod h1:EU/KcUvh4qBVN6ffu/b8t9x7SurDot+AO0HqEZ+9QOg=
3 | github.com/Masterminds/semver v0.0.0-20170707023526-c2e7f6c2f49a h1:sfxurEC2fF6fwenKeKAlrdX831dONQwmgnSBX1R8jbM=
4 | github.com/Masterminds/semver v0.0.0-20170707023526-c2e7f6c2f49a/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
5 | github.com/Microsoft/go-winio v0.3.8 h1:dvxbxtpTIjdAbx2OtL26p4eq0iEvys/U5yrsTJb3NZI=
6 | github.com/Microsoft/go-winio v0.3.8/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
7 | github.com/Sirupsen/logrus v0.11.3-0.20170215164324-7f4b1adc7917 h1:DOTgSwuJxNDNHdsdWODAG9SB/0z+LsZh+DmTwSLdUns=
8 | github.com/Sirupsen/logrus v0.11.3-0.20170215164324-7f4b1adc7917/go.mod h1:rmk17hk6i8ZSAJkSDa7nOxamrG+SP4P0mm+DAvExv4U=
9 | github.com/codegangsta/cli v1.19.1 h1:+wkU9+nidApJ051CVhVGnj5li64qOfLPz7eZMn2DPXw=
10 | github.com/codegangsta/cli v1.19.1/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
11 | github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
12 | github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
13 | github.com/davecgh/go-spew v1.0.1-0.20160907170601-6d212800a42e h1:9EoM2C6YAkhnxTxG3LrAos2/KaALZdSNG5HTGPEEedE=
14 | github.com/davecgh/go-spew v1.0.1-0.20160907170601-6d212800a42e/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15 | github.com/docker/distribution v2.6.0-rc.1.0.20170216011216-4f87c800734f+incompatible h1:g9rCimuvXuFhUcv+hOgbOq0VUCzm4WOUrSnhSgf4kWA=
16 | github.com/docker/distribution v2.6.0-rc.1.0.20170216011216-4f87c800734f+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
17 | github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
18 | github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
19 | github.com/docker/go-connections v0.2.2-0.20170203235624-7da10c8c50ca h1:y8QNCXaQavmcy33wXLFEopYBHGmpUdEAmGw390KAUpQ=
20 | github.com/docker/go-connections v0.2.2-0.20170203235624-7da10c8c50ca/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
21 | github.com/docker/go-units v0.3.2 h1:Kjm80apys7gTtfVmCvVY8gwu10uofaFSrmAKOVrtueE=
22 | github.com/docker/go-units v0.3.2/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
23 | github.com/fatih/color v1.5.0 h1:vBh+kQp8lg9XPr56u1CPrWjFXtdphMoGWVHr9/1c+A0=
24 | github.com/fatih/color v1.5.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
25 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
26 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
27 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
28 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
29 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
30 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
31 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
32 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
33 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
34 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
35 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
36 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
37 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
38 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
39 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
40 | github.com/google/go-github v0.0.0-20160619221136-1c08387e4c91 h1:1enRYvgHBPJJNoSeAvXb13qxJylZeknckj2pvir470A=
41 | github.com/google/go-github v0.0.0-20160619221136-1c08387e4c91/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
42 | github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=
43 | github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
44 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
45 | github.com/mattn/go-colorable v0.0.8-0.20170210172801-5411d3eea597 h1:Zw9aNQ5CQ3SERpCiZrPh4vdWcBWogoEd5m8AFf2QWVs=
46 | github.com/mattn/go-colorable v0.0.8-0.20170210172801-5411d3eea597/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
47 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
48 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
49 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
50 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
51 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
52 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
53 | github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
54 | github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
55 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
56 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
57 | github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
58 | github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
59 | github.com/opencontainers/go-digest v1.0.0-rc0 h1:YHPGfp+qlmg7loi376Jk5jNEgjgUUIdXGFsel8aFHnA=
60 | github.com/opencontainers/go-digest v1.0.0-rc0/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
61 | github.com/pivotal-golang/archiver v0.0.0-20170206191640-59f15fd17404 h1:yeetD1u44OGkOLoO+KuaeMvqqUlG1DUAOJ2un459sFk=
62 | github.com/pivotal-golang/archiver v0.0.0-20170206191640-59f15fd17404/go.mod h1:ar7bnlyygrresc3WEZSF4cKObxnIx2LLyLfiP7caoA0=
63 | github.com/pkg/errors v0.8.1-0.20161029093637-248dadf4e906 h1:BKfCEBHnHoXswNe0Btj/zOfiyn40z6qOti8SeLbQgdM=
64 | github.com/pkg/errors v0.8.1-0.20161029093637-248dadf4e906/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
65 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 h1:GD+A8+e+wFkqje55/2fOVnZPkoDIu1VooBWfNrnY8Uo=
66 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
67 | github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE=
68 | github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
69 | github.com/stretchr/testify v1.1.4 h1:ToftOQTytwshuOSj6bDSolVUa3GINfJP/fg3OkkOzQQ=
70 | github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
71 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
72 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
73 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
74 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
75 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
76 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
77 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
78 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
79 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
80 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
81 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
82 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
83 | golang.org/x/oauth2 v0.0.0-20170214231824-b9780ec78894 h1:0B6wMEHW7F6Hzd0UV/jBov9bd6ssDnN5pIJokDjMxh8=
84 | golang.org/x/oauth2 v0.0.0-20170214231824-b9780ec78894/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
85 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
86 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
87 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
88 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
89 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
90 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
91 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
92 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
93 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
94 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
95 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
96 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
97 | golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
98 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
99 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
100 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
101 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
102 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
103 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
104 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
105 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
106 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
107 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
108 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
109 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
110 | google.golang.org/appengine v1.0.1-0.20170206203024-2e4a801b39fc h1:/VptPD7on9gLG0uID5zYhnk2DLAMJQeCjXZBSKdGRTE=
111 | google.golang.org/appengine v1.0.1-0.20170206203024-2e4a801b39fc/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
112 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
113 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
114 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
115 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
116 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
117 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
118 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
119 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
120 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
121 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
122 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
123 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
124 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
125 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
126 |
--------------------------------------------------------------------------------
/install.ps1:
--------------------------------------------------------------------------------
1 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
2 | $ErrorActionPreference = "Stop"
3 |
4 | function downloadDvm([string] $dvmDir) {
5 | $webClient = New-Object net.webclient
6 |
7 | echo "Downloading dvm.ps1..."
8 | $webClient.DownloadFile("https://howtowhale.github.io/dvm/downloads/latest/dvm.ps1", "$dvmDir\dvm.ps1")
9 |
10 | echo "Downloading dvm.cmd..."
11 | $webClient.DownloadFile("https://howtowhale.github.io/dvm/downloads/latest/dvm.cmd", "$dvmDir\dvm.cmd")
12 |
13 | echo "Downloading dvm-helper.exe..."
14 | $tmpDir = Join-Path $dvmDir .tmp
15 | if( !(Test-Path $tmpDir) ) {
16 | mkdir $tmpDir > $null
17 | }
18 |
19 | # Ensure dvm-helper directory exists
20 | $dvmHelperDir = Join-Path $dvmDir dvm-helper
21 | if( !(Test-Path $dvmHelperDir ) ) {
22 | mkdir $dvmHelperDir > $null
23 | }
24 |
25 |
26 | # Detect x86 vs. x64
27 | if( [System.Environment]::Is64BitOperatingSystem ) { $arch = "x86_64" } else { $arch = "i686"}
28 |
29 | # Download latest release
30 | $webClient.DownloadFile("https://howtowhale.github.io/dvm/downloads/latest/Windows/$arch/dvm-helper.exe", "$tmpDir\dvm-helper.exe")
31 | $webClient.DownloadFile("https://howtowhale.github.io/dvm/downloads/latest/Windows/$arch/dvm-helper.exe.sha256", "$tmpDir\dvm-helper.exe.256")
32 |
33 | # Verify the binary was downloaded successfully
34 | $checksum = (cat $tmpDir\dvm-helper.exe.256).Split(' ')[0]
35 | $hash = (Get-FileHash $tmpDir\dvm-helper.exe).Hash
36 | if([string]::Compare($checksum, $hash, $true) -ne 0) {
37 | $host.ui.WriteErrorLine("DANGER! The downloaded dvm-helper binary, $tmpDir\dvm-helper.exe, does not match its checksum!")
38 | $host.ui.WriteErrorLine("CHECKSUM: $checksum")
39 | $host.ui.WriteErrorLine("HASH: $hash")
40 | return 1
41 | }
42 |
43 | mv -force "$tmpDir\dvm-helper.exe" "$dvmDir\dvm-helper\dvm-helper.exe"
44 | }
45 |
46 | function installDvm()
47 | {
48 | $dvmDir = $env:DVM_DIR
49 | if( $dvmDir -eq $null ) {
50 | $dvmDir = "$env:USERPROFILE\.dvm"
51 | }
52 |
53 | if( !(Test-Path $dvmDir) ) {
54 | mkdir $dvmDir > $null
55 | }
56 |
57 | downloadDvm $dvmDir
58 |
59 | echo ""
60 | echo "Docker Version Manager (dvm) has been installed to $dvmDir"
61 | echo ""
62 | echo "PowerShell Users: Run the following command to start using dvm. Then add it to your PowerShell profile to complete the installation."
63 | echo "`t. '$dvmDir\dvm.ps1'"
64 | echo ""
65 | echo "CMD Users: Run the first command to start using dvm. Then run the second command to add dvm to your PATH to complete the installation."
66 | echo "`t1. PATH=%PATH%;$dvmDir"
67 | echo "`t2. setx PATH `"%PATH%;$dvmDir`""
68 | }
69 |
70 | if($PSVersionTable -eq $null -or $PSVersionTable.PSVersion.Major -lt 4){
71 | Write-Output "dvm requires PowerShell version 4 or higher."
72 | Exit 1
73 | }
74 |
75 | installDvm
76 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | { # this ensures the entire script is downloaded #
4 |
5 | set -e
6 |
7 | dvm_has() {
8 | type "$1" > /dev/null 2>&1
9 | }
10 |
11 | dvm_checksum() {
12 | local DVM_CHECKSUM
13 | if dvm_has "sha256sum" && ! dvm_is_alias "sha256sum"; then
14 | DVM_CHECKSUM="$(command sha256sum "$1" | command awk '{print $1}')"
15 | elif dvm_has "shasum" && ! dvm_is_alias "shasum"; then
16 | DVM_CHECKSUM="$(shasum -a 256 "$1" | command awk '{print $1}')"
17 | else
18 | echo "Unaliased sha256sum, shasum not found." >&2
19 | return 2
20 | fi
21 |
22 | if [ "_$DVM_CHECKSUM" = "_$2" ]; then
23 | return
24 | elif [ -z "$2" ]; then
25 | echo 'Checksums empty'
26 | return
27 | else
28 | echo 'Checksums do not match.' >&2
29 | return 1
30 | fi
31 | }
32 |
33 | dvm_download() {
34 | if dvm_has "curl"; then
35 | curl -q $*
36 | elif dvm_has "wget"; then
37 | # Emulate curl with wget
38 | ARGS=$(echo "$*" | command sed -e 's/--progress-bar /--progress=bar /' \
39 | -e 's/-L //' \
40 | -e 's/-I /--server-response /' \
41 | -e 's/-s /-q /' \
42 | -e 's/-o /-O /')
43 | eval wget $ARGS
44 | fi
45 | }
46 |
47 | install_dvm_helper() {
48 | local url
49 | local bin
50 |
51 | # Detect mac vs. linux and x86 vs. x64
52 | DVM_OS=$(uname -s)
53 | DVM_ARCH=$(uname -m)
54 |
55 | # Download latest release
56 | mkdir -p "$DVM_DIR/dvm-helper"
57 | bin="$DVM_DIR/dvm-helper/dvm-helper"
58 | url=https://howtowhale.github.io/dvm/downloads/latest/$DVM_OS/$DVM_ARCH/dvm-helper
59 | dvm_download -L --progress-bar $url -o "$bin"
60 | chmod u+x $bin
61 | }
62 |
63 |
64 | if [ -z "$DVM_DIR" ]; then
65 | DVM_DIR="$HOME/.dvm"
66 | fi
67 |
68 | if [ ! -d "$DVM_DIR" ]; then
69 | mkdir $DVM_DIR
70 | fi
71 |
72 | echo "Downloading dvm.sh..."
73 | dvm_download -L --progress-bar https://howtowhale.github.io/dvm/downloads/latest/dvm.sh -o $DVM_DIR/dvm.sh
74 |
75 | echo "Downloading bash_completion"
76 | dvm_download -L --progress-bar https://howtowhale.github.io/dvm/downloads/latest/bash_completion -o $DVM_DIR/bash_completion
77 |
78 | echo "Downloading dvm-helper..."
79 | install_dvm_helper
80 |
81 | echo ""
82 | echo "Docker Version Manager (dvm) has been installed to ${DVM_DIR}"
83 | echo "Run the following command to start using dvm. Then add it to your bash profile (e.g. ~/.bashrc or ~/.bash_profile) to complete the installation."
84 | echo ""
85 | echo " source ${DVM_DIR}/dvm.sh"
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/script/deploy-gh-pages.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | rm -fr _deploy/ &> /dev/null
5 | git clone --branch gh-pages --depth 1 git@github.com:howtowhale/dvm.git _deploy
6 |
7 | cp -R bin/dvm/$VERSION _deploy/downloads/
8 | cp -R bin/dvm/$PERMALINK _deploy/downloads/
9 | cd _deploy/
10 | git add downloads/
11 |
12 | git config user.name "Travis CI"
13 | git config user.email "travis@travis-ci.org"
14 | git commit -m "Publish $VERSION from Travis Build #${TRAVIS_BUILD_NUMBER:-1}"
15 | git push
16 |
--------------------------------------------------------------------------------
/script/travis.deploy.pem.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/howtowhale/dvm/4ceba054e97cbb3a1329db1c08cc350807d5ea5a/script/travis.deploy.pem.enc
--------------------------------------------------------------------------------