├── .github
├── .typo-ci.yml
├── FUNDING.yml
├── dependabot.yml
├── pre-commit
├── release-drafter.yml
├── settings.yml
└── workflows
│ ├── backup.yaml
│ ├── coverage-report.yaml
│ ├── pull-request.yaml
│ ├── release-drafter.yml
│ └── release.yaml
├── .gitignore
├── .gitpod.yml
├── .goreleaser.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── OWNERS
├── README-zh.md
├── README.md
├── action.yml
├── cmd
├── doc.go
├── fetch.go
├── fetch_test.go
├── get.go
├── get_test.go
├── install.go
├── install_test.go
├── root.go
├── root_test.go
├── search.go
├── search_test.go
├── setup.go
├── setup_test.go
├── testdata
│ └── magnet.html
├── util.go
└── util_test.go
├── codecov.yml
├── go.mod
├── go.sum
├── hack
├── README.md
├── deploy.yaml
└── init.sh
├── main.go
├── mock
└── mhttp
│ └── roundtripper.go
├── pkg
├── common
│ ├── doc.go
│ ├── util.go
│ └── util_test.go
├── compress
│ ├── bzip2.go
│ ├── doc.go
│ ├── gzip.go
│ ├── testdata
│ │ ├── simple.tar.gz
│ │ └── simple.zip
│ ├── types.go
│ ├── types_test.go
│ ├── xz.go
│ └── zip.go
├── doc.go
├── error.go
├── error_test.go
├── http.go
├── installer
│ ├── check.go
│ ├── check_test.go
│ ├── cmd.go
│ ├── cmd_test.go
│ ├── doc.go
│ ├── fetch.go
│ ├── fetch_test.go
│ ├── process.go
│ ├── process_test.go
│ └── types.go
├── log
│ ├── doc.go
│ ├── level.go
│ └── level_test.go
├── net
│ ├── doc.go
│ ├── error.go
│ ├── error_test.go
│ ├── fake.go
│ ├── fake_test.go
│ ├── http.go
│ ├── http_test.go
│ ├── multi_thread.go
│ ├── progress.go
│ ├── retry_client.go
│ ├── retry_client_test.go
│ └── setup_test.go
├── os
│ ├── apk
│ │ ├── common.go
│ │ ├── common_test.go
│ │ └── doc.go
│ ├── apt
│ │ ├── asciinema.go
│ │ ├── bash-completion.go
│ │ ├── common.go
│ │ ├── common_test.go
│ │ ├── conntrack.go
│ │ ├── doc.go
│ │ ├── docker.go
│ │ ├── ffmpeg.go
│ │ ├── git.go
│ │ ├── golang.go
│ │ ├── init.go
│ │ ├── init_test.go
│ │ ├── resource
│ │ │ └── kubernetes.list
│ │ ├── socat.go
│ │ └── vim.go
│ ├── brew
│ │ ├── common.go
│ │ ├── common_test.go
│ │ └── doc.go
│ ├── core
│ │ ├── doc.go
│ │ ├── fake.go
│ │ ├── fake_test.go
│ │ └── types.go
│ ├── dnf
│ │ ├── common.go
│ │ ├── common_test.go
│ │ ├── doc.go
│ │ ├── docker.go
│ │ ├── init.go
│ │ └── init_test.go
│ ├── doc.go
│ ├── docker
│ │ ├── bitbucket.go
│ │ ├── doc.go
│ │ ├── init.go
│ │ └── init_test.go
│ ├── fake
│ │ ├── doc.go
│ │ ├── fake.go
│ │ └── fake_test.go
│ ├── generic
│ │ ├── common.go
│ │ ├── common_test.go
│ │ └── doc.go
│ ├── generic_installer.go
│ ├── generic_installer_test.go
│ ├── installer.go
│ ├── installer_test.go
│ ├── npm
│ │ ├── common.go
│ │ ├── common_test.go
│ │ └── doc.go
│ ├── scoop
│ │ ├── common.go
│ │ └── doc.go
│ ├── snap
│ │ ├── common.go
│ │ ├── common_test.go
│ │ └── doc.go
│ ├── winget
│ │ ├── common.go
│ │ └── doc.go
│ └── yum
│ │ ├── bash-completion.go
│ │ ├── common.go
│ │ ├── common_test.go
│ │ ├── conntrack.go
│ │ ├── doc.go
│ │ ├── docker.go
│ │ ├── git.go
│ │ ├── golang.go
│ │ ├── init.go
│ │ ├── init_test.go
│ │ ├── resource
│ │ ├── go-repo.repo
│ │ └── kubernetes.repo
│ │ ├── socat.go
│ │ └── vim.go
├── progress.go
├── release.go
├── setup_test.go
└── version
│ ├── doc.go
│ ├── parser.go
│ └── parser_test.go
└── wix.json
/.github/.typo-ci.yml:
--------------------------------------------------------------------------------
1 | dictionaries:
2 | - en
3 | - en_GB
4 | exclude_fiels:
5 | - ".github/**/*"
6 | - "go.mod"
7 | - "go.sum"
8 | - Makefile
9 | excluded_words:
10 | - KubeSphere
11 | - kubespheredev
12 | - minio
13 | - jcli
14 | - ioutil
15 | - Unmarshal
16 | - Errorf
17 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | open_collective: http-downloader
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | pull-request-branch-name:
8 | separator: "-"
9 | - package-ecosystem: "gomod"
10 | directory: "/"
11 | schedule:
12 | interval: "daily"
13 | pull-request-branch-name:
14 | separator: "-"
15 |
--------------------------------------------------------------------------------
/.github/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | make pre-commit
3 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | # Configuration for Release Drafter: https://github.com/toolmantim/release-drafter
2 | name-template: 'v$NEXT_PATCH_VERSION 🌈'
3 | tag-template: 'v$NEXT_PATCH_VERSION'
4 | version-template: $MAJOR.$MINOR.$PATCH
5 | # Emoji reference: https://gitmoji.carloscuesta.me/
6 | categories:
7 | - title: '🚀 Features'
8 | labels:
9 | - 'feature'
10 | - 'enhancement'
11 | - 'kind/feature'
12 | - title: '🐛 Bug Fixes'
13 | labels:
14 | - 'fix'
15 | - 'bugfix'
16 | - 'bug'
17 | - 'regression'
18 | - 'kind/bug'
19 | - title: 📝 Documentation updates
20 | labels:
21 | - documentation
22 | - 'kind/doc'
23 | - title: 👻 Maintenance
24 | labels:
25 | - chore
26 | - dependencies
27 | - 'kind/chore'
28 | - 'kind/dep'
29 | - title: 🚦 Tests
30 | labels:
31 | - test
32 | - tests
33 | exclude-labels:
34 | - reverted
35 | - no-changelog
36 | - skip-changelog
37 | - invalid
38 | change-template: '* $TITLE (#$NUMBER) @$AUTHOR'
39 | template: |
40 | ## What’s Changed
41 |
42 | $CHANGES
43 |
--------------------------------------------------------------------------------
/.github/settings.yml:
--------------------------------------------------------------------------------
1 | repository:
2 | name: http-downloader
3 | description: HTTP download tool
4 | homepage: https://github.com/linuxsuren
5 | private: false
6 | has_issues: true
7 | has_wiki: false
8 | has_downloads: false
9 | default_branch: master
10 | allow_squash_merge: true
11 | allow_merge_commit: true
12 | allow_rebase_merge: true
13 | labels:
14 | - name: newbie
15 | color: abe7f4
16 | description: 新手上路
17 | - name: bug
18 | color: d73a4a
19 | description: Something isn't working
20 | - name: feature
21 | color: ffc6a3
22 | - name: enhancement
23 | color: a2eeef
24 | description: New feature or request
25 | - name: help wanted
26 | color: 008672
27 | description: Extra attention is needed
28 | - name: bugfix
29 | color: 0412d6
30 | - name: regression
31 | color: c5def5
32 | - name: documentation
33 | color: 5ce05e
34 | - name: Hacktoberfest
35 | description: More details from https://hacktoberfest.digitalocean.com/
36 | color: 5ce05e
37 | - name: test
38 | color: c2c2fc
39 | - name: chore
40 | color: c2c2fc
41 | - name: dependencies
42 | color: 0366d6
43 | description: Pull requests that update a dependency file
44 | - name: no-changelog
45 | color: c2c2fc
46 | - name: kind/bug
47 | color: c2c2fc
48 | - name: kind/feature
49 | color: c2c2fc
50 | - name: kind/doc
51 | color: c2c2fc
52 | - name: kind/dep
53 | color: c2c2fc
54 | - name: kind/chore
55 | color: c2c2fc
56 | branches:
57 | - name: master
58 | protection:
59 | required_pull_request_reviews:
60 | required_approving_review_count: 1
61 | dismiss_stale_reviews: true
62 | require_code_owner_reviews: true
63 | dismissal_restrictions:
64 | users: []
65 | teams: []
66 | required_status_checks:
67 | strict: true
68 | contexts: []
69 | enforce_admins: false
70 | restrictions:
71 | users: []
72 | teams: []
73 |
--------------------------------------------------------------------------------
/.github/workflows/backup.yaml:
--------------------------------------------------------------------------------
1 | name: Backup Git repository
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - master
8 |
9 | jobs:
10 | BackupGit:
11 | runs-on: ubuntu-22.04
12 | steps:
13 | - uses: actions/checkout@v3.6.0
14 | - name: backup
15 | uses: jenkins-zh/git-backup-actions@v0.0.8
16 | env:
17 | GIT_DEPLOY_KEY: ${{ secrets.GIT_DEPLOY_KEY }}
18 | TARGET_GIT: "git@gitee.com:linuxsuren/http-downloader.git"
19 |
--------------------------------------------------------------------------------
/.github/workflows/coverage-report.yaml:
--------------------------------------------------------------------------------
1 | name: Coverage Report
2 |
3 | on:
4 | - push
5 | - pull_request
6 | - release
7 |
8 | jobs:
9 | TestAndReport:
10 | runs-on: ubuntu-22.04
11 | steps:
12 | - name: Set up Go
13 | uses: actions/setup-go@v4
14 | with:
15 | go-version: 1.19.x
16 | - uses: actions/checkout@v3.6.0
17 | - name: Test
18 | run: |
19 | go test ./... -coverprofile coverage.out
20 | - name: Report
21 | env:
22 | CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
23 | run: |
24 | bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r coverage.out --force-coverage-parser go
25 | - name: Upload coverage to Codecov
26 | uses: codecov/codecov-action@v3.1.4
27 | with:
28 | token: ${{ secrets.CODECOV_TOKEN }}
29 | files: coverage.out
30 | flags: unittests
31 | name: codecov-umbrella
32 | fail_ci_if_error: true
33 |
--------------------------------------------------------------------------------
/.github/workflows/pull-request.yaml:
--------------------------------------------------------------------------------
1 | name: Pull Request Build
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - master
7 | env:
8 | REGISTRY: ghcr.io
9 |
10 | jobs:
11 | build:
12 | name: Build
13 | runs-on: ubuntu-22.04
14 | steps:
15 | - name: Set up Go
16 | uses: actions/setup-go@v4
17 | with:
18 | go-version: 1.19
19 | id: go
20 | - name: Check out code into the Go module directory
21 | uses: actions/checkout@v3.6.0
22 | - name: Run GoReleaser
23 | uses: goreleaser/goreleaser-action@v4.4.0
24 | with:
25 | github_token: ${{ secrets.GH_PUBLISH_SECRETS }}
26 | version: v1.14.0
27 | args: release --skip-publish --rm-dist --snapshot
28 | - name: Test against the cmd
29 | run: |
30 | sudo cp ./release/http-downloader_linux_amd64_v1/hd /usr/local/bin
31 |
32 | # test with fullpath
33 | sudo hd install jenkins-zh/jenkins-cli/jcli
34 | jcli version
35 |
36 | # test with simple path
37 | sudo hd i mde
38 |
39 | # test with specific version
40 | sudo hd i mde@v0.0.4 -f
41 | - name: Run Trivy vulnerability scanner
42 | uses: aquasecurity/trivy-action@0.2.1
43 | if: github.event_name == 'pull_request'
44 | with:
45 | scan-type: 'fs'
46 | format: 'table'
47 | exit-code: '1'
48 | ignore-unfixed: true
49 | vuln-type: 'os,library'
50 | severity: 'CRITICAL,HIGH'
51 |
52 | GoLint:
53 | name: Lint
54 | runs-on: ubuntu-22.04
55 | steps:
56 | - name: Set up Go
57 | uses: actions/setup-go@v4
58 | with:
59 | go-version: 1.19
60 | id: go
61 | - name: Check out code into the Go module directory
62 | uses: actions/checkout@v3.6.0
63 | - name: Go-linter-1
64 | uses: Jerome1337/golint-action@v1.0.2
65 | with:
66 | golint-path: ./...
67 | Security:
68 | name: Security
69 | runs-on: ubuntu-22.04
70 | env:
71 | GO111MODULE: on
72 | steps:
73 | - name: Checkout Source
74 | uses: actions/checkout@v3.6.0
75 | - name: Run Gosec Security Scanner
76 | uses: securego/gosec@master
77 | with:
78 | args: '-exclude=G402,G204,G304,G110,G306,G107 ./...'
79 | CodeQL:
80 | name: CodeQL
81 | runs-on: ubuntu-22.04
82 | env:
83 | GO111MODULE: on
84 | steps:
85 | - name: Checkout Source
86 | uses: actions/checkout@v3.6.0
87 | - name: Initialize CodeQL
88 | uses: github/codeql-action/init@v1
89 | with:
90 | languages: go
91 | - name: Perform CodeQL Analysis
92 | uses: github/codeql-action/analyze@v1
93 | MarkdownLinkCheck:
94 | name: MarkdownLinkCheck
95 | runs-on: ubuntu-22.04
96 | steps:
97 | - uses: actions/checkout@v3.6.0
98 | - uses: gaurav-nelson/github-action-markdown-link-check@1.0.13
99 | with:
100 | use-verbose-mode: 'yes'
101 |
102 | image:
103 | runs-on: ubuntu-22.04
104 | steps:
105 | - name: Checkout
106 | uses: actions/checkout@v4
107 | with:
108 | fetch-tags: true
109 | fetch-depth: 0
110 | - name: Setup Docker buildx
111 | uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
112 | - name: Extract Docker metadata
113 | id: meta
114 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
115 | with:
116 | images: ${{ env.REGISTRY }}/linuxsuren/hd
117 | - name: Build and push Docker image
118 | id: build-and-push
119 | uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
120 | with:
121 | context: .
122 | push: false
123 | tags: ${{ steps.meta.outputs.tags }}
124 | labels: ${{ steps.meta.outputs.labels }}
125 | platforms: linux/amd64,linux/arm64
126 | cache-from: type=gha
127 | cache-to: type=gha,mode=max
128 | build-args: VERSION=${{ steps.vars.outputs.tag }}
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | UpdateReleaseDraft:
10 | runs-on: ubuntu-22.04
11 | steps:
12 | - uses: release-drafter/release-drafter@v5
13 | env:
14 | GITHUB_TOKEN: ${{ secrets.GH_PUBLISH_SECRETS }}
15 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 | env:
8 | REGISTRY: ghcr.io
9 |
10 | jobs:
11 | goreleaser:
12 | runs-on: ubuntu-22.04
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v3.6.0
16 | with:
17 | fetch-tags: true
18 | fetch-depth: 0
19 | - name: Set output
20 | id: vars
21 | run: echo "tag=$(git describe --tags)" >> $GITHUB_OUTPUT
22 | - name: Set up Go
23 | uses: actions/setup-go@v4
24 | with:
25 | go-version: 1.19
26 | - name: Image Registry Login
27 | run: |
28 | docker login --username ${{ secrets.DOCKER_HUB_USER }} --password ${{secrets.DOCKER_HUB_TOKEN}}
29 | docker login ${{ env.REGISTRY }}/linuxsuren --username linuxsuren --password ${{secrets.GH_PUBLISH_SECRETS}}
30 | - name: Run GoReleaser
31 | uses: goreleaser/goreleaser-action@v4.4.0
32 | with:
33 | github_token: ${{ secrets.GH_PUBLISH_SECRETS }}
34 | version: v1.14.0
35 | args: release --rm-dist
36 | env:
37 | GITHUB_TOKEN: ${{ secrets.GH_PUBLISH_SECRETS }}
38 | - name: Upload via oras
39 | run: |
40 | export TAG=${{ steps.vars.outputs.tag }}
41 | TAG=${TAG#v}
42 | oras push docker.io/linuxsuren/hd:$TAG release
43 | oras push ${{ env.REGISTRY }}/linuxsuren/hd:$TAG release
44 |
45 | image:
46 | runs-on: ubuntu-22.04
47 | steps:
48 | - name: Checkout
49 | uses: actions/checkout@v4
50 | with:
51 | fetch-tags: true
52 | fetch-depth: 0
53 | - name: Setup Docker buildx
54 | uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
55 | - name: Log into registry ${{ env.REGISTRY }}
56 | if: github.event_name != 'pull_request'
57 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
58 | with:
59 | registry: ${{ env.REGISTRY }}
60 | username: ${{ github.actor }}
61 | password: ${{ secrets.GH_PUBLISH_SECRETS }}
62 | - name: Extract Docker metadata
63 | id: meta
64 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
65 | with:
66 | images: ${{ env.REGISTRY }}/linuxsuren/hd
67 | - name: Build and push Docker image
68 | id: build-and-push
69 | uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
70 | with:
71 | context: .
72 | push: ${{ github.event_name != 'pull_request' }}
73 | tags: ${{ steps.meta.outputs.tags }}
74 | labels: ${{ steps.meta.outputs.labels }}
75 | platforms: linux/amd64,linux/arm64
76 | cache-from: type=gha
77 | cache-to: type=gha,mode=max
78 | build-args: VERSION=${{ steps.vars.outputs.tag }}
79 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | bin/
3 |
4 | */**/*.xml
5 | coverage.out
6 | node_modules
7 | release/
8 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | - init: make build
3 | command: make copy
4 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | # Official documentation at http://goreleaser.com
2 | project_name: http-downloader
3 | builds:
4 | - env:
5 | - CGO_ENABLED=0
6 | binary: hd
7 | goarch:
8 | - amd64
9 | - arm64
10 | - arm
11 | goarm:
12 | - 7
13 | - 6
14 | goos:
15 | - windows
16 | - linux
17 | - darwin
18 | ignore:
19 | - goos: windows
20 | goarch: arm
21 | - goos: windows
22 | goarch: arm64
23 | - goos: darwin
24 | goarch: arm
25 | ldflags:
26 | - -X github.com/linuxsuren/cobra-extension/version.version={{.Version}}
27 | - -X github.com/linuxsuren/cobra-extension/version.commit={{.ShortCommit}}
28 | - -X github.com/linuxsuren/cobra-extension/version.date={{.Date}}
29 | - -w
30 | - -s
31 | dist: release
32 | archives:
33 | - name_template: "{{ .Binary }}-{{ .Os }}-{{ .Arch }}{{ .Arm }}"
34 | replacements:
35 | darwin: darwin
36 | linux: linux
37 | windows: windows
38 | amd64: amd64
39 | arm64: arm64
40 | format_overrides:
41 | - goos: windows
42 | format: zip
43 | files:
44 | - README.md
45 | checksum:
46 | name_template: 'checksums.txt'
47 | snapshot:
48 | name_template: "{{ .Tag }}-next-{{.ShortCommit}}"
49 | changelog:
50 | skip: true
51 | sort: asc
52 | filters:
53 | exclude:
54 | - '^docs:'
55 | - '^test:'
56 | brews:
57 | - name: hd
58 | tap:
59 | owner: linuxsuren
60 | name: homebrew-linuxsuren
61 | folder: Formula
62 | homepage: "https://github.com/linuxsuren/http-downloader"
63 | description: HTTP download tool
64 | dependencies:
65 | - name: bash-completion
66 | type: optional
67 | test: |
68 | version_output = shell_output("#{bin}/hd version")
69 | assert_match version.to_s, version_output
70 | install: |
71 | bin.install name
72 |
73 | prefix.install_metafiles
74 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM docker.io/golang:1.19 AS builder
2 |
3 | WORKDIR /workspace
4 | COPY cmd/ cmd/
5 | COPY hack/ hack/
6 | COPY mock/ mock/
7 | COPY pkg/ pkg/
8 | COPY main.go .
9 | COPY README.md README.md
10 | COPY go.mod go.mod
11 | COPY go.sum go.sum
12 | RUN CGO_ENABLED=0 go build -ldflags "-w -s" -o /usr/local/bin/hd .
13 |
14 | FROM alpine:3.10
15 |
16 | COPY --from=builder /usr/local/bin/hd /usr/local/bin/hd
17 | RUN hd fetch
18 |
19 | CMD ["hd"]
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Zhao Xiaojie
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build: fmt test
2 | export GOPROXY=https://goproxy.io
3 | CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/hd
4 |
5 | build-windows:
6 | GOOS=windows CGO_ENABLED=0 go build -ldflags "-w -s" -o bin/windows/hd.exe
7 | build-linux: fmt lint build-linux-no-check
8 | build-linux-no-check:
9 | export GOPROXY=https://goproxy.io
10 | CGO_ENABLED=0 GOOS=linux go build -ldflags "-w -s" -o bin/linux/hd
11 | upx bin/linux/hd
12 |
13 | test: fmt
14 | go test ./... -coverprofile coverage.out
15 | pre-commit: fmt test build
16 | cp-pre-commit:
17 | cp .github/pre-commit .git/hooks/pre-commit
18 | run:
19 | go run main.go
20 |
21 | fmt:
22 | go fmt ./...
23 |
24 | lint:
25 | golangci-lint run ./...
26 |
27 | copy: build
28 | sudo cp bin/hd /usr/local/bin/
29 |
30 | init: gen-mock
31 | gen-mock:
32 | go get github.com/golang/mock/gomock
33 | go install github.com/golang/mock/mockgen
34 | mockgen -destination ./mock/mhttp/roundtripper.go -package mhttp net/http RoundTripper
35 |
36 | update:
37 | git fetch
38 | git reset --hard origin/$(shell git branch --show-current)
39 | goreleaser:
40 | goreleaser build --snapshot --rm-dist
41 |
--------------------------------------------------------------------------------
/OWNERS:
--------------------------------------------------------------------------------
1 | approvers:
2 | - linuxsuren
3 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 | [](https://goreportcard.com/report/linuxsuren/github-go)
2 | [](https://godoc.org/github.com/linuxsuren/http-downloader)
3 | [](https://github.com/linuxsuren/github-go/graphs/contributors)
4 | [](https://github.com/linuxsuren/github-go/releases/latest)
5 | 
6 |
7 | # 入门
8 |
9 | `hd` 是一个基于 HTTP 协议的下载工具。
10 |
11 | 通过命令:`brew install linuxsuren/linuxsuren/hd` 来安装
12 |
13 | 或者,对于 Linux 用户可以直接通过命令下载:
14 | ```shell
15 | curl -L https://github.com/linuxsuren/http-downloader/releases/latest/download/hd-linux-amd64.tar.gz | tar xzv
16 | mv hd /usr/local/bin
17 | ```
18 |
19 | 想要浏览该项目的代码吗?[GitPod](https://gitpod.io/#https://github.com/linuxsuren/http-downloader) 绝对可以帮助你!
20 |
21 | # 用法
22 |
23 | ```shell
24 | hd get https://github.com/jenkins-zh/jenkins-cli/releases/latest/download/jcli-linux-amd64.tar.gz --thread 6
25 | ```
26 |
27 | 或者,用一个更加简便的办法:
28 |
29 | ```shell
30 | hd get jenkins-zh/jenkins-cli/jcli -t 6
31 | ```
32 |
33 | 获取,你也可以安装一个来自 GitHub 的软件包:
34 |
35 | ```shell
36 | hd install jenkins-zh/jenkins-cli/jcli -t 6
37 | ```
38 |
39 | 或者,你也可以从 GitHub 上下载预发布的二进制包:
40 |
41 | ```shell
42 | hd get --pre ks
43 | ```
44 |
45 | # 功能
46 |
47 | * 基于 HTTP 协议下载文件的 Golang 工具库
48 | * 多线程
49 | * 断点续传 (TODO)
50 | * 对 GitHub release 文件下载(安装)友好
51 |
52 | ## 使用多阶段构建
53 | 你想要在 Docker 构建中下载工具吗?这个很容易的,请查看下面的例子:
54 |
55 | ```dockerfile
56 | FROM ghcr.io/linuxsuren/hd:v0.0.42 as downloader
57 | RUN hd install kubesphere-sigs/ks@v0.0.50
58 |
59 | FROM alpine:3.10
60 | COPY --from=downloader /usr/local/bin/ks /usr/local/bin/ks
61 | CMD ["ks"]
62 | ```
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://gitpod.io/#https://github.com/LinuxSuRen/http-downloader)
2 | [](https://goreportcard.com/report/linuxsuren/github-go)
3 | [](https://godoc.org/github.com/linuxsuren/http-downloader)
4 | [](https://app.codacy.com/gh/LinuxSuRen/http-downloader/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
5 | [](https://app.codacy.com/gh/LinuxSuRen/http-downloader/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
6 | [](https://codecov.io/gh/LinuxSuRen/http-downloader)
7 | [](https://github.com/linuxsuren/github-go/graphs/contributors)
8 | [](https://github.com/linuxsuren/github-go/releases/latest)
9 | 
10 | [](https://github.com/LinuxSuRen/open-source-best-practice)
11 |
12 | # Get started
13 | `hd` is a HTTP download tool.
14 |
15 | Install it via: `brew install linuxsuren/linuxsuren/hd`
16 |
17 | Or download it directly (for Linux):
18 | ```
19 | curl https://linuxsuren.github.io/tools/install.sh|bash
20 | ```
21 |
22 | Or download it via proxy:
23 | ```
24 | curl https://gitee.com/linuxsuren/tools/raw/master/install-zh.sh|bash
25 | ```
26 |
27 | for Windows users (you might need to add this program into the Windows Defence exclude list):
28 | ```
29 | winget install 'HTTP downloader'
30 | ```
31 |
32 | Want to go through the code? [GitPod](https://gitpod.io/#https://github.com/linuxsuren/http-downloader) definitely can help you.
33 |
34 | # Usage
35 |
36 | ## Download
37 | ```shell
38 | hd get https://github.com/jenkins-zh/jenkins-cli/releases/latest/download/mde-linux-amd64.tar.gz --thread 6
39 | ```
40 |
41 | Or use a simple way instead of typing the whole URL:
42 |
43 | ```shell
44 | hd get mde
45 | ```
46 |
47 | Or you might want to download a pre-released binary package from GitHub:
48 |
49 | ```shell
50 | hd get --pre ks
51 | ```
52 |
53 | ## Install
54 | You can also install a package from GitHub:
55 |
56 | ```shell
57 | #!title: Install mde with specific threads
58 | hd install mde -t 6
59 | ```
60 |
61 | or install by a category name:
62 |
63 | ```shell
64 | hd install --category security
65 | ```
66 |
67 | ## Search
68 | hd can download or install via the format of `$org/$repo`. If you find that it's not working. It might because of there's
69 | no record in [hd-home](https://github.com/LinuxSuRen/hd-home). You're welcome to help us to maintain it.
70 |
71 | When you first run it, please init via: `hd fetch`
72 |
73 | then you can search it by a keyword: `hd search jenkins`
74 |
75 | ## Use multi-stage builds
76 | Do you want to download tools in the Docker builds? It's pretty easy. Please see the following example:
77 |
78 | ```dockerfile
79 | FROM ghcr.io/linuxsuren/hd:v0.0.42 as downloader
80 | RUN hd install kubesphere-sigs/ks@v0.0.50
81 |
82 | FROM alpine:3.10
83 | COPY --from=downloader /usr/local/bin/ks /usr/local/bin/ks
84 | CMD ["ks"]
85 | ```
86 |
87 | ## As a library
88 | You can import it from `github.com/linuxsuren/http-downloader/pkg/installer`, then put the following code to your CLI.
89 | It can help you to download desired tools:
90 |
91 | ```go
92 | is := installer.Installer{
93 | Provider: "github",
94 | }
95 | if err = is.CheckDepAndInstall(map[string]string{
96 | "ks": "linuxsuren/ks",
97 | "kk": "kubekey",
98 | }); err != nil {
99 | return
100 | }
101 | ```
102 |
103 | ## Install other services
104 | It supports to install other services, for example: `bitbucket`.
105 |
106 | ```shell
107 | hd install bitbucket
108 | ```
109 |
110 | # Features
111 | * go library for HTTP
112 | * multi-thread
113 | * continuously (TODO)
114 | * GitHub release asset friendly
115 |
116 | ## Release
117 |
118 | This project can be released via [linuxsuren-versions](https://github.com/linuxsuren/linuxsuren-versions).
119 |
120 | 
121 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: setup-hd
2 | description: Setup hd, on GitHub Actions runners
3 | inputs:
4 | version:
5 | description: Version of hd CLI to install
6 | required: false
7 | default: 0.0.69
8 | runs:
9 | using: composite
10 | steps:
11 | # We verify the version against a SHA **in the published action itself**, not in the GCS bucket.
12 | - shell: bash
13 | run: |
14 | #!/bin/bash
15 | curl -L https://github.com/LinuxSuRen/http-downloader/releases/download/v${{ inputs.version }}/hd-linux-amd64.tar.gz | tar xzv hd
16 | sudo mv hd /usr/bin/hd
17 | hd fetch
18 |
--------------------------------------------------------------------------------
/cmd/doc.go:
--------------------------------------------------------------------------------
1 | // Package cmd provides the CLI commands
2 | package cmd
3 |
--------------------------------------------------------------------------------
/cmd/fetch.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "strings"
8 | "time"
9 |
10 | "github.com/linuxsuren/http-downloader/pkg"
11 | "github.com/linuxsuren/http-downloader/pkg/log"
12 |
13 | "github.com/linuxsuren/http-downloader/pkg/installer"
14 | "github.com/spf13/cobra"
15 | )
16 |
17 | func newFetchCmd(ctx context.Context) (cmd *cobra.Command) {
18 | opt := &fetchOption{
19 | fetcher: &installer.DefaultFetcher{},
20 | }
21 | cmd = &cobra.Command{
22 | Use: "fetch",
23 | Short: "Fetch the latest hd config",
24 | PreRunE: opt.preRunE,
25 | RunE: opt.runE,
26 | GroupID: configGroup.ID,
27 | }
28 |
29 | flags := cmd.Flags()
30 | opt.addFlags(flags)
31 | flags.StringVarP(&opt.branch, "branch", "b", installer.ConfigBranch,
32 | "The branch of git repository (not support currently)")
33 | flags.BoolVarP(&opt.reset, "reset", "", false,
34 | "If you want to reset the hd-config which means delete and clone it again")
35 | flags.IntVarP(&opt.retry, "retry", "", 6, "Retry times due to timeout error")
36 | flags.DurationVarP(&opt.timeout, "timeout", "", time.Second*60, "Timeout of fetching")
37 | return
38 | }
39 |
40 | func (o *fetchOption) setTimeout(c *cobra.Command) {
41 | if c.Context() != nil {
42 | var ctx context.Context
43 | ctx, o.cancel = context.WithTimeout(c.Context(), o.timeout)
44 | o.fetcher.SetContext(ctx)
45 | }
46 | }
47 |
48 | func (o *fetchOption) preRunE(c *cobra.Command, _ []string) (err error) {
49 | o.setTimeout(c)
50 | if o.reset {
51 | var configDir string
52 | if configDir, err = o.fetcher.GetConfigDir(); err == nil {
53 | err = os.RemoveAll(configDir)
54 | err = pkg.ErrorWrap(err, "failed to remove directory: %s, error %v", configDir, err)
55 | } else {
56 | err = fmt.Errorf("failed to get config directory, error %v", err)
57 | }
58 | }
59 | return
60 | }
61 |
62 | func (o *fetchOption) runE(c *cobra.Command, _ []string) (err error) {
63 | logger := log.GetLoggerFromContextOrDefault(c)
64 |
65 | var i int
66 | for i = 0; i < o.retry; i++ {
67 | err = o.fetcher.FetchLatestRepo(o.Provider, o.branch, c.OutOrStdout())
68 | if err == nil || (!strings.Contains(err.Error(), "context deadline exceeded") &&
69 | !strings.Contains(err.Error(), "i/o timeout")) {
70 | break
71 | }
72 | o.setTimeout(c)
73 | c.Print(".")
74 | }
75 | if i >= 1 {
76 | logger.Println()
77 | }
78 | return
79 | }
80 |
81 | type fetchOption struct {
82 | searchOption
83 |
84 | branch string
85 | reset bool
86 | fetcher installer.Fetcher
87 | cancel context.CancelFunc
88 | retry int
89 | timeout time.Duration
90 | }
91 |
--------------------------------------------------------------------------------
/cmd/fetch_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "os"
7 | "path"
8 | "testing"
9 | "time"
10 |
11 | "github.com/linuxsuren/http-downloader/pkg/installer"
12 | "github.com/spf13/cobra"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func Test_newFetchCmd(t *testing.T) {
17 | cmd := newFetchCmd(context.Background())
18 | assert.Equal(t, "fetch", cmd.Name())
19 |
20 | flags := []struct {
21 | name string
22 | shorthand string
23 | }{{
24 | name: "branch",
25 | shorthand: "b",
26 | }, {
27 | name: "reset",
28 | }}
29 | for i := range flags {
30 | tt := flags[i]
31 | t.Run(tt.name, func(t *testing.T) {
32 | flag := cmd.Flag(tt.name)
33 | assert.NotNil(t, flag)
34 | assert.NotEmpty(t, flag.Usage)
35 | assert.Equal(t, tt.shorthand, flag.Shorthand)
36 | })
37 | }
38 | }
39 |
40 | func TestSetTimeoutWithContext(t *testing.T) {
41 | tests := []struct {
42 | name string
43 | context context.Context
44 | timeout time.Duration
45 | expected bool
46 | }{
47 | {
48 | name: "context with timeout",
49 | context: context.Background(),
50 | timeout: time.Second * 10,
51 | expected: true,
52 | },
53 | {
54 | name: "context without timeout",
55 | context: nil,
56 | timeout: time.Second * 10,
57 | expected: false,
58 | },
59 | }
60 |
61 | for _, tt := range tests {
62 | t.Run(tt.name, func(t *testing.T) {
63 | cmd := &cobra.Command{}
64 | cmd.SetContext(tt.context)
65 | opt := &fetchOption{
66 | timeout: tt.timeout,
67 | fetcher: &installer.FakeFetcher{},
68 | }
69 | opt.setTimeout(cmd)
70 | if tt.expected {
71 | assert.NotNil(t, opt.cancel)
72 | } else {
73 | assert.Nil(t, opt.cancel)
74 | }
75 | })
76 | }
77 | }
78 |
79 | func TestFetchPreRunE(t *testing.T) {
80 | tests := []struct {
81 | name string
82 | opt *fetchOption
83 | hasErr bool
84 | }{{
85 | name: "not reset",
86 | opt: &fetchOption{},
87 | hasErr: false,
88 | }, {
89 | name: "reset, cannot get config dir",
90 | opt: &fetchOption{
91 | reset: true,
92 | fetcher: &installer.FakeFetcher{
93 | GetConfigDirErr: errors.New("no config dir"),
94 | },
95 | },
96 | hasErr: true,
97 | }, {
98 | name: "reset, remove dir",
99 | opt: &fetchOption{
100 | reset: true,
101 | fetcher: &installer.FakeFetcher{
102 | ConfigDir: path.Join(os.TempDir(), "hd-config"),
103 | },
104 | },
105 | hasErr: false,
106 | }}
107 | for _, tt := range tests {
108 | t.Run(tt.name, func(t *testing.T) {
109 | c := &cobra.Command{}
110 | err := tt.opt.preRunE(c, nil)
111 | if tt.hasErr {
112 | assert.NotNil(t, err)
113 | } else {
114 | assert.Nil(t, err)
115 | }
116 | })
117 | }
118 | }
119 |
120 | func TestFetchRunE(t *testing.T) {
121 | tests := []struct {
122 | name string
123 | opt *fetchOption
124 | hasErr bool
125 | }{
126 | {
127 | name: "normal",
128 | opt: &fetchOption{
129 | fetcher: &installer.FakeFetcher{},
130 | },
131 | hasErr: false,
132 | },
133 | {
134 | name: "fetch with retry",
135 | opt: &fetchOption{
136 | fetcher: &installer.FakeFetcher{
137 | FetchLatestRepoErr: errors.New("context deadline exceeded"),
138 | },
139 | retry: 3,
140 | },
141 | hasErr: true,
142 | },
143 | {
144 | name: "fetch with non-retryable error",
145 | opt: &fetchOption{
146 | fetcher: &installer.FakeFetcher{
147 | FetchLatestRepoErr: errors.New("some other error"),
148 | },
149 | retry: 3,
150 | },
151 | hasErr: true,
152 | },
153 | }
154 |
155 | for _, tt := range tests {
156 | t.Run(tt.name, func(t *testing.T) {
157 | c := &cobra.Command{}
158 | err := tt.opt.runE(c, nil)
159 | if tt.hasErr {
160 | assert.NotNil(t, err)
161 | } else {
162 | assert.Nil(t, err)
163 | }
164 | })
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/cmd/install_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "sync"
7 | "testing"
8 |
9 | cotesting "github.com/linuxsuren/cobra-extension/pkg/testing"
10 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
11 | "github.com/linuxsuren/http-downloader/pkg/installer"
12 | "github.com/spf13/cobra"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func Test_newInstallCmd(t *testing.T) {
17 | cmd := newInstallCmd(context.Background())
18 | assert.Equal(t, "install", cmd.Name())
19 |
20 | test := cotesting.FlagsValidation{{
21 | Name: "category",
22 | Shorthand: "c",
23 | }, {
24 | Name: "show-progress",
25 | }, {
26 | Name: "accept-preRelease",
27 | }, {
28 | Name: "pre",
29 | }, {
30 | Name: "from-source",
31 | }, {
32 | Name: "from-branch",
33 | }, {
34 | Name: "goget",
35 | }, {
36 | Name: "download",
37 | }, {
38 | Name: "force",
39 | Shorthand: "f",
40 | }, {
41 | Name: "clean-package",
42 | }, {
43 | Name: "thread",
44 | Shorthand: "t",
45 | }, {
46 | Name: "keep-part",
47 | }, {
48 | Name: "os",
49 | }, {
50 | Name: "arch",
51 | }, {
52 | Name: "proxy-github",
53 | }, {
54 | Name: "fetch",
55 | }, {
56 | Name: "provider",
57 | }, {
58 | Name: "no-proxy",
59 | }}
60 | test.Valid(t, cmd.Flags())
61 | }
62 |
63 | func TestInstallPreRunE(t *testing.T) {
64 | type args struct {
65 | cmd *cobra.Command
66 | args []string
67 | }
68 | for i, tt := range []struct {
69 | name string
70 | opt *installOption
71 | args args
72 | expectErr bool
73 | }{{
74 | name: "tool and category are empty",
75 | opt: &installOption{
76 | downloadOption: &downloadOption{},
77 | },
78 | expectErr: true,
79 | }, {
80 | name: "a fake tool that have an invalid path, no category",
81 | opt: &installOption{
82 | downloadOption: &downloadOption{
83 | searchOption: searchOption{Fetch: false},
84 | wait: &sync.WaitGroup{},
85 | },
86 | },
87 | args: args{
88 | args: []string{"xx@xx@xx"},
89 | cmd: &cobra.Command{},
90 | },
91 | expectErr: true,
92 | }, {
93 | name: "have category",
94 | opt: &installOption{
95 | downloadOption: &downloadOption{
96 | searchOption: searchOption{Fetch: false},
97 | Category: "tool",
98 | wait: &sync.WaitGroup{},
99 | },
100 | },
101 | args: args{
102 | args: []string{"xx@xx@xx"},
103 | cmd: &cobra.Command{},
104 | },
105 | expectErr: false,
106 | }} {
107 | t.Run(tt.name, func(t *testing.T) {
108 | err := tt.opt.preRunE(tt.args.cmd, tt.args.args)
109 | if tt.expectErr {
110 | assert.NotNil(t, err, "failed with [%d] - case [%s]", i, tt.name)
111 | } else {
112 | assert.Nil(t, err, "failed with [%d] - case [%s]", i, tt.name)
113 | }
114 | })
115 | }
116 | }
117 |
118 | func TestShouldInstall(t *testing.T) {
119 | opt := &installOption{
120 | downloadOption: &downloadOption{},
121 | execer: &fakeruntime.FakeExecer{
122 | ExpectOutput: "v1.2.3",
123 | },
124 | tool: "fake",
125 | }
126 | should, exist := opt.shouldInstall()
127 | assert.False(t, should)
128 | assert.True(t, exist)
129 |
130 | {
131 | optGreater := &installOption{
132 | execer: &fakeruntime.FakeExecer{
133 | ExpectOutput: "v1.2.3",
134 | },
135 | downloadOption: &downloadOption{
136 | Package: &installer.HDConfig{
137 | Version: "v1.2.4",
138 | VersionCmd: "-v",
139 | },
140 | },
141 | tool: "fake",
142 | }
143 | should, exist := optGreater.shouldInstall()
144 | assert.True(t, should)
145 | assert.True(t, exist)
146 | }
147 |
148 | // force to install
149 | opt.force = true
150 | should, exist = opt.shouldInstall()
151 | assert.True(t, should)
152 | assert.True(t, exist)
153 |
154 | // not exist
155 | opt.execer = &fakeruntime.FakeExecer{
156 | ExpectError: errors.New("fake"),
157 | ExpectLookPathError: errors.New("error"),
158 | }
159 | should, exist = opt.shouldInstall()
160 | assert.True(t, should)
161 | assert.False(t, exist)
162 | }
163 |
164 | func TestInstall(t *testing.T) {
165 | type args struct {
166 | cmd *cobra.Command
167 | args []string
168 | }
169 | for i, tt := range []struct {
170 | name string
171 | opt *installOption
172 | args args
173 | expectErr bool
174 | }{{
175 | name: "is a nativePackage, but it's exist",
176 | opt: &installOption{
177 | downloadOption: &downloadOption{},
178 | nativePackage: true,
179 | execer: fakeruntime.FakeExecer{},
180 | },
181 | args: args{cmd: &cobra.Command{}},
182 | expectErr: false,
183 | }} {
184 | t.Run(tt.name, func(t *testing.T) {
185 | err := tt.opt.install(tt.args.cmd, tt.args.args)
186 | if tt.expectErr {
187 | assert.NotNil(t, err, "failed with [%d] - case [%s]", i, tt.name)
188 | } else {
189 | assert.Nil(t, err, "failed with [%d] - case [%s]", i, tt.name)
190 | }
191 | })
192 | }
193 | }
194 |
195 | func TestGetDefaultInstallDir(t *testing.T) {
196 | tests := []struct {
197 | name string
198 | execer fakeruntime.Execer
199 | expect string
200 | }{{
201 | name: "linux",
202 | execer: fakeruntime.FakeExecer{
203 | ExpectOS: "linux",
204 | },
205 | expect: "/usr/local/bin",
206 | }, {
207 | name: "darwin",
208 | execer: fakeruntime.FakeExecer{
209 | ExpectOS: "darwin",
210 | },
211 | expect: "/usr/local/bin",
212 | }, {
213 | name: "windows",
214 | execer: fakeruntime.FakeExecer{
215 | ExpectOS: "windows",
216 | },
217 | expect: `C:\Program Files (x86)\Common Files`,
218 | }, {
219 | name: "unknown",
220 | execer: fakeruntime.FakeExecer{},
221 | expect: "",
222 | }}
223 | for _, tt := range tests {
224 | t.Run(tt.name, func(t *testing.T) {
225 | opt := &installOption{execer: tt.execer}
226 | result := opt.getDefaultInstallDir()
227 | assert.Equal(t, tt.expect, result)
228 | })
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 | "runtime"
9 |
10 | "github.com/linuxsuren/http-downloader/pkg/log"
11 | "github.com/mitchellh/go-homedir"
12 |
13 | "github.com/AlecAivazis/survey/v2/terminal"
14 | extver "github.com/linuxsuren/cobra-extension/version"
15 | "github.com/spf13/cobra"
16 | "github.com/spf13/viper"
17 | )
18 |
19 | var coreGroup *cobra.Group
20 | var configGroup *cobra.Group
21 |
22 | func init() {
23 | coreGroup = &cobra.Group{ID: "core", Title: "Core"}
24 | configGroup = &cobra.Group{ID: "conig", Title: "Config"}
25 | }
26 |
27 | // NewRoot returns the root command
28 | func NewRoot(cxt context.Context) (cmd *cobra.Command) {
29 | cmd = &cobra.Command{
30 | Use: "hd",
31 | Short: "HTTP download tool",
32 | }
33 | cmd.AddGroup(coreGroup, configGroup)
34 |
35 | v := viper.GetViper()
36 | if err := loadConfig(v); err != nil {
37 | panic(err)
38 | }
39 |
40 | stdio := terminal.Stdio{
41 | Out: os.Stdout,
42 | In: os.Stdin,
43 | Err: os.Stderr,
44 | }
45 |
46 | cxt = context.WithValue(cxt, log.LoggerContextKey, log.GetLogger())
47 | cmd.AddCommand(
48 | newGetCmd(cxt), newInstallCmd(cxt), newFetchCmd(cxt), newSearchCmd(cxt), newSetupCommand(v, stdio),
49 | extver.NewVersionCmd("linuxsuren", "http-downloader", "hd", nil))
50 |
51 | for _, c := range cmd.Commands() {
52 | registerFlagCompletionFunc(c, "provider", ArrayCompletion(ProviderGitHub, ProviderGitee))
53 | registerFlagCompletionFunc(c, "os", ArrayCompletion("window", "linux", "darwin"))
54 | registerFlagCompletionFunc(c, "arch", ArrayCompletion("amd64", "arm64"))
55 | registerFlagCompletionFunc(c, "format", ArrayCompletion("tar.gz", "zip", "msi"))
56 | }
57 | return
58 | }
59 |
60 | func registerFlagCompletionFunc(cmd *cobra.Command, flag string, completionFunc CompletionFunc) {
61 | if p := cmd.Flag(flag); p != nil {
62 | if err := cmd.RegisterFlagCompletionFunc(flag, completionFunc); err != nil {
63 | cmd.Println(err)
64 | }
65 | }
66 | }
67 |
68 | func loadConfig(v *viper.Viper) (err error) {
69 | var userHome string
70 | if userHome, err = homedir.Dir(); err != nil {
71 | return
72 | }
73 |
74 | configDir := filepath.Join(userHome, ".config")
75 | v.SetConfigName("hd")
76 | v.SetConfigType("yaml")
77 | v.AddConfigPath(configDir)
78 | if err = v.ReadInConfig(); err != nil {
79 | if _, ok := err.(viper.ConfigFileNotFoundError); ok {
80 | // Config file not found; ignore error if desired
81 | err = nil
82 | } else {
83 | err = fmt.Errorf("failed to load config: %s, error: %v", os.ExpandEnv("$HOME/.config/hd.yaml"), err)
84 | }
85 | }
86 | v.SetDefault("provider", ProviderGitHub)
87 | v.SetDefault("fetch", false)
88 | v.SetDefault("goget", false)
89 | v.SetDefault("no-proxy", false)
90 |
91 | thread := runtime.NumCPU()
92 | if thread > 4 {
93 | thread = thread / 2
94 | }
95 | v.SetDefault("thread", thread)
96 | return
97 | }
98 |
--------------------------------------------------------------------------------
/cmd/root_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestNewRoot(t *testing.T) {
11 | cmd := NewRoot(context.Background())
12 | assert.Equal(t, "hd", cmd.Name())
13 | }
14 |
--------------------------------------------------------------------------------
/cmd/search.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 |
8 | "github.com/linuxsuren/http-downloader/pkg/installer"
9 | "github.com/linuxsuren/http-downloader/pkg/log"
10 | "github.com/spf13/cobra"
11 | "github.com/spf13/pflag"
12 | "github.com/spf13/viper"
13 | )
14 |
15 | func newSearchCmd(context.Context) (cmd *cobra.Command) {
16 | opt := &searchOption{
17 | fetcher: &installer.DefaultFetcher{},
18 | }
19 |
20 | cmd = &cobra.Command{
21 | Use: "search",
22 | Aliases: []string{"s", "find", "f"},
23 | Short: "Search packages from the hd config repo",
24 | Args: cobra.MinimumNArgs(1),
25 | RunE: opt.runE,
26 | GroupID: configGroup.ID,
27 | }
28 | opt.addFlags(cmd.Flags())
29 | return
30 | }
31 |
32 | type searchOption struct {
33 | Fetch bool
34 | Provider string
35 | ProxyGitHub string
36 | fetcher installer.Fetcher
37 | }
38 |
39 | func (s *searchOption) addFlags(flags *pflag.FlagSet) {
40 | flags.BoolVarP(&s.Fetch, "fetch", "", viper.GetBool("fetch"),
41 | "If fetch the latest config from https://github.com/LinuxSuRen/hd-home")
42 | flags.StringVarP(&s.Provider, "provider", "", viper.GetString("provider"), "The file provider")
43 | flags.StringVarP(&s.ProxyGitHub, "proxy-github", "", viper.GetString("proxy-github"),
44 | `The proxy address of github.com, the proxy address will be the prefix of the final address.
45 | Submit the new proxy server in https://github.com/LinuxSuRen/hd-home`)
46 | }
47 |
48 | func (s *searchOption) runE(c *cobra.Command, args []string) (err error) {
49 | logger := log.GetLoggerFromContextOrDefault(c)
50 |
51 | err = search(args[0], s.Fetch, s.fetcher, c.OutOrStdout(), logger)
52 | return
53 | }
54 |
55 | func search(keyword string, fetch bool, fetcher installer.Fetcher, writer io.Writer, logger *log.LevelLog) (err error) {
56 | if fetch {
57 | if err = fetcher.FetchLatestRepo("", "", writer); err != nil {
58 | return
59 | }
60 | }
61 |
62 | var configDir string
63 | if configDir, err = fetcher.GetConfigDir(); err != nil {
64 | return
65 | }
66 |
67 | logger.Info("start to search in:", configDir)
68 | result := installer.FindByKeyword(keyword, configDir)
69 | for _, item := range result {
70 | fmt.Fprintln(writer, item)
71 | }
72 | return
73 | }
74 |
--------------------------------------------------------------------------------
/cmd/search_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "errors"
7 | "os"
8 | "path"
9 | "testing"
10 |
11 | "github.com/linuxsuren/http-downloader/pkg/installer"
12 | "github.com/linuxsuren/http-downloader/pkg/log"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func Test_search(t *testing.T) {
17 | logger := log.GetLogger()
18 |
19 | buf := &bytes.Buffer{}
20 | err := search("keyword", true, &installer.FakeFetcher{}, buf, logger)
21 | assert.Nil(t, err)
22 | assert.Empty(t, buf.String())
23 |
24 | // expect an error with GetConfigDir
25 | err = search("", true, &installer.FakeFetcher{GetConfigDirErr: errors.New("fake")}, buf, logger)
26 | assert.NotNil(t, err)
27 |
28 | // expect an error with FetchLatestRepo
29 | err = search("", true, &installer.FakeFetcher{FetchLatestRepoErr: errors.New("fake")}, buf, logger)
30 | assert.NotNil(t, err)
31 |
32 | tempDir, err := os.MkdirTemp("", "config")
33 | assert.Nil(t, err)
34 | defer func() {
35 | _ = os.RemoveAll(tempDir)
36 | }()
37 |
38 | configDir := path.Join(tempDir, "config")
39 | orgDir := path.Join(configDir, "org")
40 | err = os.MkdirAll(orgDir, 0755)
41 | assert.Nil(t, err)
42 | err = os.WriteFile(path.Join(orgDir, "repo.yml"), []byte("x=x"), os.ModeAppend)
43 | assert.Nil(t, err)
44 | err = os.WriteFile(path.Join(orgDir, "fake.yml"), []byte{}, os.ModeAppend)
45 | assert.Nil(t, err)
46 |
47 | err = search("repo", true, &installer.FakeFetcher{ConfigDir: tempDir}, buf, logger)
48 | assert.Nil(t, err)
49 | }
50 |
51 | func Test_newSearchCmd(t *testing.T) {
52 | cmd := newSearchCmd(context.Background())
53 | assert.Equal(t, "search", cmd.Name())
54 |
55 | flags := []struct {
56 | name string
57 | shorthand string
58 | }{{
59 | name: "fetch",
60 | }, {
61 | name: "provider",
62 | }, {
63 | name: "proxy-github",
64 | }}
65 | for i := range flags {
66 | tt := flags[i]
67 | t.Run(tt.name, func(t *testing.T) {
68 | flag := cmd.Flag(tt.name)
69 | assert.NotNil(t, flag)
70 | assert.NotEmpty(t, flag.Usage)
71 | assert.Equal(t, tt.shorthand, flag.Shorthand)
72 | })
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/cmd/setup.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 |
8 | "github.com/AlecAivazis/survey/v2"
9 | "github.com/AlecAivazis/survey/v2/terminal"
10 | "github.com/linuxsuren/http-downloader/pkg/installer"
11 | "github.com/linuxsuren/http-downloader/pkg/log"
12 | "github.com/spf13/cobra"
13 | "github.com/spf13/viper"
14 | )
15 |
16 | func newSetupCommand(v *viper.Viper, stdio terminal.Stdio) (cmd *cobra.Command) {
17 | opt := &setupOption{
18 | stdio: stdio,
19 | v: v,
20 | }
21 | cmd = &cobra.Command{
22 | Use: "setup",
23 | Short: "Init the configuration of hd",
24 | RunE: opt.runE,
25 | GroupID: configGroup.ID,
26 | }
27 | flags := cmd.Flags()
28 | flags.StringVarP(&opt.proxy, "proxy", "p", "", "The proxy of GitHub")
29 | flags.StringVarP(&opt.provider, "provider", "", "", "The provider of hd configuration")
30 | return
31 | }
32 |
33 | type setupOption struct {
34 | stdio terminal.Stdio
35 | v *viper.Viper
36 |
37 | proxy string
38 | provider string
39 | }
40 |
41 | func (o *setupOption) runE(cmd *cobra.Command, args []string) (err error) {
42 | logger := log.GetLoggerFromContextOrDefault(cmd)
43 | proxyServers := []string{""}
44 | proxyServers = append(proxyServers, installer.GetProxyServers()...)
45 |
46 | if o.proxy == "" {
47 | if o.proxy, err = selectFromList(proxyServers,
48 | o.v.GetString("proxy-github"),
49 | "Select proxy-github", o.stdio); err != nil {
50 | return
51 | }
52 | }
53 | o.v.Set("proxy-github", o.proxy)
54 |
55 | if o.provider == "" {
56 | if o.provider, err = selectFromList([]string{"github", "gitee"}, o.v.GetString("provider"),
57 | "Select provider", o.stdio); err != nil {
58 | return
59 | }
60 | }
61 | o.v.Set("provider", o.provider)
62 |
63 | var configDir string
64 | fetcher := &installer.DefaultFetcher{}
65 | if configDir, err = fetcher.GetHomeDir(); err == nil {
66 | if err = os.MkdirAll(configDir, 0750); err != nil {
67 | err = fmt.Errorf("failed to create directory: %s, error: %v", configDir, err)
68 | return
69 | }
70 |
71 | configPath := filepath.Join(configDir, ".config", "hd.yaml")
72 | logger.Info("write config into:", configPath)
73 | err = o.v.WriteConfigAs(configPath)
74 | }
75 | return
76 | }
77 |
78 | func selectFromList(items []string, defaultItem, title string, stdio terminal.Stdio) (val string, err error) {
79 | selector := &survey.Select{
80 | Message: title,
81 | Options: items,
82 | Default: defaultItem,
83 | }
84 | err = survey.AskOne(selector, &val, survey.WithStdio(stdio.In, stdio.Out, stdio.Err))
85 | return
86 | }
87 |
--------------------------------------------------------------------------------
/cmd/setup_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/AlecAivazis/survey/v2/terminal"
8 | expect "github.com/Netflix/go-expect"
9 | pseudotty "github.com/creack/pty"
10 | "github.com/hinshun/vt10x"
11 | "github.com/linuxsuren/http-downloader/pkg/log"
12 | "github.com/spf13/afero"
13 | "github.com/spf13/viper"
14 | "github.com/stretchr/testify/assert"
15 | )
16 |
17 | func Test_newSetupCommand(t *testing.T) {
18 | t.Run("normal", func(t *testing.T) {
19 | RunTest(t, func(c expectConsole) {
20 | c.ExpectString("Select proxy-github")
21 | // c.Send("99988866")
22 | c.SendLine("")
23 |
24 | c.ExpectString("Select provider")
25 | c.Send("gitee")
26 | c.SendLine("")
27 | c.ExpectEOF()
28 | }, func(tr terminal.Stdio) error {
29 | fs := afero.NewMemMapFs()
30 | v := viper.New()
31 | v.SetFs(fs)
32 | v.Set("provider", "github")
33 |
34 | cmd := newSetupCommand(v, tr)
35 | assert.Equal(t, "setup", cmd.Name())
36 |
37 | err := cmd.Execute()
38 | assert.Nil(t, err)
39 | // assert.Equal(t, "gh.api.99988866.xyz", v.GetString("proxy-github"))
40 | assert.Equal(t, "gitee", v.GetString("provider"))
41 | return err
42 | })
43 | })
44 |
45 | t.Run("test the default value", func(t *testing.T) {
46 | RunTest(t, func(c expectConsole) {
47 | c.ExpectString("Select proxy-github")
48 | c.SendLine("")
49 |
50 | c.ExpectString("Select provider")
51 | c.SendLine("")
52 | c.ExpectEOF()
53 | }, func(tr terminal.Stdio) error {
54 | fs := afero.NewMemMapFs()
55 | v := viper.New()
56 | v.SetFs(fs)
57 | v.Set("provider", "gitee")
58 | v.Set("proxy-github", "gh.api.99988866.xyz")
59 |
60 | cmd := newSetupCommand(v, tr)
61 | assert.Equal(t, "setup", cmd.Name())
62 |
63 | err := cmd.Execute()
64 | assert.Nil(t, err)
65 | assert.Equal(t, "gh.api.99988866.xyz", v.GetString("proxy-github"))
66 | assert.Equal(t, "gitee", v.GetString("provider"))
67 | return err
68 | })
69 | })
70 |
71 | t.Run("setup with given flags", func(t *testing.T) {
72 | RunTest(t, func(c expectConsole) {
73 | }, func(tr terminal.Stdio) error {
74 | fs := afero.NewMemMapFs()
75 | v := viper.New()
76 | v.SetFs(fs)
77 | v.Set("provider", "gitee")
78 | v.Set("proxy-github", "gh.api.99988866.xyz")
79 |
80 | cmd := newSetupCommand(v, tr)
81 | assert.Equal(t, "setup", cmd.Name())
82 | cmd.SetArgs([]string{"--proxy", "fake.com", "--provider", "fake"})
83 | cmd.SetContext(log.NewContextWithLogger(context.Background(), 0))
84 |
85 | err := cmd.Execute()
86 | assert.Nil(t, err)
87 | assert.Equal(t, "fake.com", v.GetString("proxy-github"))
88 | assert.Equal(t, "fake", v.GetString("provider"))
89 | return err
90 | })
91 | })
92 | }
93 |
94 | func TestSelectFromList(t *testing.T) {
95 | RunTest(t, func(c expectConsole) {
96 | c.ExpectString("title")
97 | c.SendLine(string(terminal.KeyArrowDown))
98 | c.SendLine("")
99 | c.ExpectEOF()
100 | }, func(tr terminal.Stdio) error {
101 | val, err := selectFromList([]string{"one", "two", "three"}, "", "title", tr)
102 | assert.Equal(t, "two", val)
103 | return err
104 | })
105 | }
106 |
107 | type expectConsole interface {
108 | ExpectString(string)
109 | ExpectEOF()
110 | SendLine(string)
111 | Send(string)
112 | }
113 |
114 | func RunTest(t *testing.T, procedure func(expectConsole), test func(terminal.Stdio) error) {
115 | t.Helper()
116 | t.Parallel()
117 |
118 | pty, tty, err := pseudotty.Open()
119 | if err != nil {
120 | t.Fatalf("failed to open pseudotty: %v", err)
121 | }
122 |
123 | term := vt10x.New(vt10x.WithWriter(tty))
124 | c, err := expect.NewConsole(expect.WithStdin(pty), expect.WithStdout(term), expect.WithCloser(pty, tty))
125 | if err != nil {
126 | t.Fatalf("failed to create console: %v", err)
127 | }
128 | defer c.Close()
129 |
130 | donec := make(chan struct{})
131 | go func() {
132 | defer close(donec)
133 | procedure(&consoleWithErrorHandling{console: c, t: t})
134 | }()
135 |
136 | stdio := terminal.Stdio{In: c.Tty(), Out: c.Tty(), Err: c.Tty()}
137 | if err := test(stdio); err != nil {
138 | t.Error(err)
139 | }
140 |
141 | if err := c.Tty().Close(); err != nil {
142 | t.Errorf("error closing Tty: %v", err)
143 | }
144 | <-donec
145 | }
146 |
147 | type consoleWithErrorHandling struct {
148 | console *expect.Console
149 | t *testing.T
150 | }
151 |
152 | func (c *consoleWithErrorHandling) ExpectString(s string) {
153 | if _, err := c.console.ExpectString(s); err != nil {
154 | c.t.Helper()
155 | c.t.Fatalf("ExpectString(%q) = %v", s, err)
156 | }
157 | }
158 |
159 | func (c *consoleWithErrorHandling) SendLine(s string) {
160 | if _, err := c.console.SendLine(s); err != nil {
161 | c.t.Helper()
162 | c.t.Fatalf("SendLine(%q) = %v", s, err)
163 | }
164 | }
165 |
166 | func (c *consoleWithErrorHandling) Send(s string) {
167 | if _, err := c.console.Send(s); err != nil {
168 | c.t.Helper()
169 | c.t.Fatalf("Send(%q) = %v", s, err)
170 | }
171 | }
172 |
173 | func (c *consoleWithErrorHandling) ExpectEOF() {
174 | if _, err := c.console.ExpectEOF(); err != nil {
175 | c.t.Helper()
176 | c.t.Fatalf("ExpectEOF() = %v", err)
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/cmd/testdata/magnet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
--------------------------------------------------------------------------------
/cmd/util.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | "github.com/spf13/cobra"
8 | )
9 |
10 | func getOrDefault(key, def string, data map[string]string) (result string) {
11 | var ok bool
12 | if result, ok = data[key]; !ok {
13 | result = def
14 | }
15 | return
16 | }
17 |
18 | type contextRoundTripper string
19 |
20 | func getRoundTripper(ctx context.Context) (tripper http.RoundTripper) {
21 | if ctx == nil {
22 | return
23 | }
24 | roundTripper := ctx.Value(contextRoundTripper("roundTripper"))
25 |
26 | switch v := roundTripper.(type) {
27 | case *http.Transport:
28 | tripper = v
29 | }
30 | return
31 | }
32 |
33 | // CompletionFunc is the function for command completion
34 | type CompletionFunc func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective)
35 |
36 | // ArrayCompletion return a completion which base on an array
37 | func ArrayCompletion(array ...string) CompletionFunc {
38 | return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
39 | return array, cobra.ShellCompDirectiveNoFileComp
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/cmd/util_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "github.com/spf13/cobra"
6 | "github.com/stretchr/testify/assert"
7 | "net/http"
8 | "reflect"
9 | "testing"
10 | )
11 |
12 | func Test_getRoundTripper(t *testing.T) {
13 | type args struct {
14 | ctx context.Context
15 | }
16 | tests := []struct {
17 | name string
18 | args args
19 | wantTripper http.RoundTripper
20 | }{{
21 | name: "context is nil",
22 | wantTripper: nil,
23 | }, {
24 | name: "invalid type of RounderTripper in the context",
25 | args: args{
26 | ctx: context.WithValue(context.TODO(), contextRoundTripper("roundTripper"), "invalid"),
27 | },
28 | wantTripper: nil,
29 | }, {
30 | name: "valid type of RounderTripper in the context",
31 | args: args{
32 | ctx: context.WithValue(context.TODO(), contextRoundTripper("roundTripper"), &http.Transport{}),
33 | },
34 | wantTripper: &http.Transport{},
35 | }}
36 | for _, tt := range tests {
37 | t.Run(tt.name, func(t *testing.T) {
38 | if gotTripper := getRoundTripper(tt.args.ctx); !reflect.DeepEqual(gotTripper, tt.wantTripper) {
39 | t.Errorf("getRoundTripper() = %v, want %v", gotTripper, tt.wantTripper)
40 | }
41 | })
42 | }
43 | }
44 |
45 | func Test_getOrDefault(t *testing.T) {
46 | type args struct {
47 | key string
48 | def string
49 | data map[string]string
50 | }
51 | tests := []struct {
52 | name string
53 | args args
54 | wantResult string
55 | }{{
56 | name: "no key exist",
57 | args: args{
58 | key: "key",
59 | def: "def",
60 | data: map[string]string{},
61 | },
62 | wantResult: "def",
63 | }, {
64 | name: "key exist",
65 | args: args{
66 | key: "key",
67 | def: "def",
68 | data: map[string]string{
69 | "key": "key",
70 | },
71 | },
72 | wantResult: "key",
73 | }}
74 | for _, tt := range tests {
75 | t.Run(tt.name, func(t *testing.T) {
76 | if gotResult := getOrDefault(tt.args.key, tt.args.def, tt.args.data); gotResult != tt.wantResult {
77 | t.Errorf("getOrDefault() = %v, want %v", gotResult, tt.wantResult)
78 | }
79 | })
80 | }
81 | }
82 |
83 | func TestArrayCompletion(t *testing.T) {
84 | function := ArrayCompletion("a", "b")
85 | assert.NotNil(t, function)
86 |
87 | array, direct := function(nil, nil, "")
88 | assert.Equal(t, []string{"a", "b"}, array)
89 | assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, direct)
90 | }
91 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | # https://docs.codecov.com/docs/commit-status
2 | # it's hard to have a coverage for some code lines
3 | coverage:
4 | status:
5 | project:
6 | default:
7 | target: auto
8 | threshold: 0.1%
9 | patch:
10 | default:
11 | target: auto
12 | threshold: 0%
13 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/linuxsuren/http-downloader
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/AlecAivazis/survey/v2 v2.3.2
7 | github.com/go-git/go-git/v5 v5.10.1
8 | github.com/golang/mock v1.6.0
9 | github.com/google/go-github/v29 v29.0.3
10 | github.com/h2non/gock v1.0.9
11 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec
12 | github.com/linuxsuren/cobra-extension v0.0.16
13 | github.com/mitchellh/go-homedir v1.1.0
14 | github.com/onsi/ginkgo v1.16.5
15 | github.com/onsi/gomega v1.27.10
16 | github.com/spf13/cobra v1.6.1
17 | github.com/spf13/pflag v1.0.5
18 | github.com/spf13/viper v1.9.0
19 | github.com/stretchr/testify v1.8.2
20 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8
21 | gopkg.in/yaml.v3 v3.0.1
22 | )
23 |
24 | require (
25 | github.com/antonmedv/expr v1.11.1
26 | github.com/creack/pty v1.1.17
27 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213
28 | github.com/linuxsuren/go-fake-runtime v0.0.0-20230426144714-1a7a0d160d3f
29 | github.com/schollz/progressbar/v3 v3.13.0
30 | )
31 |
32 | require (
33 | dario.cat/mergo v1.0.0 // indirect
34 | github.com/cloudflare/circl v1.3.3 // indirect
35 | github.com/cyphar/filepath-securejoin v0.2.4 // indirect
36 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
37 | github.com/google/go-cmp v0.6.0 // indirect
38 | github.com/mattn/go-runewidth v0.0.14 // indirect
39 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
40 | github.com/pjbgf/sha1cd v0.3.0 // indirect
41 | github.com/prometheus/client_golang v1.11.1 // indirect
42 | github.com/rivo/uniseg v0.4.3 // indirect
43 | github.com/skeema/knownhosts v1.2.1 // indirect
44 | golang.org/x/mod v0.12.0 // indirect
45 | golang.org/x/tools v0.13.0 // indirect
46 | )
47 |
48 | require (
49 | github.com/Microsoft/go-winio v0.6.1 // indirect
50 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2
51 | github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
52 | github.com/blang/semver/v4 v4.0.0
53 | github.com/davecgh/go-spew v1.1.1 // indirect
54 | github.com/emirpasic/gods v1.18.1 // indirect
55 | github.com/fsnotify/fsnotify v1.5.1 // indirect
56 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
57 | github.com/go-git/go-billy/v5 v5.5.0 // indirect
58 | github.com/golang/protobuf v1.5.3 // indirect
59 | github.com/google/go-querystring v1.0.0 // indirect
60 | github.com/hashicorp/hcl v1.0.0 // indirect
61 | github.com/inconshreveable/mousetrap v1.0.1 // indirect
62 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
63 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
64 | github.com/kevinburke/ssh_config v1.2.0 // indirect
65 | github.com/magiconair/properties v1.8.5 // indirect
66 | github.com/mattn/go-colorable v0.1.6 // indirect
67 | github.com/mattn/go-isatty v0.0.17 // indirect
68 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
69 | github.com/mitchellh/mapstructure v1.4.2 // indirect
70 | github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect
71 | github.com/nxadm/tail v1.4.8 // indirect
72 | github.com/pelletier/go-toml v1.9.4 // indirect
73 | github.com/pmezard/go-difflib v1.0.0 // indirect
74 | github.com/sergi/go-diff v1.1.0 // indirect
75 | github.com/spf13/afero v1.6.0
76 | github.com/spf13/cast v1.4.1 // indirect
77 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
78 | github.com/subosito/gotenv v1.2.0 // indirect
79 | github.com/xanzy/ssh-agent v0.3.3 // indirect
80 | golang.org/x/crypto v0.15.0 // indirect
81 | golang.org/x/net v0.18.0
82 | golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
83 | golang.org/x/sys v0.14.0 // indirect
84 | golang.org/x/term v0.14.0 // indirect
85 | golang.org/x/text v0.14.0 // indirect
86 | google.golang.org/appengine v1.6.7 // indirect
87 | google.golang.org/protobuf v1.28.0 // indirect
88 | gopkg.in/ini.v1 v1.63.2 // indirect
89 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
90 | gopkg.in/warnings.v0 v0.1.2 // indirect
91 | gopkg.in/yaml.v2 v2.4.0 // indirect
92 | )
93 |
--------------------------------------------------------------------------------
/hack/README.md:
--------------------------------------------------------------------------------
1 | Add as a sidecar:
2 |
3 | ```shell
4 | kubectl patch deployment you-app -p'{"spec":{"template":{"spec":{"containers":[{"name":"hd","image":"ghcr.io/linuxsuren/hd","command":["/bin/sh"],"args":["-c","while true; do echo hello; sleep 10;done"]}]}}}}'
5 | ```
6 |
--------------------------------------------------------------------------------
/hack/deploy.yaml:
--------------------------------------------------------------------------------
1 | kind: Deployment
2 | apiVersion: apps/v1
3 | metadata:
4 | name: hd
5 | labels:
6 | app: hd
7 | spec:
8 | replicas: 1
9 | selector:
10 | matchLabels:
11 | app: hd
12 | template:
13 | metadata:
14 | creationTimestamp: null
15 | labels:
16 | app: hd
17 | spec:
18 | containers:
19 | - name: hd
20 | image: 'ghcr.io/linuxsuren/hd:v0.0.60'
21 | command:
22 | - /bin/sh
23 | args:
24 | - '-c'
25 | - while true; do echo hello; sleep 10;done
26 | resources: {}
27 | terminationMessagePath: /dev/termination-log
28 | terminationMessagePolicy: File
29 | imagePullPolicy: IfNotPresent
30 | restartPolicy: Always
31 | terminationGracePeriodSeconds: 30
32 | dnsPolicy: ClusterFirst
33 | securityContext: {}
34 | schedulerName: default-scheduler
35 | strategy:
36 | type: RollingUpdate
37 | rollingUpdate:
38 | maxUnavailable: 25%
39 | maxSurge: 25%
40 | revisionHistoryLimit: 10
41 | progressDeadlineSeconds: 600
42 |
--------------------------------------------------------------------------------
/hack/init.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | if [[ "$1" == "" ]]; then
4 | echo "please give a appropriate name"
5 | exit 1
6 | fi
7 |
8 | find . -name "*.yaml" -exec sed -i '' s/github-go/"$1"/ {} +
9 | find . -name "*.yml" -exec sed -i '' s/github-go/"$1"/ {} +
10 | find . -name "*.md" -exec sed -i '' s/github-go/"$1"/ {} +
11 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "os"
6 |
7 | "github.com/linuxsuren/http-downloader/cmd"
8 | )
9 |
10 | func main() {
11 | ctx := context.Background()
12 | if err := cmd.NewRoot(ctx).ExecuteContext(ctx); err != nil {
13 | os.Exit(1)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/mock/mhttp/roundtripper.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: net/http (interfaces: RoundTripper)
3 |
4 | // Package mhttp is a generated GoMock package.
5 | package mhttp
6 |
7 | import (
8 | gomock "github.com/golang/mock/gomock"
9 | http "net/http"
10 | reflect "reflect"
11 | )
12 |
13 | // MockRoundTripper is a mock of RoundTripper interface
14 | type MockRoundTripper struct {
15 | ctrl *gomock.Controller
16 | recorder *MockRoundTripperMockRecorder
17 | }
18 |
19 | // MockRoundTripperMockRecorder is the mock recorder for MockRoundTripper
20 | type MockRoundTripperMockRecorder struct {
21 | mock *MockRoundTripper
22 | }
23 |
24 | // NewMockRoundTripper creates a new mock instance
25 | func NewMockRoundTripper(ctrl *gomock.Controller) *MockRoundTripper {
26 | mock := &MockRoundTripper{ctrl: ctrl}
27 | mock.recorder = &MockRoundTripperMockRecorder{mock}
28 | return mock
29 | }
30 |
31 | // EXPECT returns an object that allows the caller to indicate expected use
32 | func (m *MockRoundTripper) EXPECT() *MockRoundTripperMockRecorder {
33 | return m.recorder
34 | }
35 |
36 | // RoundTrip mocks base method
37 | func (m *MockRoundTripper) RoundTrip(arg0 *http.Request) (*http.Response, error) {
38 | m.ctrl.T.Helper()
39 | ret := m.ctrl.Call(m, "RoundTrip", arg0)
40 | ret0, _ := ret[0].(*http.Response)
41 | ret1, _ := ret[1].(error)
42 | return ret0, ret1
43 | }
44 |
45 | // RoundTrip indicates an expected call of RoundTrip
46 | func (mr *MockRoundTripperMockRecorder) RoundTrip(arg0 interface{}) *gomock.Call {
47 | mr.mock.ctrl.T.Helper()
48 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RoundTrip", reflect.TypeOf((*MockRoundTripper)(nil).RoundTrip), arg0)
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/common/doc.go:
--------------------------------------------------------------------------------
1 | // Package common provides some common functions for the whole project.
2 | package common
3 |
--------------------------------------------------------------------------------
/pkg/common/util.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "regexp"
8 | )
9 |
10 | // GetOrDefault returns the value or a default value from a map
11 | func GetOrDefault(key, def string, data map[string]string) (result string) {
12 | var ok bool
13 | if result, ok = data[key]; !ok {
14 | result = def
15 | }
16 | return
17 | }
18 |
19 | // GetReplacement returns a string which replace via a map
20 | func GetReplacement(key string, data map[string]string) (result string) {
21 | return GetOrDefault(key, key, data)
22 | }
23 |
24 | // PathExists checks if the target path exist or not
25 | func PathExists(path string) (bool, error) {
26 | _, err := os.Stat(path)
27 | if err == nil {
28 | return true, nil
29 | }
30 | if os.IsNotExist(err) {
31 | return false, nil
32 | }
33 | return false, err
34 | }
35 |
36 | const (
37 | // PrivateFileMode grants owner to read/write a file.
38 | PrivateFileMode = 0600
39 | // PrivateDirMode grants owner to make/remove files inside the directory.
40 | PrivateDirMode = 0700
41 | )
42 |
43 | // IsDirWriteable checks if dir is writable by writing and removing a file
44 | // to dir. It returns nil if dir is writable.
45 | func IsDirWriteable(dir string) error {
46 | f := filepath.Join(dir, ".touch")
47 | if err := os.WriteFile(f, []byte(""), PrivateFileMode); err != nil {
48 | return err
49 | }
50 | return os.Remove(f)
51 | }
52 |
53 | // CheckDirPermission checks permission on an existing dir.
54 | // Returns error if dir is empty or exist with a different permission than specified.
55 | func CheckDirPermission(dir string, perm os.FileMode) error {
56 | if !Exist(dir) {
57 | return fmt.Errorf("directory %q empty, cannot check permission", dir)
58 | }
59 | // check the existing permission on the directory
60 | dirInfo, err := os.Stat(dir)
61 | if err != nil {
62 | return err
63 | }
64 | dirMode := dirInfo.Mode().Perm()
65 | if dirMode != perm {
66 | err = fmt.Errorf("directory %q exist, but the permission is %q. The recommended permission is %q to prevent possible unprivileged access to the data", dir, dirInfo.Mode(), os.FileMode(PrivateDirMode))
67 | return err
68 | }
69 | return nil
70 | }
71 |
72 | // Exist returns true if a file or directory exists.
73 | func Exist(name string) bool {
74 | ok, _ := PathExists(name)
75 | return ok
76 | }
77 |
78 | // ParseVersionNum split version from release or tag
79 | func ParseVersionNum(release string) string {
80 | return regexp.MustCompile(`^.*v`).ReplaceAllString(release, "")
81 | }
82 |
83 | // GetEnvironment retrieves the value of the environment variable named by the key.
84 | // If the environment doesn't exist, we will lookup for alternative environment variables
85 | // until we find an environment. Return empty environment value while no environment variables found.
86 | func GetEnvironment(key string, alternativeKeys ...string) string {
87 | if value, exists := os.LookupEnv(key); exists {
88 | return value
89 | }
90 | for _, alternativeKey := range alternativeKeys {
91 | if value, exists := os.LookupEnv(alternativeKey); exists {
92 | return value
93 | }
94 | }
95 | return ""
96 | }
97 |
--------------------------------------------------------------------------------
/pkg/compress/bzip2.go:
--------------------------------------------------------------------------------
1 | package compress
2 |
3 | import (
4 | "compress/bzip2"
5 | "errors"
6 | "fmt"
7 | "io"
8 | "os"
9 | "path/filepath"
10 | )
11 |
12 | // Bzip2 implements a compress which based is based on bzip2
13 | type Bzip2 struct {
14 | additionBinaries []string
15 | }
16 |
17 | // NewBzip2 creates an instance of Bzip2
18 | func NewBzip2(additionBinaries []string) *Bzip2 {
19 | return &Bzip2{additionBinaries: additionBinaries}
20 | }
21 |
22 | // make sure Bzip2 implements the interface Compress
23 | var _ Compress = &Bzip2{}
24 |
25 | // ExtractFiles extracts files from a target compress file
26 | func (x *Bzip2) ExtractFiles(sourceFile, targetName string) (err error) {
27 | if sourceFile == "" || targetName == "" {
28 | err = errors.New("source or target filename is empty")
29 | return
30 | }
31 | var f *os.File
32 | if f, err = os.Open(sourceFile); err != nil {
33 | return err
34 | }
35 | defer func() {
36 | _ = f.Close()
37 | }()
38 |
39 | // Create a Bzip2 Reader
40 | r := bzip2.NewReader(f)
41 |
42 | var targetFile *os.File
43 | if targetFile, err = os.OpenFile(fmt.Sprintf("%s/%s", filepath.Dir(sourceFile), targetName),
44 | os.O_CREATE|os.O_RDWR, 0600); err != nil {
45 | return
46 | }
47 | if _, err = io.Copy(targetFile, r); err != nil {
48 | return
49 | }
50 | return
51 | }
52 |
--------------------------------------------------------------------------------
/pkg/compress/doc.go:
--------------------------------------------------------------------------------
1 | // Package compress provides the compress and uncompress functions.
2 | package compress
3 |
--------------------------------------------------------------------------------
/pkg/compress/gzip.go:
--------------------------------------------------------------------------------
1 | package compress
2 |
3 | import (
4 | "archive/tar"
5 | "compress/gzip"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "os"
10 | "path"
11 | )
12 |
13 | // GZip implements a compress which based is based on gzip
14 | type GZip struct {
15 | additionBinaries []string
16 | }
17 |
18 | // NewGZip creates an instance of GZip
19 | // additionBinaries could be empty or nil
20 | func NewGZip(additionBinaries []string) *GZip {
21 | return &GZip{additionBinaries: additionBinaries}
22 | }
23 |
24 | // make sure GZip implements the interface Compress
25 | var _ Compress = &GZip{}
26 |
27 | // ExtractFiles extracts files from a target compress file
28 | func (c *GZip) ExtractFiles(sourceFile, targetName string) (err error) {
29 | if sourceFile == "" || targetName == "" {
30 | err = errors.New("source or target filename is empty")
31 | return
32 | }
33 |
34 | var f *os.File
35 | var gzf *gzip.Reader
36 | if f, err = os.Open(sourceFile); err != nil {
37 | return
38 | }
39 | defer func() {
40 | _ = f.Close()
41 | }()
42 |
43 | if gzf, err = gzip.NewReader(f); err != nil {
44 | return
45 | }
46 |
47 | err = zipProcess(tar.NewReader(gzf), targetName, sourceFile, c.additionBinaries)
48 | return
49 | }
50 |
51 | func zipProcess(tarReader *tar.Reader, targetName, sourceFile string, additionBinaries []string) (err error) {
52 | var header *tar.Header
53 | var found bool
54 | for {
55 | if header, err = tarReader.Next(); err == io.EOF {
56 | err = nil
57 | break
58 | } else if err != nil {
59 | break
60 | }
61 | name := path.Base(header.Name)
62 |
63 | switch header.Typeflag {
64 | case tar.TypeReg:
65 | if name == targetName {
66 | if err = extraFile(name, targetName, sourceFile, header, tarReader); err == nil {
67 | found = true
68 | } else {
69 | break
70 | }
71 | } else {
72 | for i := range additionBinaries {
73 | addition := additionBinaries[i]
74 | if name != addition {
75 | continue
76 | }
77 |
78 | if err = extraFile(addition, addition, sourceFile, header, tarReader); err != nil {
79 | return
80 | }
81 | }
82 | }
83 | }
84 | }
85 |
86 | if err == nil && !found {
87 | err = fmt.Errorf("cannot find item '%s' from '%s'", targetName, sourceFile)
88 | }
89 | return
90 | }
91 |
--------------------------------------------------------------------------------
/pkg/compress/testdata/simple.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LinuxSuRen/http-downloader/ec90fa5ea59a1aef39fef78b71e42f7ff32a28bf/pkg/compress/testdata/simple.tar.gz
--------------------------------------------------------------------------------
/pkg/compress/testdata/simple.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LinuxSuRen/http-downloader/ec90fa5ea59a1aef39fef78b71e42f7ff32a28bf/pkg/compress/testdata/simple.zip
--------------------------------------------------------------------------------
/pkg/compress/types.go:
--------------------------------------------------------------------------------
1 | package compress
2 |
3 | import (
4 | "archive/tar"
5 | "io"
6 | "os"
7 | "path/filepath"
8 | "strings"
9 | )
10 |
11 | // Compress is a common compress interface
12 | type Compress interface {
13 | ExtractFiles(sourceFile, targetName string) error
14 | }
15 |
16 | func extraFile(name, targetName, tarFile string, header *tar.Header, tarReader *tar.Reader) (err error) {
17 | if name != targetName && !strings.HasSuffix(name, "/"+targetName) {
18 | return
19 | }
20 | var targetFile *os.File
21 | if targetFile, err = os.OpenFile(filepath.Join(filepath.Dir(tarFile), targetName),
22 | os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)); err != nil {
23 | return
24 | }
25 | defer func() {
26 | _ = targetFile.Close()
27 | }()
28 | _, err = io.Copy(targetFile, tarReader)
29 | return
30 | }
31 |
32 | // GetCompressor gets the compressor base on file extension
33 | func GetCompressor(extension string, additionBinaries []string) Compress {
34 | // Select the right decompressor based on file type
35 | switch extension {
36 | case ".xz":
37 | return NewXz(additionBinaries)
38 | case ".zip":
39 | return NewZip(additionBinaries)
40 | case ".gz", ".tar.gz", ".tgz":
41 | return NewGZip(additionBinaries)
42 | case ".bz2":
43 | return NewBzip2(additionBinaries)
44 | }
45 | return nil
46 | }
47 |
48 | // IsSupport checks if the desired file extension
49 | func IsSupport(extension string) bool {
50 | return GetCompressor(extension, nil) != nil
51 | }
52 |
--------------------------------------------------------------------------------
/pkg/compress/types_test.go:
--------------------------------------------------------------------------------
1 | package compress
2 |
3 | import (
4 | "archive/tar"
5 | "bytes"
6 | "fmt"
7 | "github.com/stretchr/testify/assert"
8 | "os"
9 | "path"
10 | "reflect"
11 | "testing"
12 | )
13 |
14 | func TestGetCompressor(t *testing.T) {
15 | type args struct {
16 | extension string
17 | additionBinaries []string
18 | }
19 | tests := []struct {
20 | name string
21 | args args
22 | want Compress
23 | }{{
24 | name: "unknown type",
25 | args: args{extension: ".xdf"},
26 | want: nil,
27 | }, {
28 | name: ".zip",
29 | args: args{extension: ".zip"},
30 | want: NewZip(nil),
31 | }, {
32 | name: ".xz",
33 | args: args{extension: ".xz"},
34 | want: NewXz(nil),
35 | }, {
36 | name: ".tar.gz",
37 | args: args{extension: ".tar.gz"},
38 | want: NewGZip(nil),
39 | }, {
40 | name: ".bz2",
41 | args: args{extension: ".bz2"},
42 | want: NewBzip2(nil),
43 | }}
44 | for _, tt := range tests {
45 | t.Run(tt.name, func(t *testing.T) {
46 | if got := GetCompressor(tt.args.extension, tt.args.additionBinaries); !reflect.DeepEqual(got, tt.want) {
47 | t.Errorf("GetCompressor() = %v, want %v", got, tt.want)
48 | } else if got != nil {
49 | err := got.ExtractFiles("", "")
50 | assert.NotNil(t, err)
51 |
52 | // test with a regular file
53 | var f *os.File
54 | if f, err = os.CreateTemp(os.TempDir(), "fake"); err != nil {
55 | return
56 | }
57 | assert.Nil(t, err)
58 | assert.NotNil(t, f)
59 | defer func() {
60 | _ = os.RemoveAll(f.Name())
61 | }()
62 |
63 | err = got.ExtractFiles(f.Name(), "fake")
64 | assert.NotNil(t, err)
65 |
66 | // try to read a non-exist file
67 | err = got.ExtractFiles(path.Join(os.TempDir(), "fake"), "fake")
68 | assert.NotNil(t, err)
69 | }
70 | })
71 | }
72 | }
73 |
74 | func TestIsSupport(t *testing.T) {
75 | type args struct {
76 | extension string
77 | }
78 | tests := []struct {
79 | name string
80 | args args
81 | want bool
82 | }{{
83 | name: "supported extension: .tar.gz",
84 | args: args{
85 | extension: path.Ext("test.tar.gz"),
86 | },
87 | want: true,
88 | }, {
89 | name: "supported extension: .xz",
90 | args: args{
91 | extension: path.Ext("test.xz"),
92 | },
93 | want: true,
94 | }, {
95 | name: "supported extension: .zip",
96 | args: args{
97 | extension: path.Ext("test.zip"),
98 | },
99 | want: true,
100 | }, {
101 | name: "supported extension: .gz",
102 | args: args{
103 | extension: path.Ext("test.gz"),
104 | },
105 | want: true,
106 | }, {
107 | name: "supported extension: .tgz",
108 | args: args{
109 | extension: path.Ext("test.tgz"),
110 | },
111 | want: true,
112 | }, {
113 | name: "supported extension: .bz2",
114 | args: args{
115 | extension: path.Ext("test.bz2"),
116 | },
117 | want: true,
118 | }, {
119 | name: "not supported extension: .ab",
120 | args: args{
121 | extension: path.Ext("test.ab"),
122 | },
123 | want: false,
124 | }}
125 | for _, tt := range tests {
126 | t.Run(tt.name, func(t *testing.T) {
127 | if got := IsSupport(tt.args.extension); got != tt.want {
128 | t.Errorf("IsSupport() = %v, want %v", got, tt.want)
129 | }
130 | })
131 | }
132 | }
133 |
134 | func Test_extraFile(t *testing.T) {
135 | type args struct {
136 | name string
137 | targetName string
138 | tarFile string
139 | header *tar.Header
140 | tarReader *tar.Reader
141 | }
142 | tests := []struct {
143 | name string
144 | args args
145 | wantErr assert.ErrorAssertionFunc
146 | }{{
147 | name: "invalid name",
148 | args: args{
149 | name: "fake",
150 | targetName: "fake.go",
151 | tarFile: "",
152 | header: nil,
153 | tarReader: nil,
154 | },
155 | wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
156 | assert.Nil(t, err)
157 | return true
158 | },
159 | }, {
160 | name: "normal",
161 | args: args{
162 | name: "fake",
163 | targetName: "fake",
164 | tarFile: "fake",
165 | header: &tar.Header{Mode: 400},
166 | tarReader: tar.NewReader(bytes.NewBufferString("fake")),
167 | },
168 | wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
169 | assert.Nil(t, err)
170 | return true
171 | },
172 | }}
173 | for _, tt := range tests {
174 | t.Run(tt.name, func(t *testing.T) {
175 | var file *os.File
176 | var err error
177 | if tt.args.tarFile != "" {
178 | file, err = os.CreateTemp(os.TempDir(), tt.args.tarFile)
179 | assert.Nil(t, err)
180 | assert.NotNil(t, file)
181 | }
182 |
183 | if err != nil || file == nil {
184 | return
185 | }
186 |
187 | defer func() {
188 | _ = os.RemoveAll(tt.args.tarFile)
189 | }()
190 | err = extraFile(tt.args.name, tt.args.targetName, tt.args.tarFile, tt.args.header, tt.args.tarReader)
191 | tt.wantErr(t, err, fmt.Sprintf("extraFile(%v, %v, %v, %v, %v)", tt.args.name, tt.args.targetName, tt.args.tarFile, tt.args.header, tt.args.tarReader))
192 | })
193 | }
194 | }
195 |
196 | func TestExtractFiles(t *testing.T) {
197 | t.Run("test .tar.gz", func(t *testing.T) {
198 | compressor := GetCompressor(".tar.gz", []string{"bb", "cc"})
199 | assert.NotNil(t, compressor)
200 |
201 | err := compressor.ExtractFiles("testdata/simple.tar.gz", "aa")
202 | assert.NoError(t, err)
203 |
204 | assertFileContentEqual(t, "testdata/aa", "aa\n")
205 | assertFileContentEqual(t, "testdata/bb", "bb\n")
206 | assertFileContentEqual(t, "testdata/cc", "cc\n")
207 | })
208 |
209 | t.Run("test .zip", func(t *testing.T) {
210 | compressor := GetCompressor(".zip", []string{"bb", "cc"})
211 | assert.NotNil(t, compressor)
212 |
213 | err := compressor.ExtractFiles("testdata/simple.zip", "aa")
214 | assert.NoError(t, err)
215 |
216 | assertFileContentEqual(t, "testdata/aa", "aa\n")
217 | assertFileContentEqual(t, "testdata/bb", "bb\n")
218 | assertFileContentEqual(t, "testdata/cc", "cc\n")
219 |
220 | // invalid parameters
221 | err = compressor.ExtractFiles("", "")
222 | assert.Error(t, err)
223 | })
224 | }
225 |
226 | func assertFileContentEqual(t *testing.T, filePath string, expectedContent string) {
227 | defer func() {
228 | _ = os.RemoveAll(filePath)
229 | }()
230 | if data, err := os.ReadFile(filePath); err == nil {
231 | assert.Equal(t, expectedContent, string(data))
232 | } else {
233 | t.Fatalf("not found %q: %v", filePath, err)
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/pkg/compress/xz.go:
--------------------------------------------------------------------------------
1 | package compress
2 |
3 | import (
4 | "archive/tar"
5 | "errors"
6 | "github.com/xi2/xz"
7 | "os"
8 | )
9 |
10 | // Xz implements a compress which based is based on xz
11 | type Xz struct {
12 | additionBinaries []string
13 | }
14 |
15 | // NewXz creates an instance of Xz
16 | func NewXz(additionBinaries []string) *Xz {
17 | return &Xz{additionBinaries: additionBinaries}
18 | }
19 |
20 | // make sure Xz implements the interface Compress
21 | var _ Compress = &Xz{}
22 |
23 | // ExtractFiles extracts files from a target compress file
24 | func (x *Xz) ExtractFiles(sourceFile, targetName string) (err error) {
25 | if sourceFile == "" || targetName == "" {
26 | err = errors.New("source or target filename is empty")
27 | return
28 | }
29 | var f *os.File
30 | if f, err = os.Open(sourceFile); err != nil {
31 | return err
32 | }
33 | defer func() {
34 | _ = f.Close()
35 | }()
36 |
37 | // Create a xz Reader
38 | r, err := xz.NewReader(f, 0)
39 | if err != nil {
40 | return
41 | }
42 |
43 | err = zipProcess(tar.NewReader(r), targetName, sourceFile, x.additionBinaries)
44 | return
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/compress/zip.go:
--------------------------------------------------------------------------------
1 | package compress
2 |
3 | import (
4 | "archive/zip"
5 | "errors"
6 | "io"
7 | "os"
8 | "path/filepath"
9 | )
10 |
11 | // Zip implements a compress which is base on zip file
12 | type Zip struct {
13 | additionBinaries []string
14 | }
15 |
16 | // NewZip creates an instance of zip
17 | func NewZip(additionBinaries []string) *Zip {
18 | return &Zip{additionBinaries: additionBinaries}
19 | }
20 |
21 | // make sure Zip implements the interface Compress
22 | var _ Compress = &Zip{}
23 |
24 | // ExtractFiles extracts files from a target compress file
25 | func (z *Zip) ExtractFiles(sourceFile, targetName string) (err error) {
26 | if sourceFile == "" || targetName == "" {
27 | err = errors.New("source or target filename is empty")
28 | return
29 | }
30 |
31 | var archive *zip.ReadCloser
32 | if archive, err = zip.OpenReader(sourceFile); err != nil {
33 | return
34 | }
35 | defer func() {
36 | _ = archive.Close()
37 | }()
38 |
39 | z.additionBinaries = append(z.additionBinaries, targetName)
40 | for _, f := range archive.File {
41 | if f.FileInfo().IsDir() {
42 | continue
43 | }
44 |
45 | for _, ff := range z.additionBinaries {
46 | if filepath.Base(f.Name) != ff {
47 | continue
48 | }
49 |
50 | var targetFile *os.File
51 | if targetFile, err = os.OpenFile(filepath.Join(filepath.Dir(sourceFile), ff),
52 | os.O_CREATE|os.O_RDWR, f.Mode()); err != nil {
53 | return
54 | }
55 |
56 | var fileInArchive io.ReadCloser
57 | fileInArchive, err = f.Open()
58 | if err != nil {
59 | return
60 | }
61 | if _, err = io.Copy(targetFile, fileInArchive); err != nil {
62 | return
63 | }
64 | _ = targetFile.Close()
65 | break
66 | }
67 | }
68 | return
69 | }
70 |
--------------------------------------------------------------------------------
/pkg/doc.go:
--------------------------------------------------------------------------------
1 | // Package pkg provides the features to interal and external users.
2 | // All CLI related logics should not be in this package.
3 | package pkg
4 |
--------------------------------------------------------------------------------
/pkg/error.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | /**
4 | * This file was deprecated, please use the following package instead
5 | * github.com/linuxsuren/http-downloader/pkg/net
6 | */
7 |
8 | import (
9 | "fmt"
10 | "github.com/linuxsuren/http-downloader/pkg/net"
11 | )
12 |
13 | // DownloadError represents the error of HTTP download
14 | type DownloadError net.DownloadError
15 |
16 | // Error print the error message
17 | func (e *DownloadError) Error() string {
18 | return (*net.DownloadError)(e).Error()
19 | }
20 |
21 | // ErrorWrap warps the error if it is not nil
22 | func ErrorWrap(err error, format string, a ...any) error {
23 | if err != nil {
24 | return fmt.Errorf(format, a...)
25 | }
26 | return nil
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/error_test.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | import (
4 | "errors"
5 | "github.com/stretchr/testify/assert"
6 | "testing"
7 | )
8 |
9 | func TestDownloadError_Error(t *testing.T) {
10 | tests := []struct {
11 | name string
12 | e DownloadError
13 | want string
14 | }{{
15 | name: "normal",
16 | e: DownloadError{Message: "message", StatusCode: 1},
17 | want: "message: status code: 1",
18 | }}
19 | for _, tt := range tests {
20 | t.Run(tt.name, func(t *testing.T) {
21 | if got := tt.e.Error(); got != tt.want {
22 | t.Errorf("Error() = %v, want %v", got, tt.want)
23 | }
24 | })
25 | }
26 | }
27 |
28 | func TestErrorWrap(t *testing.T) {
29 | sampleErr := errors.New("sample")
30 |
31 | type arg struct {
32 | err error
33 | format string
34 | args []string
35 | }
36 | tests := []struct {
37 | name string
38 | arg arg
39 | hasErr bool
40 | }{{
41 | name: "err is not nil",
42 | arg: arg{
43 | err: sampleErr,
44 | format: "",
45 | },
46 | hasErr: true,
47 | }, {
48 | name: "error is nil",
49 | arg: arg{},
50 | hasErr: false,
51 | }}
52 | for _, tt := range tests {
53 | t.Run(tt.name, func(t *testing.T) {
54 | err := ErrorWrap(tt.arg.err, tt.arg.format, tt.arg.args)
55 | assert.Equal(t, tt.hasErr, err != nil)
56 | })
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/http.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | /**
4 | * This file was deprecated, please use the following package instead
5 | * github.com/linuxsuren/http-downloader/pkg/net
6 | */
7 |
8 | import (
9 | "github.com/linuxsuren/http-downloader/pkg/net"
10 | )
11 |
12 | const (
13 | // ContentType is for the http header of content type
14 | ContentType = net.ContentType
15 | // ApplicationForm is for the form submit
16 | ApplicationForm = net.ApplicationForm
17 | )
18 |
19 | // HTTPDownloader is the downloader for http request
20 | type HTTPDownloader net.HTTPDownloader
21 |
22 | // DownloadFile download a file with the progress
23 | // deprecated
24 | func (h *HTTPDownloader) DownloadFile() error {
25 | return (*net.HTTPDownloader)(h).DownloadFile()
26 | }
27 |
28 | // DownloadFileWithMultipleThread downloads the files with multiple threads
29 | func DownloadFileWithMultipleThread(targetURL, targetFilePath string, thread int, showProgress bool) (err error) {
30 | return net.DownloadFileWithMultipleThreadKeepParts(targetURL, targetFilePath, thread, false, showProgress)
31 | }
32 |
33 | // DownloadFileWithMultipleThreadKeepParts downloads the files with multiple threads
34 | // deprecated
35 | func DownloadFileWithMultipleThreadKeepParts(targetURL, targetFilePath string, thread int, keepParts, showProgress bool) (err error) {
36 | return net.DownloadFileWithMultipleThreadKeepParts(targetURL, targetFilePath, thread, keepParts, showProgress)
37 | }
38 |
39 | // DownloadWithContinue downloads the files continuously
40 | // deprecated
41 | func DownloadWithContinue(targetURL, output string, index, continueAt, end int64, showProgress bool) (err error) {
42 | downloader := &net.ContinueDownloader{}
43 | return downloader.DownloadWithContinue(targetURL, output, index, continueAt, end, showProgress)
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/installer/cmd.go:
--------------------------------------------------------------------------------
1 | package installer
2 |
3 | func (o *Installer) runCommandList(cmds []CmdWithArgs) (err error) {
4 | for i := range cmds {
5 | cmd := cmds[i]
6 | if err = o.Execer.RunCommand(cmd.Cmd, cmd.Args...); err != nil {
7 | return
8 | }
9 | }
10 | return
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/installer/cmd_test.go:
--------------------------------------------------------------------------------
1 | package installer
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestRunCommandList(t *testing.T) {
12 | i := &Installer{
13 | Execer: &fakeruntime.FakeExecer{},
14 | }
15 | assert.Nil(t, i.runCommandList(nil))
16 | assert.Nil(t, i.runCommandList([]CmdWithArgs{{
17 | Cmd: "ls",
18 | }}))
19 |
20 | errInstaller := &Installer{
21 | Execer: fakeruntime.FakeExecer{
22 | ExpectError: errors.New("error"),
23 | },
24 | }
25 | assert.NotNil(t, errInstaller.runCommandList([]CmdWithArgs{{
26 | Cmd: "ls",
27 | }}))
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/installer/doc.go:
--------------------------------------------------------------------------------
1 | // Package installer is the core part of the software package installation.
2 | package installer
3 |
--------------------------------------------------------------------------------
/pkg/installer/fetch_test.go:
--------------------------------------------------------------------------------
1 | package installer
2 |
3 | import (
4 | "context"
5 | "os/user"
6 | "path/filepath"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestGetConfigDir(t *testing.T) {
13 | u, err := user.Current()
14 | assert.Nil(t, err)
15 |
16 | var fetcher Fetcher
17 | fetcher = &DefaultFetcher{}
18 | dir, err := fetcher.GetConfigDir()
19 | assert.Nil(t, err)
20 | assert.Equal(t, filepath.Join(u.HomeDir, ".config", "hd-home"), dir)
21 | fetcher.SetContext(context.TODO())
22 |
23 | // test the fake fetcher
24 | fetcher = &FakeFetcher{ConfigDir: "fake"}
25 | dir, err = fetcher.GetConfigDir()
26 | assert.Nil(t, err)
27 | assert.Equal(t, "fake", dir)
28 | err = fetcher.FetchLatestRepo("", "", nil)
29 | assert.Nil(t, err)
30 | fetcher.SetContext(context.TODO())
31 |
32 | dir, err = fetcher.GetHomeDir()
33 | assert.Equal(t, "", dir)
34 | assert.Nil(t, err)
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/installer/process.go:
--------------------------------------------------------------------------------
1 | package installer
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "path"
8 | "path/filepath"
9 | "strings"
10 |
11 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
12 |
13 | "github.com/linuxsuren/http-downloader/pkg/common"
14 | "github.com/linuxsuren/http-downloader/pkg/compress"
15 | )
16 |
17 | // Install installs a package
18 | func (o *Installer) Install() (err error) {
19 | if o.Execer.OS() == "windows" {
20 | o.Name = fmt.Sprintf("%s.exe", o.Name)
21 | }
22 | targetBinary := o.Name
23 | if o.Package != nil && o.Package.TargetBinary != "" {
24 | // this is the desired binary file
25 | targetBinary = o.Package.TargetBinary
26 | }
27 |
28 | var source string
29 | var target string
30 | tarFile := o.Output
31 | if o.Tar {
32 | if err = o.extractFiles(tarFile, o.Name); err == nil {
33 | source = fmt.Sprintf("%s/%s", filepath.Dir(tarFile), o.Name)
34 | target = path.Join(o.TargetDirectory, targetBinary)
35 | } else {
36 | err = fmt.Errorf("cannot extract %s from tar file, error: %v", tarFile, err)
37 | }
38 | } else {
39 | source = o.Source
40 | target = path.Join(o.TargetDirectory, targetBinary)
41 | }
42 |
43 | if err == nil {
44 | if o.Package != nil && o.Package.PreInstalls != nil {
45 | if err = o.runCommandList(o.Package.PreInstalls); err != nil {
46 | return
47 | }
48 | }
49 |
50 | if o.Package != nil && o.Package.Installation != nil {
51 | err = o.Execer.RunCommand(o.Package.Installation.Cmd, o.Package.Installation.Args...)
52 | } else {
53 | if err = o.OverWriteBinary(source, target); err != nil {
54 | return
55 | }
56 |
57 | for i := range o.AdditionBinaries {
58 | addition := o.AdditionBinaries[i]
59 | if err = o.OverWriteBinary(addition, path.Join(o.TargetDirectory, filepath.Base(addition))); err != nil {
60 | return
61 | }
62 | }
63 | }
64 |
65 | if o.Package != nil {
66 | for i := range o.Package.DefaultConfigFile {
67 | configFile := o.Package.DefaultConfigFile[i]
68 | configFilePath := configFile.Path
69 | configDir := filepath.Dir(configFilePath)
70 |
71 | if configFile.OS == o.Execer.OS() {
72 | if err = os.MkdirAll(configDir, 0750); err != nil {
73 | if strings.Contains(err.Error(), "permission denied") {
74 | err = o.Execer.RunCommandWithSudo("mkdir", "-p", configDir)
75 | }
76 |
77 | if err != nil {
78 | err = fmt.Errorf("cannot create config dir: %s, error: %v", configDir, err)
79 | return
80 | }
81 | }
82 |
83 | if err = os.WriteFile(configFilePath, []byte(configFile.Content), 0622); err != nil {
84 | if strings.Contains(err.Error(), "permission denied") {
85 | if err = o.Execer.RunCommandWithSudo("touch", configFilePath); err == nil {
86 | err = o.Execer.RunCommandWithSudo("chmod", "+w", configFilePath)
87 | }
88 | }
89 |
90 | if err != nil {
91 | err = fmt.Errorf("cannot write config file: %s, error: %v", configFilePath, err)
92 | return
93 | }
94 | }
95 |
96 | fmt.Printf("config file [%s] is ready.\n", configFilePath)
97 | }
98 | }
99 | }
100 |
101 | if err == nil && o.Package != nil && o.Package.PostInstalls != nil {
102 | err = o.runCommandList(o.Package.PostInstalls)
103 | }
104 |
105 | if err == nil && o.Package != nil && o.Package.TestInstalls != nil {
106 | err = o.runCommandList(o.Package.TestInstalls)
107 | }
108 |
109 | if err == nil && o.CleanPackage {
110 | if cleanErr := os.RemoveAll(tarFile); cleanErr != nil {
111 | fmt.Println("cannot remove file", tarFile, ", error:", cleanErr)
112 | }
113 | }
114 | }
115 | return
116 | }
117 |
118 | // OverWriteBinary install a binary file
119 | func (o *Installer) OverWriteBinary(sourceFile, targetPath string) (err error) {
120 | fmt.Println("install", sourceFile, "to", targetPath)
121 | switch o.Execer.OS() {
122 | case fakeruntime.OSLinux, fakeruntime.OSDarwin:
123 | if err = o.Execer.RunCommand("chmod", "u+x", sourceFile); err != nil {
124 | return
125 | }
126 |
127 | if common.IsDirWriteable(path.Dir(targetPath)) != nil {
128 | if err = o.Execer.RunCommandWithSudo("rm", "-rf", targetPath); err != nil {
129 | return
130 | }
131 | } else {
132 | if err = o.Execer.RunCommand("rm", "-rf", targetPath); err != nil {
133 | return
134 | }
135 | }
136 |
137 | if common.IsDirWriteable(path.Dir(targetPath)) != nil {
138 | err = o.Execer.RunCommandWithSudo("mv", sourceFile, targetPath)
139 | } else {
140 | err = o.Execer.RunCommand("mv", sourceFile, targetPath)
141 | }
142 | default:
143 | sourceF, sourceE := os.Open(sourceFile)
144 | targetF, targetE := os.OpenFile(targetPath, os.O_CREATE|os.O_RDWR, 0600)
145 | if sourceE != nil || targetE != nil {
146 | err = fmt.Errorf("failed to open source file: %v, or target file: %v", sourceE, targetE)
147 | return
148 | }
149 |
150 | if _, err = io.Copy(targetF, sourceF); err != nil {
151 | err = fmt.Errorf("cannot copy %s from %s to %v, error: %v", o.Name, sourceFile, targetPath, err)
152 | } else {
153 | _ = os.RemoveAll(sourceFile)
154 | }
155 | }
156 | return
157 | }
158 |
159 | func (o *Installer) extractFiles(tarFile, targetName string) (err error) {
160 | compressor := compress.GetCompressor(path.Ext(tarFile), o.AdditionBinaries)
161 | if compressor == nil {
162 | err = fmt.Errorf("no compressor support for %s", tarFile)
163 | } else {
164 | err = compressor.ExtractFiles(tarFile, targetName)
165 | }
166 | return
167 | }
168 |
--------------------------------------------------------------------------------
/pkg/installer/process_test.go:
--------------------------------------------------------------------------------
1 | package installer
2 |
3 | import (
4 | "os"
5 | "path"
6 | "testing"
7 |
8 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestInstallerExtractFiles(t *testing.T) {
13 | installer := &Installer{}
14 |
15 | assert.NotNil(t, installer.extractFiles("fake.fake", ""))
16 | assert.NotNil(t, installer.extractFiles("a.tar.gz", ""))
17 | }
18 |
19 | func TestOverwriteBinary(t *testing.T) {
20 | installer := &Installer{
21 | Execer: &fakeruntime.FakeExecer{},
22 | }
23 |
24 | sourceFile := path.Join(os.TempDir(), "fake-1")
25 | targetFile := path.Join(os.TempDir(), "fake-2")
26 |
27 | _ = os.WriteFile(sourceFile, []byte("fake"), 0600)
28 |
29 | defer func() {
30 | _ = os.RemoveAll(sourceFile)
31 | }()
32 | defer func() {
33 | _ = os.RemoveAll(targetFile)
34 | }()
35 |
36 | assert.Nil(t, installer.OverWriteBinary(sourceFile, targetFile))
37 | }
38 |
39 | func TestInstall(t *testing.T) {
40 | tests := []struct {
41 | name string
42 | installer *Installer
43 | hasErr bool
44 | }{{
45 | name: "empty",
46 | installer: &Installer{
47 | Execer: fakeruntime.FakeExecer{},
48 | },
49 | hasErr: true,
50 | }, {
51 | name: "fake linux",
52 | installer: &Installer{
53 | Execer: fakeruntime.FakeExecer{
54 | ExpectOS: fakeruntime.OSLinux,
55 | },
56 | },
57 | hasErr: false,
58 | }}
59 | for _, tt := range tests {
60 | t.Run(tt.name, func(t *testing.T) {
61 | err := tt.installer.Install()
62 | if tt.hasErr {
63 | assert.NotNil(t, err)
64 | } else {
65 | assert.Nil(t, err)
66 | }
67 | })
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/pkg/installer/types.go:
--------------------------------------------------------------------------------
1 | package installer
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // PackagingFormat is used for containing config depending on machine
10 | type PackagingFormat struct {
11 | Windows string `yaml:"windows"`
12 | Linux string `yaml:"linux"`
13 | Format string
14 | }
15 |
16 | // String returns a pretty string
17 | func (f PackagingFormat) String() string {
18 | return fmt.Sprintf("Windows: %s, Linux: %s, Format: %s", f.Windows, f.Linux, f.Format)
19 | }
20 |
21 | // HDConfig is the config of http-downloader
22 | type HDConfig struct {
23 | Name string `yaml:"name"`
24 | Categories []string `yaml:"categories"`
25 | Filename string `yaml:"filename"`
26 | FormatOverrides PackagingFormat `yaml:"formatOverrides"`
27 | Binary string `yaml:"binary"`
28 | TargetBinary string `yaml:"targetBinary"`
29 | TargetDirectory string `yaml:"targetDirectory"`
30 | AdditionBinaries []string `yaml:"additionBinaries"`
31 | FromSource bool `yaml:"fromSource"`
32 | URL string `yaml:"url"`
33 | Tar string `yaml:"tar"`
34 | LatestVersion string `yaml:"latestVersion"`
35 | SupportOS []string `yaml:"supportOS"`
36 | SupportArch []string `yaml:"supportArch"`
37 | Replacements map[string]string `yaml:"replacements"`
38 | Requirements []string `yaml:"requirements"`
39 | Installation *CmdWithArgs `yaml:"installation"`
40 | DefaultConfigFile []ConfigFile `yaml:"defaultConfigFiles"`
41 | PreInstalls []CmdWithArgs `yaml:"preInstalls"`
42 | PostInstalls []CmdWithArgs `yaml:"postInstalls"`
43 | TestInstalls []CmdWithArgs `yaml:"testInstalls"`
44 | Version string `yaml:"version"`
45 | VersionCmd string `yaml:"versionCmd"`
46 |
47 | Org, Repo string
48 | }
49 |
50 | // ConfigFile represents a config file
51 | type ConfigFile struct {
52 | OS string `yaml:"os"`
53 | Path string `yaml:"path"`
54 | Content string `yaml:"content"`
55 | }
56 |
57 | // CmdWithArgs is a command with arguments
58 | type CmdWithArgs struct {
59 | Cmd string `yaml:"cmd"`
60 | Args []string `yaml:"args"`
61 | }
62 |
63 | // HDPackage represents a package of http-downloader
64 | type HDPackage struct {
65 | Name string
66 | Version string // e.g. v1.0.1
67 | VersionNum string // e.g. 1.0.1
68 | OS string // e.g. linux, darwin
69 | Arch string // e.g. amd64
70 | AdditionBinaries []string
71 | }
72 |
73 | // Installer is a tool to install a package
74 | type Installer struct {
75 | Package *HDConfig
76 | Tar bool
77 | Output string
78 | TargetDirectory string
79 | Source string
80 | Name string
81 | CleanPackage bool
82 | Provider string
83 | OS string
84 | Arch string
85 | Fetch bool
86 | AdditionBinaries []string
87 |
88 | Org string
89 | Repo string
90 | ProxyGitHub string
91 |
92 | Execer fakeruntime.Execer
93 | }
94 |
--------------------------------------------------------------------------------
/pkg/log/doc.go:
--------------------------------------------------------------------------------
1 | // Package log provides the core logger functions.
2 | // Basically, this is a wrapper of log.Logger
3 | package log
4 |
--------------------------------------------------------------------------------
/pkg/log/level.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "context"
5 | "io"
6 | syslog "log"
7 | )
8 |
9 | // LevelLog is the wrapper of built-in log.Logger
10 | type LevelLog struct {
11 | *syslog.Logger
12 | level int
13 | }
14 |
15 | // Info prints the info level message.
16 | // Info level means the level >= 3
17 | func (l *LevelLog) Info(v ...any) {
18 | if l.level >= 3 {
19 | l.Println(v...)
20 | }
21 | }
22 |
23 | // Debug prints the debug level message.
24 | // Debug level means the level >= 7
25 | func (l *LevelLog) Debug(v ...any) {
26 | if l.level >= 7 {
27 | l.Println(v...)
28 | }
29 | }
30 |
31 | // SetLevel sets the level of logger
32 | func (l *LevelLog) SetLevel(level int) *LevelLog {
33 | l.level = level
34 | return l
35 | }
36 |
37 | // GetLevel returns the level of logger
38 | func (l *LevelLog) GetLevel() int {
39 | return l.level
40 | }
41 |
42 | // SetOutput sets the output destination for the logger.
43 | func (l *LevelLog) SetOutput(writer io.Writer) *LevelLog {
44 | l.Logger.SetOutput(writer)
45 | return l
46 | }
47 |
48 | // LoggerContext used to get and set context value
49 | type LoggerContext string
50 |
51 | // LoggerContextKey is the key of context for get/set Logger
52 | const LoggerContextKey = LoggerContext("LoggerContext")
53 |
54 | // ContextAware is the interface for getting context.Context
55 | type ContextAware interface {
56 | // Context returns the instance of context.Context
57 | Context() context.Context
58 | }
59 |
60 | // GetLoggerFromContextOrDefault returns a Logger instance from context,
61 | // or a default instance if no Logger in the context
62 | func GetLoggerFromContextOrDefault(aware ContextAware) (logger *LevelLog) {
63 | var ok bool
64 | if aware.Context() != nil {
65 | val := aware.Context().Value(LoggerContextKey)
66 | logger, ok = val.(*LevelLog)
67 | }
68 |
69 | if !ok {
70 | logger = GetLogger()
71 | }
72 | return
73 | }
74 |
75 | // GetLogger returns an instance of Logger
76 | func GetLogger() *LevelLog {
77 | return &LevelLog{
78 | Logger: syslog.Default(),
79 | level: 3,
80 | }
81 | }
82 |
83 | // NewContextWithLogger returns a new context with given logger level
84 | func NewContextWithLogger(ctx context.Context, level int) context.Context {
85 | logger := GetLogger().SetLevel(level)
86 | return context.WithValue(ctx, LoggerContextKey, logger)
87 | }
88 |
--------------------------------------------------------------------------------
/pkg/log/level_test.go:
--------------------------------------------------------------------------------
1 | package log_test
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "testing"
7 |
8 | "github.com/linuxsuren/http-downloader/pkg/log"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestLogger(t *testing.T) {
13 | t.Run("default level", func(t *testing.T) {
14 | logger := log.GetLoggerFromContextOrDefault(&fakeContextAwareObj{})
15 | assert.Equal(t, 3, logger.GetLevel())
16 |
17 | logger = log.GetLoggerFromContextOrDefault(&fakeContextAwareObj{ctx: context.Background()})
18 | assert.Equal(t, 3, logger.GetLevel())
19 |
20 | ctx := log.NewContextWithLogger(context.Background(), 5)
21 | logger = log.GetLoggerFromContextOrDefault(&fakeContextAwareObj{ctx: ctx})
22 | assert.Equal(t, 5, logger.GetLevel())
23 | })
24 |
25 | t.Run("print in different level", func(t *testing.T) {
26 | buf := new(bytes.Buffer)
27 | logger := log.GetLogger().SetOutput(buf)
28 |
29 | logger.Debug("debug")
30 | logger.Info("info")
31 |
32 | assert.Contains(t, buf.String(), "info")
33 |
34 | logger.SetLevel(7)
35 | logger.Debug("debug")
36 | assert.Contains(t, buf.String(), "debug")
37 | })
38 | }
39 |
40 | type fakeContextAwareObj struct {
41 | ctx context.Context
42 | }
43 |
44 | func (f *fakeContextAwareObj) Context() context.Context {
45 | return f.ctx
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/net/doc.go:
--------------------------------------------------------------------------------
1 | // Package net provides the core download functions
2 | package net
3 |
--------------------------------------------------------------------------------
/pkg/net/error.go:
--------------------------------------------------------------------------------
1 | package net
2 |
3 | import "fmt"
4 |
5 | // DownloadError represents the error of HTTP download
6 | type DownloadError struct {
7 | StatusCode int
8 | Message string
9 | }
10 |
11 | // Error print the error message
12 | func (e *DownloadError) Error() string {
13 | return fmt.Sprintf("%s: status code: %d", e.Message, e.StatusCode)
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/net/error_test.go:
--------------------------------------------------------------------------------
1 | package net_test
2 |
3 | import (
4 | "github.com/linuxsuren/http-downloader/pkg/net"
5 | "github.com/stretchr/testify/assert"
6 | "testing"
7 | )
8 |
9 | func TestError(t *testing.T) {
10 | err := net.DownloadError{
11 | Message: "message",
12 | StatusCode: 200,
13 | }
14 | assert.Contains(t, err.Error(), "message")
15 | assert.Contains(t, err.Error(), "200")
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/net/fake.go:
--------------------------------------------------------------------------------
1 | // Package net provides net related functions
2 | package net
3 |
4 | import "fmt"
5 |
6 | // FakeReader is a fake reader for the test purpose
7 | type FakeReader struct {
8 | ExpectErr error
9 | }
10 |
11 | // Read is a fake method
12 | func (e *FakeReader) Read(p []byte) (n int, err error) {
13 | err = e.ExpectErr
14 | fmt.Println(err, "fake")
15 | return
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/net/fake_test.go:
--------------------------------------------------------------------------------
1 | package net_test
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | "github.com/linuxsuren/http-downloader/pkg/net"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestFakeReader(t *testing.T) {
12 | reader := &net.FakeReader{
13 | ExpectErr: errors.New("error"),
14 | }
15 | _, err := reader.Read(nil)
16 | assert.NotNil(t, err)
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/net/progress.go:
--------------------------------------------------------------------------------
1 | package net
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "sync"
8 |
9 | "github.com/k0kubun/go-ansi"
10 | "github.com/schollz/progressbar/v3"
11 | )
12 |
13 | // ProgressIndicator hold the progress of io operation
14 | type ProgressIndicator struct {
15 | Writer io.Writer
16 | Reader io.Reader
17 | Title string
18 |
19 | // bytes.Buffer
20 | Total float64
21 | line int
22 | bar *progressbar.ProgressBar
23 | }
24 |
25 | var line int = 0
26 | var currentLine int = 0
27 | var guard sync.Mutex = sync.Mutex{}
28 |
29 | // GetCurrentLine returns the current line
30 | func GetCurrentLine() int {
31 | return currentLine
32 | }
33 |
34 | // Init set the default value for progress indicator
35 | func (i *ProgressIndicator) Init() {
36 | i.line = line
37 | line++
38 | i.bar = progressbar.NewOptions64(int64(i.Total),
39 | progressbar.OptionSetWriter(ansi.NewAnsiStdout()),
40 | progressbar.OptionEnableColorCodes(true),
41 | progressbar.OptionShowBytes(true),
42 | progressbar.OptionSetWidth(10),
43 | progressbar.OptionFullWidth(),
44 | progressbar.OptionSetDescription(fmt.Sprintf("[cyan][reset] %s", i.Title)),
45 | progressbar.OptionSetTheme(progressbar.Theme{
46 | Saucer: "[green]=[reset]",
47 | SaucerHead: "[green]>[reset]",
48 | SaucerPadding: " ",
49 | BarStart: "[",
50 | BarEnd: "]",
51 | }))
52 | }
53 |
54 | // Close shutdowns the ui process
55 | func (i ProgressIndicator) Close() {
56 | _ = i.bar.Close()
57 | }
58 |
59 | // Write writes the progress
60 | // See also https://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
61 | func (i *ProgressIndicator) Write(p []byte) (n int, err error) {
62 | guard.Lock()
63 | defer guard.Unlock()
64 | bias := currentLine - i.line
65 | currentLine = i.line
66 | if bias > 0 {
67 | // move up
68 | fmt.Fprintf(os.Stdout, "\r\033[%dA", bias)
69 | } else if bias < 0 {
70 | // move down
71 | fmt.Fprintf(os.Stdout, "\r\033[%dB", -bias)
72 | }
73 | n, err = io.MultiWriter(i.Writer, i.bar).Write(p)
74 | return
75 | }
76 |
77 | // Read reads the progress
78 | func (i *ProgressIndicator) Read(p []byte) (n int, err error) {
79 | n, err = io.MultiReader(i.Reader, i.bar).Read(p)
80 | return
81 | }
82 |
--------------------------------------------------------------------------------
/pkg/net/retry_client.go:
--------------------------------------------------------------------------------
1 | package net
2 |
3 | import (
4 | "net/http"
5 | "net/url"
6 | "strings"
7 | )
8 |
9 | // RetryClient is the wrap of http.Client
10 | type RetryClient struct {
11 | http.Client
12 | MaxAttempts int
13 | currentAttempts int
14 | }
15 |
16 | // NewRetryClient creates the instance of RetryClient
17 | func NewRetryClient(client http.Client) *RetryClient {
18 | return &RetryClient{
19 | Client: client,
20 | MaxAttempts: 3,
21 | }
22 | }
23 |
24 | // Do is the wrap of http.Client.Do
25 | func (c *RetryClient) Do(req *http.Request) (rsp *http.Response, err error) {
26 | rsp, err = c.Client.Do(req)
27 | // fmt.Println("client error", err, c.Client.Timeout, reflect.TypeOf(err))
28 |
29 | if _, ok := err.(*url.Error); ok && !strings.Contains(err.Error(), "context canceled") {
30 | // fmt.Println("retry", c.currentAttempts, c.MaxAttempts)
31 | if c.currentAttempts < c.MaxAttempts {
32 | c.currentAttempts++
33 | // fmt.Println("try", c.currentAttempts)
34 | return c.Do(req)
35 | }
36 | }
37 | return
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/net/retry_client_test.go:
--------------------------------------------------------------------------------
1 | package net
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "reflect"
9 | "testing"
10 |
11 | "github.com/golang/mock/gomock"
12 | "github.com/linuxsuren/http-downloader/mock/mhttp"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | const fakeURL = "http://fake"
17 |
18 | func TestRetry(t *testing.T) {
19 | ctrl := gomock.NewController(t)
20 | roundTripper := mhttp.NewMockRoundTripper(ctrl)
21 |
22 | client := &RetryClient{
23 | Client: http.Client{
24 | Transport: roundTripper,
25 | },
26 | MaxAttempts: 3,
27 | }
28 |
29 | mockRequest, _ := http.NewRequest(http.MethodGet, fakeURL, nil)
30 | mockResponse := &http.Response{
31 | StatusCode: http.StatusOK,
32 | Proto: "HTTP/1.1",
33 | Request: mockRequest,
34 | Body: io.NopCloser(bytes.NewBufferString("responseBody")),
35 | }
36 | roundTripper.EXPECT().
37 | RoundTrip(mockRequest).Return(mockResponse, nil)
38 |
39 | request, _ := http.NewRequest(http.MethodGet, fakeURL, nil)
40 | response, err := client.Do(request)
41 | fmt.Println(reflect.TypeOf(err))
42 | assert.Nil(t, err)
43 | assert.NotNil(t, response)
44 | assert.Equal(t, http.StatusOK, response.StatusCode)
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/net/setup_test.go:
--------------------------------------------------------------------------------
1 | package net_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/onsi/ginkgo/reporters"
7 |
8 | . "github.com/onsi/ginkgo"
9 | . "github.com/onsi/gomega"
10 | )
11 |
12 | func TestUtils(t *testing.T) {
13 | RegisterFailHandler(Fail)
14 | junitReporter := reporters.NewJUnitReporter("test-net.xml")
15 | RunSpecsWithDefaultAndCustomReporters(t, "util", []Reporter{junitReporter})
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/os/apk/common.go:
--------------------------------------------------------------------------------
1 | package apk
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | const (
10 | // Tool is the tool name of apk
11 | Tool = "apk"
12 | )
13 |
14 | // CommonInstaller is the installer of a common apk
15 | type CommonInstaller struct {
16 | Name string
17 | Execer fakeruntime.Execer
18 | }
19 |
20 | // Available check if support current platform
21 | func (d *CommonInstaller) Available() (ok bool) {
22 | if d.Execer.OS() == fakeruntime.OSLinux {
23 | _, err := d.Execer.LookPath(Tool)
24 | ok = err == nil
25 | }
26 | return
27 | }
28 |
29 | // Install installs the target package
30 | func (d *CommonInstaller) Install() (err error) {
31 | err = d.Execer.RunCommand(Tool, "add", d.Name)
32 | return
33 | }
34 |
35 | // Uninstall uninstalls the target package
36 | func (d *CommonInstaller) Uninstall() (err error) {
37 | err = d.Execer.RunCommand(Tool, "del", d.Name)
38 | return
39 | }
40 |
41 | // WaitForStart waits for the service be started
42 | func (d *CommonInstaller) WaitForStart() (ok bool, err error) {
43 | ok = true
44 | return
45 | }
46 |
47 | // Start starts the target service
48 | func (d *CommonInstaller) Start() error {
49 | fmt.Println("not supported yet")
50 | return nil
51 | }
52 |
53 | // Stop stops the target service
54 | func (d *CommonInstaller) Stop() error {
55 | fmt.Println("not supported yet")
56 | return nil
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/os/apk/common_test.go:
--------------------------------------------------------------------------------
1 | package apk
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestCommon(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | installer CommonInstaller
15 | expectAvailable bool
16 | hasErr bool
17 | }{{
18 | name: "normal",
19 | installer: CommonInstaller{
20 | Execer: fakeruntime.FakeExecer{
21 | ExpectError: nil,
22 | ExpectOutput: "",
23 | ExpectOS: "linux",
24 | ExpectArch: "amd64",
25 | },
26 | },
27 | expectAvailable: true,
28 | hasErr: false,
29 | }, {
30 | name: "not is linux",
31 | installer: CommonInstaller{
32 | Execer: fakeruntime.FakeExecer{ExpectOS: "darwin"},
33 | },
34 | expectAvailable: false,
35 | hasErr: false,
36 | }, {
37 | name: "command not found",
38 | installer: CommonInstaller{
39 | Execer: fakeruntime.FakeExecer{ExpectError: errors.New("error")},
40 | },
41 | expectAvailable: false,
42 | hasErr: true,
43 | }}
44 | for _, tt := range tests {
45 | t.Run(tt.name, func(t *testing.T) {
46 | assert.Equal(t, tt.expectAvailable, tt.installer.Available())
47 | assert.Nil(t, tt.installer.Start())
48 | assert.Nil(t, tt.installer.Stop())
49 |
50 | ok, err := tt.installer.WaitForStart()
51 | assert.True(t, ok)
52 | assert.Nil(t, err)
53 | assert.Equal(t, tt.hasErr, tt.installer.Install() != nil)
54 | assert.Equal(t, tt.hasErr, tt.installer.Uninstall() != nil)
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/os/apk/doc.go:
--------------------------------------------------------------------------------
1 | // Package apk provides the common functions to install a package via apk.
2 | // See also https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper.
3 | package apk
4 |
--------------------------------------------------------------------------------
/pkg/os/apt/asciinema.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // asciinemaInstallerInUbuntu is the installer of asciinema in CentOS
10 | type asciinemaInstallerInUbuntu struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *asciinemaInstallerInUbuntu) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("apt-get")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the asciinema
24 | func (d *asciinemaInstallerInUbuntu) Install() (err error) {
25 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err != nil {
26 | return
27 | }
28 | if err = d.Execer.RunCommand("apt-get", "install", "-y",
29 | "asciinema"); err != nil {
30 | return
31 | }
32 | return
33 | }
34 |
35 | // Uninstall uninstalls the asciinema
36 | func (d *asciinemaInstallerInUbuntu) Uninstall() (err error) {
37 | err = d.Execer.RunCommand("apt-get", "remove", "-y",
38 | "asciinema")
39 | return
40 | }
41 |
42 | // WaitForStart waits for the service be started
43 | func (d *asciinemaInstallerInUbuntu) WaitForStart() (ok bool, err error) {
44 | ok = true
45 | return
46 | }
47 |
48 | // Start starts the asciinema service
49 | func (d *asciinemaInstallerInUbuntu) Start() error {
50 | fmt.Println("not supported yet")
51 | return nil
52 | }
53 |
54 | // Stop stops the asciinema service
55 | func (d *asciinemaInstallerInUbuntu) Stop() error {
56 | fmt.Println("not supported yet")
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/os/apt/bash-completion.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // bashCompletionInstallerInUbuntu is the installer of bashCompletion in CentOS
10 | type bashCompletionInstallerInUbuntu struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *bashCompletionInstallerInUbuntu) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("apt-get")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the bashCompletion
24 | func (d *bashCompletionInstallerInUbuntu) Install() (err error) {
25 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err != nil {
26 | return
27 | }
28 | if err = d.Execer.RunCommand("apt-get", "install", "-y",
29 | "bash-completion"); err != nil {
30 | return
31 | }
32 | return
33 | }
34 |
35 | // Uninstall uninstalls the bashCompletion
36 | func (d *bashCompletionInstallerInUbuntu) Uninstall() (err error) {
37 | err = d.Execer.RunCommand("apt-get", "remove", "-y",
38 | "bash-completion")
39 | return
40 | }
41 |
42 | // WaitForStart waits for the service be started
43 | func (d *bashCompletionInstallerInUbuntu) WaitForStart() (ok bool, err error) {
44 | ok = true
45 | return
46 | }
47 |
48 | // Start starts the bashCompletion service
49 | func (d *bashCompletionInstallerInUbuntu) Start() error {
50 | fmt.Println("not supported yet")
51 | return nil
52 | }
53 |
54 | // Stop stops the bashCompletion service
55 | func (d *bashCompletionInstallerInUbuntu) Stop() error {
56 | fmt.Println("not supported yet")
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/os/apt/common.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | const (
10 | // Tool is the tool name of apt-get
11 | Tool = "apt-get"
12 | )
13 |
14 | // CommonInstaller is the installer of Conntrack in CentOS
15 | type CommonInstaller struct {
16 | Name string
17 | Execer fakeruntime.Execer
18 | }
19 |
20 | // Available check if support current platform
21 | func (d *CommonInstaller) Available() (ok bool) {
22 | if d.Execer.OS() == fakeruntime.OSLinux {
23 | _, err := d.Execer.LookPath(Tool)
24 | ok = err == nil
25 | }
26 | return
27 | }
28 |
29 | // Install installs the Conntrack
30 | func (d *CommonInstaller) Install() (err error) {
31 | if err = d.Execer.RunCommand(Tool, "update", "-y"); err == nil {
32 | err = d.Execer.RunCommand(Tool, "install", "-y", d.Name)
33 | }
34 | return
35 | }
36 |
37 | // Uninstall uninstalls the Conntrack
38 | func (d *CommonInstaller) Uninstall() (err error) {
39 | err = d.Execer.RunCommand(Tool, "remove", "-y", d.Name)
40 | return
41 | }
42 |
43 | // WaitForStart waits for the service be started
44 | func (d *CommonInstaller) WaitForStart() (ok bool, err error) {
45 | ok = true
46 | return
47 | }
48 |
49 | // Start starts the Conntrack service
50 | func (d *CommonInstaller) Start() error {
51 | fmt.Println("not supported yet")
52 | return nil
53 | }
54 |
55 | // Stop stops the Conntrack service
56 | func (d *CommonInstaller) Stop() error {
57 | fmt.Println("not supported yet")
58 | return nil
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/os/apt/common_test.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestCommon(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | installer CommonInstaller
15 | expectAvailable bool
16 | hasErr bool
17 | }{{
18 | name: "normal",
19 | installer: CommonInstaller{
20 | Execer: fakeruntime.FakeExecer{
21 | ExpectError: nil,
22 | ExpectOutput: "",
23 | ExpectOS: "linux",
24 | ExpectArch: "amd64",
25 | },
26 | },
27 | expectAvailable: true,
28 | hasErr: false,
29 | }, {
30 | name: "not is linux",
31 | installer: CommonInstaller{
32 | Execer: fakeruntime.FakeExecer{ExpectOS: "darwin"},
33 | },
34 | expectAvailable: false,
35 | hasErr: false,
36 | }, {
37 | name: "command not found",
38 | installer: CommonInstaller{
39 | Execer: fakeruntime.FakeExecer{ExpectError: errors.New("error")},
40 | },
41 | expectAvailable: false,
42 | hasErr: true,
43 | }}
44 | for _, tt := range tests {
45 | t.Run(tt.name, func(t *testing.T) {
46 | assert.Equal(t, tt.expectAvailable, tt.installer.Available())
47 | assert.Nil(t, tt.installer.Start())
48 | assert.Nil(t, tt.installer.Stop())
49 |
50 | ok, err := tt.installer.WaitForStart()
51 | assert.True(t, ok)
52 | assert.Nil(t, err)
53 | assert.Equal(t, tt.hasErr, tt.installer.Install() != nil)
54 | assert.Equal(t, tt.hasErr, tt.installer.Uninstall() != nil)
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/os/apt/conntrack.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // conntrackInstallerInUbuntu is the installer of Conntrack in CentOS
10 | type conntrackInstallerInUbuntu struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *conntrackInstallerInUbuntu) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("apt-get")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the Conntrack
24 | func (d *conntrackInstallerInUbuntu) Install() (err error) {
25 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err != nil {
26 | return
27 | }
28 | if err = d.Execer.RunCommand("apt-get", "install", "-y",
29 | "conntrack"); err != nil {
30 | return
31 | }
32 | return
33 | }
34 |
35 | // Uninstall uninstalls the Conntrack
36 | func (d *conntrackInstallerInUbuntu) Uninstall() (err error) {
37 | err = d.Execer.RunCommand("apt-get", "remove", "-y",
38 | "conntrack")
39 | return
40 | }
41 |
42 | // WaitForStart waits for the service be started
43 | func (d *conntrackInstallerInUbuntu) WaitForStart() (ok bool, err error) {
44 | ok = true
45 | return
46 | }
47 |
48 | // Start starts the Conntrack service
49 | func (d *conntrackInstallerInUbuntu) Start() error {
50 | fmt.Println("not supported yet")
51 | return nil
52 | }
53 |
54 | // Stop stops the Conntrack service
55 | func (d *conntrackInstallerInUbuntu) Stop() error {
56 | fmt.Println("not supported yet")
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/os/apt/doc.go:
--------------------------------------------------------------------------------
1 | // Package apt provides the common functions to install a package via apt.
2 | // See also https://en.wikipedia.org/wiki/APT_(software).
3 | package apt
4 |
--------------------------------------------------------------------------------
/pkg/os/apt/docker.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 | "time"
8 |
9 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
10 | )
11 |
12 | // dockerInstallerInUbuntu is the installer of Docker in Ubuntu
13 | type dockerInstallerInUbuntu struct {
14 | Execer fakeruntime.Execer
15 | count int
16 | }
17 |
18 | // Available check if support current platform
19 | func (d *dockerInstallerInUbuntu) Available() (ok bool) {
20 | if d.Execer.OS() == "linux" {
21 | _, err := d.Execer.LookPath("apt-get")
22 | ok = err == nil
23 | }
24 | return
25 | }
26 |
27 | // Install installs the Docker
28 | func (d *dockerInstallerInUbuntu) Install() (err error) {
29 | if d.isDebian() {
30 | return d.installOnDebian()
31 | }
32 |
33 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err != nil {
34 | return
35 | }
36 | if err = d.Execer.RunCommand("apt-get", "install", "-y",
37 | "apt-transport-https",
38 | "ca-certificates",
39 | "curl",
40 | "gnupg",
41 | "lsb-release"); err != nil {
42 | return
43 | }
44 | if err = d.Execer.RunCommand("rm", "-rf", "/usr/share/keyrings/docker-archive-keyring.gpg"); err != nil {
45 | return
46 | }
47 | const dockerGPG = "docker.gpg"
48 | defer func() {
49 | _ = d.Execer.RunCommand("rm", "-rf", dockerGPG)
50 | }()
51 | if err = d.Execer.RunCommand("curl", "-fsSL",
52 | "https://download.docker.com/linux/ubuntu/gpg", "-o", dockerGPG); err == nil {
53 | if err = d.Execer.RunCommand("gpg",
54 | "--dearmor",
55 | "-o",
56 | "/usr/share/keyrings/docker-archive-keyring.gpg", dockerGPG); err != nil {
57 | err = fmt.Errorf("failed to install docker-archive-keyring.gpg, error: %v", err)
58 | return
59 | }
60 | } else {
61 | err = fmt.Errorf("failed to download docker gpg file, error: %v", err)
62 | return
63 | }
64 | var release string
65 | if release, err = d.Execer.RunCommandAndReturn("lsb_release", "", "-cs"); err == nil {
66 | item := fmt.Sprintf("deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu %s stable",
67 | strings.TrimSpace(release))
68 | if err = os.WriteFile("/etc/apt/sources.list.d/docker.list", []byte(item), 0622); err != nil {
69 | err = fmt.Errorf("failed to write docker.list, error: %v", err)
70 | return
71 | }
72 | } else {
73 | err = fmt.Errorf("failed to run command lsb_release -cs, error: %v", err)
74 | return
75 | }
76 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err != nil {
77 | return
78 | }
79 | if err = d.Execer.RunCommand("apt-get", "install", "-y",
80 | "docker-ce", "docker-ce-cli", "containerd.io"); err != nil {
81 | return
82 | }
83 | return
84 | }
85 |
86 | func (d *dockerInstallerInUbuntu) isDebian() bool {
87 | output, err := d.Execer.RunCommandAndReturn("lsb_release", "", "-d")
88 | if err == nil {
89 | return strings.Contains(output, "Debian")
90 | }
91 | return false
92 | }
93 |
94 | // see also https://docs.docker.com/engine/install/debian/
95 | func (d *dockerInstallerInUbuntu) installOnDebian() (err error) {
96 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err != nil {
97 | return
98 | }
99 |
100 | if err = d.Execer.RunCommand("apt-get", "install", "-y",
101 | "ca-certificates", "curl", "gnupg", "lsb-release"); err != nil {
102 | return
103 | }
104 |
105 | if err = os.MkdirAll("/etc/apt/keyrings", 0750); err != nil {
106 | return
107 | }
108 |
109 | const dockerGPG = "docker.gpg"
110 | defer func() {
111 | _ = d.Execer.RunCommand("rm", "-rf", dockerGPG)
112 | }()
113 | if err = d.Execer.RunCommand("curl", "-fsSL",
114 | "https://download.docker.com/linux/debian/gpg", "-o", dockerGPG); err == nil {
115 | if err = d.Execer.RunCommand("gpg",
116 | "--dearmor",
117 | "-o",
118 | "/etc/apt/keyrings/docker.gpg", dockerGPG); err != nil {
119 | err = fmt.Errorf("failed to install docker-archive-keyring.gpg, error: %v", err)
120 | return
121 | }
122 | } else {
123 | err = fmt.Errorf("failed to download docker gpg file, error: %v", err)
124 | return
125 | }
126 |
127 | var release string
128 | if release, err = d.Execer.RunCommandAndReturn("lsb_release", "", "-cs"); err == nil {
129 | item := fmt.Sprintf("deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian %s stable",
130 | strings.TrimSpace(release))
131 | if err = os.WriteFile("/etc/apt/sources.list.d/docker.list", []byte(item), 0622); err != nil {
132 | err = fmt.Errorf("failed to write docker.list, error: %v", err)
133 | return
134 | }
135 | } else {
136 | err = fmt.Errorf("failed to run command lsb_release -cs, error: %v", err)
137 | return
138 | }
139 |
140 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err != nil {
141 | return
142 | }
143 | err = d.Execer.RunCommand("apt-get", "install", "docker-ce", "docker-ce-cli", "containerd.io", "-y")
144 | return
145 | }
146 |
147 | // Uninstall uninstalls the Docker
148 | func (d *dockerInstallerInUbuntu) Uninstall() (err error) {
149 | if err = d.Execer.RunCommand("apt-get", "remove", "-y",
150 | "docker",
151 | "docker-engine",
152 | "docker.io",
153 | "containerd",
154 | "runc"); err == nil {
155 | err = d.Execer.RunCommand("apt-get", "purge", "-y",
156 | "docker-ce",
157 | "docker-ce-cli",
158 | "containerd.io")
159 | }
160 | return
161 | }
162 |
163 | // WaitForStart waits for the service be started
164 | func (d *dockerInstallerInUbuntu) WaitForStart() (ok bool, err error) {
165 | var result string
166 | if result, err = d.Execer.RunCommandAndReturn("systemctl", "", "status", "docker"); err != nil {
167 | return
168 | } else if strings.Contains(result, "Unit docker.service could not be found") {
169 | err = fmt.Errorf("unit docker.service could not be found")
170 | } else if strings.Contains(result, "Active: active") {
171 | ok = true
172 | } else {
173 | if d.count > 0 {
174 | fmt.Println("waiting for Docker service start")
175 | } else if d.count > 9 {
176 | return
177 | }
178 |
179 | d.count++
180 | time.Sleep(time.Second * 1)
181 | return d.WaitForStart()
182 | }
183 | return
184 | }
185 |
186 | // Start starts the Docker service
187 | func (d *dockerInstallerInUbuntu) Start() error {
188 | fmt.Println("not implemented yet")
189 | return nil
190 | }
191 |
192 | // Stop stops the Docker service
193 | func (d *dockerInstallerInUbuntu) Stop() error {
194 | return d.Execer.RunCommand("systemctl", "start", "docker")
195 | }
196 |
--------------------------------------------------------------------------------
/pkg/os/apt/ffmpeg.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // ffmpegInstallerInUbuntu is the installer of ffmpeg in CentOS
10 | type ffmpegInstallerInUbuntu struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *ffmpegInstallerInUbuntu) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("apt-get")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the ffmpeg
24 | func (d *ffmpegInstallerInUbuntu) Install() (err error) {
25 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err != nil {
26 | return
27 | }
28 | if err = d.Execer.RunCommand("apt-get", "install", "-y",
29 | "ffmpeg"); err != nil {
30 | return
31 | }
32 | return
33 | }
34 |
35 | // Uninstall uninstalls the ffmpeg
36 | func (d *ffmpegInstallerInUbuntu) Uninstall() (err error) {
37 | err = d.Execer.RunCommand("apt-get", "remove", "-y",
38 | "ffmpeg")
39 | return
40 | }
41 |
42 | // WaitForStart waits for the service be started
43 | func (d *ffmpegInstallerInUbuntu) WaitForStart() (ok bool, err error) {
44 | ok = true
45 | return
46 | }
47 |
48 | // Start starts the ffmpeg service
49 | func (d *ffmpegInstallerInUbuntu) Start() error {
50 | fmt.Println("not supported yet")
51 | return nil
52 | }
53 |
54 | // Stop stops the ffmpeg service
55 | func (d *ffmpegInstallerInUbuntu) Stop() error {
56 | fmt.Println("not supported yet")
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/os/apt/git.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // gitInstallerInUbuntu is the installer of git in CentOS
10 | type gitInstallerInUbuntu struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *gitInstallerInUbuntu) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("apt-get")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the git
24 | func (d *gitInstallerInUbuntu) Install() (err error) {
25 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err == nil {
26 | err = d.Execer.RunCommand("apt-get", "install", "-y",
27 | "git")
28 | }
29 | return
30 | }
31 |
32 | // Uninstall uninstalls the git
33 | func (d *gitInstallerInUbuntu) Uninstall() (err error) {
34 | err = d.Execer.RunCommand("apt-get", "remove", "-y", "git")
35 | return
36 | }
37 |
38 | // WaitForStart waits for the service be started
39 | func (d *gitInstallerInUbuntu) WaitForStart() (ok bool, err error) {
40 | ok = true
41 | return
42 | }
43 |
44 | // Start starts the git service
45 | func (d *gitInstallerInUbuntu) Start() error {
46 | fmt.Println("not supported yet")
47 | return nil
48 | }
49 |
50 | // Stop stops the git service
51 | func (d *gitInstallerInUbuntu) Stop() error {
52 | fmt.Println("not supported yet")
53 | return nil
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/os/apt/golang.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // golangInstallerInUbuntu is the installer of golang in CentOS
10 | type golangInstallerInUbuntu struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *golangInstallerInUbuntu) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("apt-get")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the golang
24 | func (d *golangInstallerInUbuntu) Install() (err error) {
25 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err != nil {
26 | return
27 | }
28 | if err = d.Execer.RunCommand("apt-get", "install", "-y", "golang-go"); err != nil {
29 | return
30 | }
31 | return
32 | }
33 |
34 | // Uninstall uninstalls the golang
35 | func (d *golangInstallerInUbuntu) Uninstall() (err error) {
36 | err = d.Execer.RunCommand("apt-get", "remove", "-y", "golang-go")
37 | return
38 | }
39 |
40 | // WaitForStart waits for the service be started
41 | func (d *golangInstallerInUbuntu) WaitForStart() (ok bool, err error) {
42 | ok = true
43 | return
44 | }
45 |
46 | // Start starts the golang service
47 | func (d *golangInstallerInUbuntu) Start() error {
48 | fmt.Println("not supported yet")
49 | return nil
50 | }
51 |
52 | // Stop stops the golang service
53 | func (d *golangInstallerInUbuntu) Stop() error {
54 | fmt.Println("not supported yet")
55 | return nil
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/os/apt/init.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
5 | "github.com/linuxsuren/http-downloader/pkg/os/core"
6 | )
7 |
8 | // SetInstallerRegistry sets the installer of registry
9 | func SetInstallerRegistry(registry core.InstallerRegistry, defaultExecer fakeruntime.Execer) {
10 | registry.Registry("docker", &dockerInstallerInUbuntu{Execer: defaultExecer})
11 | registry.Registry("conntrack", &conntrackInstallerInUbuntu{Execer: defaultExecer})
12 | registry.Registry("socat", &socatInstallerInUbuntu{Execer: defaultExecer})
13 | registry.Registry("vim", &vimInstallerInUbuntu{Execer: defaultExecer})
14 | registry.Registry("golang", &golangInstallerInUbuntu{Execer: defaultExecer})
15 | registry.Registry("git", &gitInstallerInUbuntu{Execer: defaultExecer})
16 | registry.Registry("bash-completion", &bashCompletionInstallerInUbuntu{Execer: defaultExecer})
17 | registry.Registry("asciinema", &asciinemaInstallerInUbuntu{Execer: defaultExecer})
18 | registry.Registry("ffmpge", &ffmpegInstallerInUbuntu{Execer: defaultExecer})
19 | }
20 |
--------------------------------------------------------------------------------
/pkg/os/apt/init_test.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
8 | "github.com/linuxsuren/http-downloader/pkg/os/core"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestCommonCase(t *testing.T) {
13 | registry := &core.FakeRegistry{}
14 | SetInstallerRegistry(registry, fakeruntime.FakeExecer{
15 | ExpectOS: "linux",
16 | })
17 |
18 | registry.Walk(func(s string, i core.Installer) {
19 | t.Run(s, func(t *testing.T) {
20 | assert.True(t, i.Available())
21 | assert.Nil(t, i.Uninstall())
22 | if s != "docker" {
23 | assert.Nil(t, i.Install())
24 | assert.Nil(t, i.Start())
25 | assert.Nil(t, i.Stop())
26 | ok, err := i.WaitForStart()
27 | assert.True(t, ok)
28 | assert.Nil(t, err)
29 | }
30 | })
31 | })
32 |
33 | errRegistry := &core.FakeRegistry{}
34 | SetInstallerRegistry(errRegistry, fakeruntime.FakeExecer{
35 | ExpectLookPathError: errors.New("error"),
36 | ExpectError: errors.New("error"),
37 | ExpectOS: "linux",
38 | })
39 | errRegistry.Walk(func(s string, i core.Installer) {
40 | t.Run(s, func(t *testing.T) {
41 | assert.False(t, i.Available())
42 | assert.NotNil(t, i.Uninstall())
43 | if s != "docker" {
44 | assert.NotNil(t, i.Install())
45 | assert.Nil(t, i.Start())
46 | assert.Nil(t, i.Stop())
47 | ok, err := i.WaitForStart()
48 | assert.True(t, ok)
49 | assert.Nil(t, err)
50 | }
51 | })
52 | })
53 | }
54 |
--------------------------------------------------------------------------------
/pkg/os/apt/resource/kubernetes.list:
--------------------------------------------------------------------------------
1 | deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main
2 |
--------------------------------------------------------------------------------
/pkg/os/apt/socat.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // socatInstallerInUbuntu is the installer of socat in CentOS
10 | type socatInstallerInUbuntu struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *socatInstallerInUbuntu) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("apt-get")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the socat
24 | func (d *socatInstallerInUbuntu) Install() (err error) {
25 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err != nil {
26 | return
27 | }
28 | if err = d.Execer.RunCommand("apt-get", "install", "-y",
29 | "socat"); err != nil {
30 | return
31 | }
32 | return
33 | }
34 |
35 | // Uninstall uninstalls the socat
36 | func (d *socatInstallerInUbuntu) Uninstall() (err error) {
37 | err = d.Execer.RunCommand("apt-get", "remove", "-y",
38 | "socat")
39 | return
40 | }
41 |
42 | // WaitForStart waits for the service be started
43 | func (d *socatInstallerInUbuntu) WaitForStart() (ok bool, err error) {
44 | ok = true
45 | return
46 | }
47 |
48 | // Start starts the socat service
49 | func (d *socatInstallerInUbuntu) Start() error {
50 | fmt.Println("not supported yet")
51 | return nil
52 | }
53 |
54 | // Stop stops the socat service
55 | func (d *socatInstallerInUbuntu) Stop() error {
56 | fmt.Println("not supported yet")
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/os/apt/vim.go:
--------------------------------------------------------------------------------
1 | package apt
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // vimInstallerInUbuntu is the installer of vim in CentOS
10 | type vimInstallerInUbuntu struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *vimInstallerInUbuntu) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("apt-get")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the vim
24 | func (d *vimInstallerInUbuntu) Install() (err error) {
25 | if err = d.Execer.RunCommand("apt-get", "update", "-y"); err != nil {
26 | return
27 | }
28 | if err = d.Execer.RunCommand("apt-get", "install", "-y",
29 | "vim"); err != nil {
30 | return
31 | }
32 | return
33 | }
34 |
35 | // Uninstall uninstalls the vim
36 | func (d *vimInstallerInUbuntu) Uninstall() (err error) {
37 | err = d.Execer.RunCommand("apt-get", "remove", "-y",
38 | "vim")
39 | return
40 | }
41 |
42 | // WaitForStart waits for the service be started
43 | func (d *vimInstallerInUbuntu) WaitForStart() (ok bool, err error) {
44 | ok = true
45 | return
46 | }
47 |
48 | // Start starts the vim service
49 | func (d *vimInstallerInUbuntu) Start() error {
50 | fmt.Println("not supported yet")
51 | return nil
52 | }
53 |
54 | // Stop stops the vim service
55 | func (d *vimInstallerInUbuntu) Stop() error {
56 | fmt.Println("not supported yet")
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/os/brew/common.go:
--------------------------------------------------------------------------------
1 | package brew
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | const (
10 | // Tool is the tool name of brew
11 | Tool = "brew"
12 | )
13 |
14 | // CommonInstaller is the installer of a common brew
15 | type CommonInstaller struct {
16 | Name string
17 | Execer fakeruntime.Execer
18 | }
19 |
20 | // Available check if support current platform
21 | func (d *CommonInstaller) Available() (ok bool) {
22 | if d.Execer.OS() == fakeruntime.OSDarwin || d.Execer.OS() == fakeruntime.OSLinux {
23 | _, err := d.Execer.LookPath(Tool)
24 | ok = err == nil
25 | }
26 | return
27 | }
28 |
29 | // Install installs the target package
30 | func (d *CommonInstaller) Install() (err error) {
31 | if err = d.Execer.RunCommand(Tool, "install", d.Name); err != nil {
32 | return
33 | }
34 | return
35 | }
36 |
37 | // Uninstall uninstalls the Conntrack
38 | func (d *CommonInstaller) Uninstall() (err error) {
39 | err = d.Execer.RunCommand(Tool, "remove", d.Name)
40 | return
41 | }
42 |
43 | // WaitForStart waits for the service be started
44 | func (d *CommonInstaller) WaitForStart() (ok bool, err error) {
45 | ok = true
46 | return
47 | }
48 |
49 | // Start starts the Conntrack service
50 | func (d *CommonInstaller) Start() error {
51 | fmt.Println("not supported yet")
52 | return nil
53 | }
54 |
55 | // Stop stops the Conntrack service
56 | func (d *CommonInstaller) Stop() error {
57 | fmt.Println("not supported yet")
58 | return nil
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/os/brew/common_test.go:
--------------------------------------------------------------------------------
1 | package brew
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestCommon(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | installer CommonInstaller
15 | expectAvailable bool
16 | hasErr bool
17 | }{{
18 | name: "normal",
19 | installer: CommonInstaller{
20 | Execer: fakeruntime.FakeExecer{
21 | ExpectError: nil,
22 | ExpectOutput: "",
23 | ExpectOS: "darwin",
24 | ExpectArch: "amd64",
25 | },
26 | },
27 | expectAvailable: true,
28 | hasErr: false,
29 | }, {
30 | name: "not is darwin",
31 | installer: CommonInstaller{
32 | Execer: fakeruntime.FakeExecer{ExpectOS: fakeruntime.OSWindows},
33 | },
34 | expectAvailable: false,
35 | hasErr: false,
36 | }, {
37 | name: "command not found",
38 | installer: CommonInstaller{
39 | Execer: fakeruntime.FakeExecer{ExpectError: errors.New("error")},
40 | },
41 | expectAvailable: false,
42 | hasErr: true,
43 | }}
44 | for _, tt := range tests {
45 | t.Run(tt.name, func(t *testing.T) {
46 | assert.Equal(t, tt.expectAvailable, tt.installer.Available())
47 | assert.Nil(t, tt.installer.Start())
48 | assert.Nil(t, tt.installer.Stop())
49 |
50 | ok, err := tt.installer.WaitForStart()
51 | assert.True(t, ok)
52 | assert.Nil(t, err)
53 | assert.Equal(t, tt.hasErr, tt.installer.Install() != nil)
54 | assert.Equal(t, tt.hasErr, tt.installer.Uninstall() != nil)
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/os/brew/doc.go:
--------------------------------------------------------------------------------
1 | // Package brew provides the common functions to install a package via brew.
2 | // See also https://brew.sh/.
3 | package brew
4 |
--------------------------------------------------------------------------------
/pkg/os/core/doc.go:
--------------------------------------------------------------------------------
1 | // Package core provides the core structs about the os-related package manager.
2 | package core
3 |
--------------------------------------------------------------------------------
/pkg/os/core/fake.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | // FakeRegistry is a fake registry which only for the test purpose
4 | type FakeRegistry struct {
5 | data map[string]Installer
6 | }
7 |
8 | // Registry puts the registry to memory
9 | func (r *FakeRegistry) Registry(id string, installer Installer) {
10 | if r.data == nil {
11 | r.data = map[string]Installer{}
12 | }
13 | r.data[id] = installer
14 | }
15 |
16 | // Walk allows to iterate all the installers
17 | func (r *FakeRegistry) Walk(walkFunc func(string, Installer)) {
18 | if r.data != nil {
19 | for id, installer := range r.data {
20 | walkFunc(id, installer)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/os/core/fake_test.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | )
7 |
8 | func TestRegistry(t *testing.T) {
9 | reg := &FakeRegistry{}
10 | reg.Registry("id", nil)
11 |
12 | reg.Walk(func(s string, installer Installer) {
13 | assert.Equal(t, "id", s)
14 | assert.Nil(t, installer)
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/os/core/types.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | // Installer is the interface of a installer
4 | // Deprecated use AdvanceInstaller instead
5 | type Installer interface {
6 | Available() bool
7 | Install() error
8 | Uninstall() error
9 |
10 | WaitForStart() (bool, error)
11 | Start() error
12 | Stop() error
13 | }
14 |
15 | // AdvanceInstaller is a generic installer
16 | type AdvanceInstaller interface {
17 | Installer
18 |
19 | IsService() bool
20 | }
21 |
22 | // InstallerRegistry is the interface of install registry
23 | type InstallerRegistry interface {
24 | Registry(string, Installer)
25 | }
26 |
27 | // ProxyAble define the proxy support feature
28 | type ProxyAble interface {
29 | SetURLReplace(map[string]string)
30 | }
31 |
--------------------------------------------------------------------------------
/pkg/os/dnf/common.go:
--------------------------------------------------------------------------------
1 | package dnf
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // DNFName is the tool name
10 | const DNFName = "dnf"
11 |
12 | // CommonInstaller is the installer of a common dnf
13 | type CommonInstaller struct {
14 | Name string
15 | Execer fakeruntime.Execer
16 | }
17 |
18 | // Available check if support current platform
19 | func (d *CommonInstaller) Available() (ok bool) {
20 | if d.Execer.OS() == fakeruntime.OSLinux {
21 | _, err := d.Execer.LookPath(DNFName)
22 | ok = err == nil
23 | }
24 | return
25 | }
26 |
27 | // Install installs the target package
28 | func (d *CommonInstaller) Install() (err error) {
29 | if err = d.Execer.RunCommand(DNFName, "install", d.Name, "-y"); err != nil {
30 | return
31 | }
32 | return
33 | }
34 |
35 | // Uninstall uninstalls the target
36 | func (d *CommonInstaller) Uninstall() (err error) {
37 | err = d.Execer.RunCommand(DNFName, "remove", d.Name, "-y")
38 | return
39 | }
40 |
41 | // WaitForStart waits for the service be started
42 | func (d *CommonInstaller) WaitForStart() (ok bool, err error) {
43 | ok = true
44 | return
45 | }
46 |
47 | // Start starts the target service
48 | func (d *CommonInstaller) Start() error {
49 | fmt.Println("not supported yet")
50 | return nil
51 | }
52 |
53 | // Stop stops the target service
54 | func (d *CommonInstaller) Stop() error {
55 | fmt.Println("not supported yet")
56 | return nil
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/os/dnf/common_test.go:
--------------------------------------------------------------------------------
1 | package dnf
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestCommon(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | installer CommonInstaller
15 | expectAvailable bool
16 | hasErr bool
17 | }{{
18 | name: "normal",
19 | installer: CommonInstaller{
20 | Execer: fakeruntime.FakeExecer{
21 | ExpectError: nil,
22 | ExpectOutput: "",
23 | ExpectOS: "linux",
24 | ExpectArch: "amd64",
25 | },
26 | },
27 | expectAvailable: true,
28 | hasErr: false,
29 | }, {
30 | name: "not is linux",
31 | installer: CommonInstaller{
32 | Execer: fakeruntime.FakeExecer{ExpectOS: "darwin"},
33 | },
34 | expectAvailable: false,
35 | hasErr: false,
36 | }, {
37 | name: "command not found",
38 | installer: CommonInstaller{
39 | Execer: fakeruntime.FakeExecer{ExpectError: errors.New("error")},
40 | },
41 | expectAvailable: false,
42 | hasErr: true,
43 | }}
44 | for _, tt := range tests {
45 | t.Run(tt.name, func(t *testing.T) {
46 | assert.Equal(t, tt.expectAvailable, tt.installer.Available())
47 | assert.Nil(t, tt.installer.Start())
48 | assert.Nil(t, tt.installer.Stop())
49 |
50 | ok, err := tt.installer.WaitForStart()
51 | assert.True(t, ok)
52 | assert.Nil(t, err)
53 | assert.Equal(t, tt.hasErr, tt.installer.Install() != nil)
54 | assert.Equal(t, tt.hasErr, tt.installer.Uninstall() != nil)
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/os/dnf/doc.go:
--------------------------------------------------------------------------------
1 | // Package dnf provides the common function of dnf package manager.
2 | // See also https://en.wikipedia.org/wiki/DNF_(software).
3 | package dnf
4 |
--------------------------------------------------------------------------------
/pkg/os/dnf/docker.go:
--------------------------------------------------------------------------------
1 | package dnf
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "time"
7 |
8 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
9 | )
10 |
11 | // dockerInstallerInFedora is the installer of Docker in Fedora
12 | type dockerInstallerInFedora struct {
13 | Execer fakeruntime.Execer
14 | count int
15 | }
16 |
17 | // Available check if support current platform
18 | func (d *dockerInstallerInFedora) Available() (ok bool) {
19 | if d.Execer.OS() == "linux" {
20 | _, err := d.Execer.LookPath("dnf")
21 | ok = err == nil
22 | }
23 | return
24 | }
25 |
26 | // Install installs the Docker
27 | func (d *dockerInstallerInFedora) Install() (err error) {
28 | if err = d.Execer.RunCommand("dnf", "-y", "install", "dnf-plugins-core"); err != nil {
29 | return
30 | }
31 |
32 | if err = d.Execer.RunCommand("dnf", "config-manager",
33 | "--add-repo", "https://download.docker.com/linux/fedora/docker-ce.repo"); err != nil {
34 | return
35 | }
36 |
37 | err = d.Execer.RunCommand("dnf", "install", "docker-ce", "docker-ce-cli", "containerd.io", "docker-compose-plugin")
38 | return
39 | }
40 |
41 | // Uninstall uninstalls the Docker
42 | func (d *dockerInstallerInFedora) Uninstall() (err error) {
43 | err = d.Execer.RunCommand("dnf", "remove", "docker",
44 | "docker-client", "docker-client-latest", "docker-common", "docker-latest",
45 | "docker-latest-logrotate",
46 | "docker-logrotate",
47 | "docker-selinux",
48 | "docker-engine-selinux",
49 | "docker-engine")
50 | return
51 | }
52 |
53 | // WaitForStart waits for the service be started
54 | func (d *dockerInstallerInFedora) WaitForStart() (ok bool, err error) {
55 | var result string
56 | if result, err = d.Execer.RunCommandAndReturn("systemctl", "", "status", "docker"); err != nil {
57 | return
58 | } else if strings.Contains(result, "Unit docker.service could not be found") {
59 | err = fmt.Errorf("unit docker.service could not be found")
60 | } else if strings.Contains(result, "Active: active") {
61 | ok = true
62 | } else {
63 | if d.count > 0 {
64 | fmt.Println("waiting for Docker service start")
65 | } else if d.count > 9 {
66 | return
67 | }
68 |
69 | d.count++
70 | time.Sleep(time.Second * 1)
71 | return d.WaitForStart()
72 | }
73 | return
74 | }
75 |
76 | // Start starts the Docker service
77 | func (d *dockerInstallerInFedora) Start() (err error) {
78 | err = d.Execer.RunCommand("systemctl", "start", "docker")
79 | return
80 | }
81 |
82 | // Stop stops the Docker service
83 | func (d *dockerInstallerInFedora) Stop() (err error) {
84 | err = d.Execer.RunCommand("systemctl", "stop", "docker")
85 | return
86 | }
87 |
--------------------------------------------------------------------------------
/pkg/os/dnf/init.go:
--------------------------------------------------------------------------------
1 | package dnf
2 |
3 | import (
4 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
5 | "github.com/linuxsuren/http-downloader/pkg/os/core"
6 | )
7 |
8 | // SetInstallerRegistry sets the installer of registry
9 | func SetInstallerRegistry(registry core.InstallerRegistry, defaultExecer fakeruntime.Execer) {
10 | registry.Registry("docker", &dockerInstallerInFedora{Execer: defaultExecer})
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/os/dnf/init_test.go:
--------------------------------------------------------------------------------
1 | package dnf
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
8 | "github.com/linuxsuren/http-downloader/pkg/os/core"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestCommonCase(t *testing.T) {
13 | registry := &core.FakeRegistry{}
14 | SetInstallerRegistry(registry, fakeruntime.FakeExecer{
15 | ExpectOS: "linux",
16 | })
17 |
18 | registry.Walk(func(s string, i core.Installer) {
19 | t.Run(s, func(t *testing.T) {
20 | assert.True(t, i.Available())
21 | assert.Nil(t, i.Uninstall())
22 | assert.Nil(t, i.Install())
23 | assert.Nil(t, i.Start())
24 | assert.Nil(t, i.Stop())
25 | })
26 | })
27 |
28 | errRegistry := &core.FakeRegistry{}
29 | SetInstallerRegistry(errRegistry, fakeruntime.FakeExecer{
30 | ExpectLookPathError: errors.New("error"),
31 | ExpectError: errors.New("error"),
32 | ExpectOS: "linux",
33 | })
34 | errRegistry.Walk(func(s string, i core.Installer) {
35 | t.Run(s, func(t *testing.T) {
36 | assert.False(t, i.Available())
37 | assert.NotNil(t, i.Uninstall())
38 | if s != "docker" {
39 | assert.NotNil(t, i.Install())
40 | assert.Nil(t, i.Start())
41 | assert.Nil(t, i.Stop())
42 | ok, err := i.WaitForStart()
43 | assert.True(t, ok)
44 | assert.Nil(t, err)
45 | }
46 | })
47 | })
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/os/doc.go:
--------------------------------------------------------------------------------
1 | // Package os provides a generic installer for all platforms.
2 | // See the different native packages in the sub-packages, such as: apt, apk, brew etc.
3 | package os
4 |
--------------------------------------------------------------------------------
/pkg/os/docker/bitbucket.go:
--------------------------------------------------------------------------------
1 | package docker
2 |
3 | import fakeruntime "github.com/linuxsuren/go-fake-runtime"
4 |
5 | // bitbucket is the installer of bitbucket in CentOS
6 | type bitbucket struct {
7 | Execer fakeruntime.Execer
8 | }
9 |
10 | // Available check if support current platform
11 | func (d *bitbucket) Available() (ok bool) {
12 | _, err := d.Execer.LookPath("docker")
13 | ok = err == nil
14 | return
15 | }
16 |
17 | // Install installs the bitbucket
18 | func (d *bitbucket) Install() (err error) {
19 | err = d.Execer.RunCommand("docker", "run", `--name=bitbucket`, "-d",
20 | "-p", "7990:7990", "-p", "7999:7999", "atlassian/bitbucket")
21 | return
22 | }
23 |
24 | // Uninstall uninstalls the bitbucket
25 | func (d *bitbucket) Uninstall() (err error) {
26 | if err = d.Stop(); err == nil {
27 | err = d.Execer.RunCommand("docker", "rm", "bitbucket")
28 | }
29 | return
30 | }
31 |
32 | // WaitForStart waits for the service be started
33 | func (d *bitbucket) WaitForStart() (ok bool, err error) {
34 | ok = true
35 | return
36 | }
37 |
38 | // Start starts the bitbucket service
39 | func (d *bitbucket) Start() (err error) {
40 | err = d.Execer.RunCommand("docker", "start", "bitbucket")
41 | return
42 | }
43 |
44 | // Stop stops the bitbucket service
45 | func (d *bitbucket) Stop() (err error) {
46 | err = d.Execer.RunCommand("docker", "stop", "bitbucket")
47 | return
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/os/docker/doc.go:
--------------------------------------------------------------------------------
1 | // Package docker provides the way to install a package via docker CLI.
2 | package docker
3 |
--------------------------------------------------------------------------------
/pkg/os/docker/init.go:
--------------------------------------------------------------------------------
1 | package docker
2 |
3 | import (
4 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
5 | "github.com/linuxsuren/http-downloader/pkg/os/core"
6 | )
7 |
8 | // SetInstallerRegistry sets the installer of registry
9 | func SetInstallerRegistry(registry core.InstallerRegistry, execer fakeruntime.Execer) {
10 | registry.Registry("bitbucket", &bitbucket{Execer: execer})
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/os/docker/init_test.go:
--------------------------------------------------------------------------------
1 | package docker
2 |
3 | import (
4 | "testing"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | "github.com/linuxsuren/http-downloader/pkg/os/core"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestCommonCase(t *testing.T) {
12 | registry := &core.FakeRegistry{}
13 | SetInstallerRegistry(registry, fakeruntime.FakeExecer{
14 | ExpectOS: "linux",
15 | })
16 |
17 | registry.Walk(func(s string, i core.Installer) {
18 | t.Run(s, func(t *testing.T) {
19 | assert.True(t, i.Available())
20 | assert.Nil(t, i.Start())
21 | assert.Nil(t, i.Stop())
22 | assert.Nil(t, i.Install())
23 | assert.Nil(t, i.Uninstall())
24 | ok, err := i.WaitForStart()
25 | assert.Nil(t, err)
26 | assert.True(t, ok)
27 | })
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/os/fake/doc.go:
--------------------------------------------------------------------------------
1 | // Package fake provides functions for unit test.
2 | package fake
3 |
--------------------------------------------------------------------------------
/pkg/os/fake/fake.go:
--------------------------------------------------------------------------------
1 | package fake
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/linuxsuren/http-downloader/pkg/os/core"
7 | )
8 |
9 | // Installer only for test purpose
10 | type Installer struct {
11 | hasError bool
12 | support bool
13 | data map[string]string
14 | }
15 |
16 | // NewFakeInstaller returns a Installer
17 | func NewFakeInstaller(support bool, hasError bool) core.Installer {
18 | return &Installer{
19 | hasError: hasError,
20 | support: support,
21 | }
22 | }
23 |
24 | // Available check if support current platform
25 | func (d *Installer) Available() bool {
26 | return true
27 | }
28 |
29 | // Install installs the vim
30 | func (d *Installer) Install() (err error) {
31 | if d.hasError {
32 | err = fmt.Errorf("fake error")
33 | }
34 | return
35 | }
36 |
37 | // Uninstall uninstalls the vim
38 | func (d *Installer) Uninstall() (err error) {
39 | if d.hasError {
40 | err = fmt.Errorf("fake error")
41 | }
42 | return
43 | }
44 |
45 | // WaitForStart waits for the service be started
46 | func (d *Installer) WaitForStart() (ok bool, err error) {
47 | if d.hasError {
48 | err = fmt.Errorf("fake error")
49 | }
50 | ok = true
51 | return
52 | }
53 |
54 | // Start starts the vim service
55 | func (d *Installer) Start() (err error) {
56 | if d.hasError {
57 | err = fmt.Errorf("fake error")
58 | }
59 | return
60 | }
61 |
62 | // Stop stops the vim service
63 | func (d *Installer) Stop() (err error) {
64 | if d.hasError {
65 | err = fmt.Errorf("fake error")
66 | }
67 | return
68 | }
69 |
70 | // SetURLReplace is a fake method
71 | func (d *Installer) SetURLReplace(data map[string]string) {
72 | d.data = data
73 | }
74 |
--------------------------------------------------------------------------------
/pkg/os/fake/fake_test.go:
--------------------------------------------------------------------------------
1 | package fake
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/linuxsuren/http-downloader/pkg/os/core"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestNewFakeInstaller(t *testing.T) {
11 | var installer core.Installer = NewFakeInstaller(true, true)
12 |
13 | assert.True(t, installer.Available())
14 | assert.NotNil(t, installer.Install())
15 | assert.NotNil(t, installer.Uninstall())
16 | assert.NotNil(t, installer.Start())
17 | assert.NotNil(t, installer.Stop())
18 |
19 | ok, err := installer.WaitForStart()
20 | assert.True(t, ok)
21 | assert.NotNil(t, err)
22 |
23 | proxyAbleInstaller := &Installer{}
24 | proxyAbleInstaller.SetURLReplace(map[string]string{
25 | "key": "value",
26 | })
27 | assert.Equal(t, map[string]string{
28 | "key": "value",
29 | }, proxyAbleInstaller.data)
30 |
31 | var _ core.ProxyAble = &Installer{}
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/os/generic/common.go:
--------------------------------------------------------------------------------
1 | package generic
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 |
8 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
9 | )
10 |
11 | // CommonInstaller is the installer of a common bash
12 | type CommonInstaller struct {
13 | Name string
14 | OS string
15 | InstallCmd CmdWithArgs
16 | UninstallCmd CmdWithArgs
17 | Execer fakeruntime.Execer
18 |
19 | // inner fields
20 | proxyMap map[string]string
21 | }
22 |
23 | // CmdWithArgs is a command and with args
24 | type CmdWithArgs struct {
25 | Cmd string `yaml:"cmd"`
26 | Args []string `yaml:"args"`
27 | SystemCall bool `yaml:"systemCall"`
28 | Execer fakeruntime.Execer
29 | }
30 |
31 | // Run runs the current command
32 | func (c CmdWithArgs) Run() (err error) {
33 | if c.SystemCall {
34 | var targetBinary string
35 | if targetBinary, err = c.Execer.LookPath(c.Cmd); err != nil {
36 | err = fmt.Errorf("cannot find %s", c.Cmd)
37 | } else {
38 | sysCallArgs := []string{c.Cmd}
39 | sysCallArgs = append(sysCallArgs, c.Args...)
40 | fmt.Println(c.Cmd, strings.Join(sysCallArgs, " "))
41 | err = c.Execer.SystemCall(targetBinary, sysCallArgs, os.Environ())
42 | }
43 | } else {
44 | fmt.Println(c.Cmd, strings.Join(c.Args, " "))
45 | err = c.Execer.RunCommand(c.Cmd, c.Args...)
46 | }
47 | return
48 | }
49 |
50 | // SetURLReplace set the URL replace map
51 | func (d *CommonInstaller) SetURLReplace(data map[string]string) {
52 | d.proxyMap = data
53 | }
54 |
55 | func (d *CommonInstaller) sliceReplace(args []string) []string {
56 | for i, arg := range args {
57 | if result := d.urlReplace(arg); result != arg {
58 | args[i] = result
59 | }
60 | }
61 | return args
62 | }
63 |
64 | func (d *CommonInstaller) urlReplace(old string) string {
65 | if d.proxyMap == nil {
66 | return old
67 | }
68 |
69 | for k, v := range d.proxyMap {
70 | if !strings.Contains(old, k) {
71 | continue
72 | }
73 | old = strings.ReplaceAll(old, k, v)
74 | }
75 | return old
76 | }
77 |
78 | // Available check if support current platform
79 | func (d *CommonInstaller) Available() (ok bool) {
80 | ok = d.OS == "" || d.Execer.OS() == d.OS
81 | return
82 | }
83 |
84 | // Install installs the target package
85 | func (d *CommonInstaller) Install() (err error) {
86 | d.InstallCmd.Args = d.sliceReplace(d.InstallCmd.Args)
87 | d.InstallCmd.Execer = d.Execer
88 | err = d.InstallCmd.Run()
89 | return
90 | }
91 |
92 | // Uninstall uninstalls the target package
93 | func (d *CommonInstaller) Uninstall() (err error) {
94 | d.UninstallCmd.Args = d.sliceReplace(d.UninstallCmd.Args)
95 | d.UninstallCmd.Execer = d.Execer
96 | err = d.UninstallCmd.Run()
97 | return
98 | }
99 |
100 | // WaitForStart waits for the service be started
101 | func (d *CommonInstaller) WaitForStart() (ok bool, err error) {
102 | ok = true
103 | return
104 | }
105 |
106 | // Start starts the desired service
107 | func (d *CommonInstaller) Start() error {
108 | fmt.Println("not supported yet")
109 | return nil
110 | }
111 |
112 | // Stop stops the desired service
113 | func (d *CommonInstaller) Stop() error {
114 | fmt.Println("not supported yet")
115 | return nil
116 | }
117 |
--------------------------------------------------------------------------------
/pkg/os/generic/common_test.go:
--------------------------------------------------------------------------------
1 | package generic
2 |
3 | import (
4 | "testing"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestSliceReplace(t *testing.T) {
11 | installer := &CommonInstaller{}
12 | installer.SetURLReplace(map[string]string{
13 | "https://raw.githubusercontent.com": "https://gh-proxy.com/https://raw.githubusercontent.com",
14 | })
15 |
16 | // a normal case
17 | result := installer.sliceReplace([]string{
18 | "abc",
19 | "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh",
20 | })
21 | assert.Equal(t, []string{"abc",
22 | "https://gh-proxy.com/https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"}, result)
23 |
24 | // an empty slice
25 | noProxyInstaller := &CommonInstaller{}
26 | assert.Equal(t, []string{"abc"}, noProxyInstaller.sliceReplace([]string{"abc"}))
27 | }
28 |
29 | func TestCommonInstaller(t *testing.T) {
30 | installer := &CommonInstaller{
31 | Execer: fakeruntime.FakeExecer{},
32 | }
33 | assert.Nil(t, installer.Install())
34 | assert.Nil(t, installer.Uninstall())
35 | assert.True(t, installer.Available())
36 | assert.Nil(t, installer.Stop())
37 | assert.Nil(t, installer.Start())
38 |
39 | ok, err := installer.WaitForStart()
40 | assert.True(t, ok)
41 | assert.Nil(t, err)
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/os/generic/doc.go:
--------------------------------------------------------------------------------
1 | // Package generic provides the generic packge installation.
2 | // It does not care about the real package mananger. Because
3 | // It will accept arbitrary command line.
4 | package generic
5 |
--------------------------------------------------------------------------------
/pkg/os/generic_installer_test.go:
--------------------------------------------------------------------------------
1 | package os
2 |
3 | import (
4 | "errors"
5 | "io"
6 | "os"
7 | "testing"
8 |
9 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
10 | "github.com/linuxsuren/http-downloader/pkg/os/fake"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func TestURLReplace(t *testing.T) {
15 | genericPkg := &genericPackage{
16 | env: map[string]string{
17 | "key": "value",
18 | },
19 | execer: fakeruntime.FakeExecer{ExpectOS: fakeruntime.OSLinux},
20 | }
21 | genericPkg.SetURLReplace(map[string]string{
22 | "github": "ghproxy",
23 | })
24 | genericPkg.loadEnv()
25 | assert.Equal(t, "ghproxy-value", genericPkg.urlReplace("github-{{.key}}"))
26 | assert.Equal(t, "value", genericPkg.urlReplace("{{.key}}"))
27 | assert.Equal(t, []string{"value"}, genericPkg.sliceReplace([]string{"{{.key}}"}))
28 |
29 | emptyGenericPkg := &genericPackage{
30 | execer: fakeruntime.FakeExecer{ExpectOS: fakeruntime.OSLinux},
31 | }
32 | emptyGenericPkg.loadEnv()
33 | assert.NotNil(t, emptyGenericPkg.env)
34 | assert.Nil(t, emptyGenericPkg.Start())
35 | assert.Nil(t, emptyGenericPkg.Stop())
36 | assert.False(t, emptyGenericPkg.IsService())
37 | assert.False(t, emptyGenericPkg.Available())
38 | assert.NotNil(t, emptyGenericPkg.Install())
39 | assert.NotNil(t, emptyGenericPkg.Uninstall())
40 | ok, err := emptyGenericPkg.WaitForStart()
41 | assert.True(t, ok)
42 | assert.Nil(t, err)
43 |
44 | withPreInstall := &genericPackage{
45 | execer: fakeruntime.FakeExecer{
46 | ExpectOS: fakeruntime.OSLinux,
47 | },
48 | PreInstall: []preInstall{{
49 | Cmd: CmdWithArgs{
50 | Cmd: "ls",
51 | },
52 | }, {
53 | IssuePrefix: "good",
54 | Cmd: CmdWithArgs{
55 | Cmd: "ls",
56 | },
57 | }, {
58 | IssuePrefix: "Ubuntu",
59 | Cmd: CmdWithArgs{
60 | Cmd: "ls",
61 | },
62 | }},
63 | CommonInstaller: fake.NewFakeInstaller(true, false),
64 | }
65 | assert.Nil(t, withPreInstall.Install())
66 |
67 | withErrorPreInstall := &genericPackage{
68 | execer: fakeruntime.FakeExecer{
69 | ExpectOS: fakeruntime.OSLinux,
70 | ExpectError: errors.New("error"),
71 | },
72 | PreInstall: []preInstall{{
73 | Cmd: CmdWithArgs{
74 | Cmd: "ls",
75 | },
76 | }},
77 | CommonInstaller: fake.NewFakeInstaller(true, true),
78 | }
79 | assert.NotNil(t, withErrorPreInstall.Install())
80 | assert.NotNil(t, withErrorPreInstall.Uninstall())
81 | assert.True(t, withErrorPreInstall.Available())
82 |
83 | tmpFile, err := os.CreateTemp(os.TempDir(), "installer")
84 | assert.Nil(t, err)
85 | defer func() {
86 | os.Remove(tmpFile.Name())
87 | }()
88 | writeToFileInstall := &genericPackage{
89 | execer: fakeruntime.FakeExecer{
90 | ExpectOS: fakeruntime.OSLinux,
91 | },
92 | PreInstall: []preInstall{{
93 | Cmd: CmdWithArgs{
94 | WriteTo: &WriteTo{
95 | File: tmpFile.Name(),
96 | Content: "sample",
97 | },
98 | },
99 | }},
100 | CommonInstaller: fake.NewFakeInstaller(true, false),
101 | }
102 | err = writeToFileInstall.Install()
103 | assert.Nil(t, err)
104 | data, err := io.ReadAll(tmpFile)
105 | assert.Nil(t, err)
106 | assert.Equal(t, "sample", string(data))
107 | }
108 |
109 | func TestShould(t *testing.T) {
110 | tests := []struct {
111 | name string
112 | writeTo *WriteTo
113 | wantOK bool
114 | wantErr bool
115 | }{{
116 | name: "expr is empty",
117 | writeTo: &WriteTo{},
118 | wantOK: true,
119 | wantErr: false,
120 | }, {
121 | name: "1==1",
122 | writeTo: &WriteTo{
123 | When: "1==1",
124 | },
125 | wantOK: true,
126 | wantErr: false,
127 | }, {
128 | name: "not bool expr",
129 | writeTo: &WriteTo{
130 | When: "not-expect",
131 | },
132 | wantOK: false,
133 | wantErr: true,
134 | }, {
135 | name: "false",
136 | writeTo: &WriteTo{
137 | When: "false",
138 | },
139 | wantOK: false,
140 | wantErr: false,
141 | }, {
142 | name: "true",
143 | writeTo: &WriteTo{
144 | When: "true",
145 | },
146 | wantOK: true,
147 | wantErr: false,
148 | }, {
149 | name: "expr is number",
150 | writeTo: &WriteTo{
151 | When: "123",
152 | },
153 | wantOK: false,
154 | wantErr: true,
155 | }, {
156 | name: "with env, equal",
157 | writeTo: &WriteTo{
158 | env: map[string]string{
159 | "OS": "ubuntu",
160 | },
161 | When: "OS=='ubuntu'",
162 | },
163 | wantOK: true,
164 | wantErr: false,
165 | }, {
166 | name: "with env, not equal",
167 | writeTo: &WriteTo{
168 | env: map[string]string{
169 | "OS": "ubuntu",
170 | },
171 | When: "OS!='ubuntu'",
172 | },
173 | wantOK: false,
174 | wantErr: false,
175 | }}
176 | for _, tt := range tests {
177 | t.Run(tt.name, func(t *testing.T) {
178 | ok, err := tt.writeTo.Should()
179 | assert.Equal(t, tt.wantOK, ok)
180 | assert.Equal(t, tt.wantErr, err != nil, err)
181 | })
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/pkg/os/installer.go:
--------------------------------------------------------------------------------
1 | package os
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 | "strings"
7 |
8 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
9 | "github.com/linuxsuren/http-downloader/pkg/os/apt"
10 | "github.com/linuxsuren/http-downloader/pkg/os/core"
11 | "github.com/linuxsuren/http-downloader/pkg/os/dnf"
12 | "github.com/linuxsuren/http-downloader/pkg/os/docker"
13 | "github.com/linuxsuren/http-downloader/pkg/os/yum"
14 | "github.com/mitchellh/go-homedir"
15 | )
16 |
17 | // DefaultInstallerRegistry is the default installer registry
18 | type DefaultInstallerRegistry struct {
19 | installerMap map[string][]core.Installer
20 | }
21 |
22 | var defaultInstallerRegistry *DefaultInstallerRegistry
23 |
24 | func init() {
25 | defaultExecer := fakeruntime.DefaultExecer{}
26 | defaultInstallerRegistry = &DefaultInstallerRegistry{
27 | installerMap: map[string][]core.Installer{},
28 | }
29 | yum.SetInstallerRegistry(defaultInstallerRegistry, defaultExecer)
30 | apt.SetInstallerRegistry(defaultInstallerRegistry, defaultExecer)
31 | docker.SetInstallerRegistry(defaultInstallerRegistry, defaultExecer)
32 | dnf.SetInstallerRegistry(defaultInstallerRegistry, defaultExecer)
33 |
34 | var userHome string
35 | var err error
36 | if userHome, err = homedir.Dir(); err == nil {
37 | configDir := filepath.Join(userHome, "/.config/hd-home")
38 | if err = GenericInstallerRegistry(filepath.Join(configDir, "config/generic.yaml"), defaultInstallerRegistry); err != nil {
39 | fmt.Println(err)
40 | }
41 | }
42 | }
43 |
44 | // Registry registries a DockerInstaller
45 | func (r *DefaultInstallerRegistry) Registry(name string, installer core.Installer) {
46 | _, ok := r.installerMap[name]
47 | if ok {
48 | r.installerMap[name] = append(r.installerMap[name], installer)
49 | } else {
50 | r.installerMap[name] = []core.Installer{installer}
51 | }
52 | }
53 |
54 | // GetInstallers returns all the installers belong to a package
55 | func GetInstallers(name string) (installers []core.Installer, ok bool) {
56 | installers, ok = defaultInstallerRegistry.installerMap[name]
57 | return
58 | }
59 |
60 | // HasPackage finds if the target package installer exist
61 | func HasPackage(name string) bool {
62 | if installers, ok := GetInstallers(name); ok {
63 | for _, item := range installers {
64 | if item.Available() {
65 | return true
66 | }
67 | }
68 | }
69 | return false
70 | }
71 |
72 | // SearchPackages searches the packages by keyword
73 | func SearchPackages(keyword string) (pkgs []string) {
74 | for key, pms := range defaultInstallerRegistry.installerMap {
75 | if strings.Contains(key, keyword) {
76 | var available bool
77 | for _, pm := range pms {
78 | if pm.Available() {
79 | available = true
80 | break
81 | }
82 | }
83 |
84 | if available {
85 | pkgs = append(pkgs, key)
86 | }
87 | }
88 | }
89 | return
90 | }
91 |
92 | // InstallWithProxy installs a package with name
93 | func InstallWithProxy(name string, proxy map[string]string) (err error) {
94 | var installer core.Installer
95 | if installers, ok := GetInstallers(name); ok {
96 | for _, installer = range installers {
97 | if installer.Available() {
98 | if proxyAble, ok := installer.(core.ProxyAble); ok {
99 | proxyAble.SetURLReplace(proxy)
100 | }
101 |
102 | err = installer.Install()
103 | break
104 | }
105 | }
106 | }
107 |
108 | if installer != nil && err == nil {
109 | if err = installer.Start(); err != nil {
110 | err = fmt.Errorf("failed to start service %s, error: %v", name, err)
111 | return
112 | }
113 |
114 | var ok bool
115 | if ok, err = installer.WaitForStart(); err != nil {
116 | err = fmt.Errorf("failed to check the service status of %s, error: %v", name, err)
117 | } else if !ok {
118 | err = fmt.Errorf("%s was not started yet, please check it manually", name)
119 | }
120 | }
121 | return
122 | }
123 |
124 | // Uninstall uninstalls a package with name
125 | func Uninstall(name string) (err error) {
126 | if installers, ok := GetInstallers(name); ok {
127 | for _, installer := range installers {
128 | if installer.Available() {
129 | err = installer.Uninstall()
130 | break
131 | }
132 | }
133 | }
134 | return
135 | }
136 |
--------------------------------------------------------------------------------
/pkg/os/installer_test.go:
--------------------------------------------------------------------------------
1 | package os
2 |
3 | import (
4 | "runtime"
5 | "testing"
6 |
7 | "github.com/linuxsuren/http-downloader/pkg/os/fake"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestHasPackage(t *testing.T) {
12 | // currently, this function only support Linux
13 | if runtime.GOOS != "linux" {
14 | return
15 | }
16 |
17 | type args struct {
18 | name string
19 | }
20 | tests := []struct {
21 | name string
22 | args args
23 | want bool
24 | }{{
25 | name: "fake-package",
26 | args: args{
27 | name: "fake-package",
28 | },
29 | want: false,
30 | }, {
31 | name: "docker",
32 | args: args{
33 | name: "docker",
34 | },
35 | want: true,
36 | }, {
37 | name: "golang",
38 | args: args{
39 | name: "golang",
40 | },
41 | want: true,
42 | }, {
43 | name: "git",
44 | args: args{
45 | name: "git",
46 | },
47 | want: true,
48 | }, {
49 | name: "bash-completion",
50 | args: args{
51 | name: "bash-completion",
52 | },
53 | want: true,
54 | }}
55 | for _, tt := range tests {
56 | t.Run(tt.name, func(t *testing.T) {
57 | if got := HasPackage(tt.args.name); got != tt.want {
58 | t.Errorf("test: %s, HasPackage() = %v, want %v", tt.name, got, tt.want)
59 | }
60 | })
61 | }
62 | }
63 |
64 | func TestWithFakeInstaller(t *testing.T) {
65 | // test uninstall a fake package
66 | err := Uninstall("fake")
67 | assert.Nil(t, err)
68 | assert.False(t, HasPackage("fake"))
69 |
70 | defaultInstallerRegistry.Registry("fake", fake.NewFakeInstaller(true, false))
71 | err = Uninstall("fake")
72 | assert.Nil(t, err)
73 | err = InstallWithProxy("fake", nil)
74 | assert.Nil(t, err)
75 |
76 | defaultInstallerRegistry.Registry("fake-with-err", fake.NewFakeInstaller(true, true))
77 | err = Uninstall("fake-with-err")
78 | assert.NotNil(t, err)
79 | }
80 |
81 | func TestSearchPackages(t *testing.T) {
82 | // currently, this function only support Linux
83 | if runtime.GOOS != "linux" {
84 | return
85 | }
86 | pkgs := SearchPackages("vim")
87 | assert.NotEmpty(t, pkgs)
88 | }
89 |
--------------------------------------------------------------------------------
/pkg/os/npm/common.go:
--------------------------------------------------------------------------------
1 | package npm
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // NPMName is the name of npm
10 | const NPMName = "npm"
11 |
12 | // CommonInstaller is the installer of a common npm
13 | type CommonInstaller struct {
14 | Name string
15 | Execer fakeruntime.Execer
16 | }
17 |
18 | // Available check if support current platform
19 | func (d *CommonInstaller) Available() (ok bool) {
20 | _, err := d.Execer.LookPath(NPMName)
21 | ok = err == nil
22 | return
23 | }
24 |
25 | // Install installs the target package
26 | func (d *CommonInstaller) Install() (err error) {
27 | err = d.Execer.RunCommand(NPMName, "i", "-g", d.Name)
28 | return
29 | }
30 |
31 | // Uninstall uninstalls the target package
32 | func (d *CommonInstaller) Uninstall() (err error) {
33 | err = d.Execer.RunCommand(NPMName, "uninstall", "-g", d.Name)
34 | return
35 | }
36 |
37 | // WaitForStart waits for the service be started
38 | func (d *CommonInstaller) WaitForStart() (ok bool, err error) {
39 | ok = true
40 | return
41 | }
42 |
43 | // Start starts the target service
44 | func (d *CommonInstaller) Start() error {
45 | fmt.Println("not supported yet")
46 | return nil
47 | }
48 |
49 | // Stop stops the target service
50 | func (d *CommonInstaller) Stop() error {
51 | fmt.Println("not supported yet")
52 | return nil
53 | }
54 |
--------------------------------------------------------------------------------
/pkg/os/npm/common_test.go:
--------------------------------------------------------------------------------
1 | package npm
2 |
3 | import (
4 | "testing"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestCommon(t *testing.T) {
11 | tests := []struct {
12 | name string
13 | installer CommonInstaller
14 | expectAvailable bool
15 | hasErr bool
16 | }{{
17 | name: "normal",
18 | installer: CommonInstaller{
19 | Execer: fakeruntime.FakeExecer{
20 | ExpectError: nil,
21 | ExpectOutput: "",
22 | ExpectOS: "linux",
23 | ExpectArch: "amd64",
24 | },
25 | },
26 | expectAvailable: true,
27 | hasErr: false,
28 | }}
29 | for _, tt := range tests {
30 | t.Run(tt.name, func(t *testing.T) {
31 | assert.Equal(t, tt.expectAvailable, tt.installer.Available())
32 | assert.Nil(t, tt.installer.Start())
33 | assert.Nil(t, tt.installer.Stop())
34 |
35 | ok, err := tt.installer.WaitForStart()
36 | assert.True(t, ok)
37 | assert.Nil(t, err)
38 | assert.Equal(t, tt.hasErr, tt.installer.Install() != nil)
39 | assert.Equal(t, tt.hasErr, tt.installer.Uninstall() != nil)
40 | })
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/os/npm/doc.go:
--------------------------------------------------------------------------------
1 | // Package npm provides the common usage of npm.
2 | // See also https://www.npmjs.com/.
3 | package npm
4 |
--------------------------------------------------------------------------------
/pkg/os/scoop/common.go:
--------------------------------------------------------------------------------
1 | package scoop
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // Tool is the tool name of this intergration
10 | const Tool = "scoop"
11 |
12 | // CommonInstaller is the installer of Conntrack in CentOS
13 | type CommonInstaller struct {
14 | Name string
15 | Execer fakeruntime.Execer
16 | }
17 |
18 | // Available check if support current platform
19 | func (d *CommonInstaller) Available() (ok bool) {
20 | if d.Execer.OS() == fakeruntime.OSWindows {
21 | _, err := d.Execer.LookPath(Tool)
22 | ok = err == nil
23 | }
24 | return
25 | }
26 |
27 | // Install installs the Conntrack
28 | func (d *CommonInstaller) Install() (err error) {
29 | if err = d.Execer.RunCommand(Tool, "bucket", "add", "extras"); err == nil {
30 | err = d.Execer.RunCommand(Tool, "install", d.Name)
31 | }
32 | return
33 | }
34 |
35 | // Uninstall uninstalls the Conntrack
36 | func (d *CommonInstaller) Uninstall() (err error) {
37 | err = d.Execer.RunCommand(Tool, "uninstall", d.Name)
38 | return
39 | }
40 |
41 | // WaitForStart waits for the service be started
42 | func (d *CommonInstaller) WaitForStart() (ok bool, err error) {
43 | ok = true
44 | return
45 | }
46 |
47 | // Start starts the Conntrack service
48 | func (d *CommonInstaller) Start() error {
49 | fmt.Println("not supported yet")
50 | return nil
51 | }
52 |
53 | // Stop stops the Conntrack service
54 | func (d *CommonInstaller) Stop() error {
55 | fmt.Println("not supported yet")
56 | return nil
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/os/scoop/doc.go:
--------------------------------------------------------------------------------
1 | // Package scoop implements the scoop integration
2 | package scoop
3 |
--------------------------------------------------------------------------------
/pkg/os/snap/common.go:
--------------------------------------------------------------------------------
1 | package snap
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // SnapName is the name of the snap
10 | const SnapName = "snap"
11 |
12 | // CommonInstaller is the installer of a common snap
13 | type CommonInstaller struct {
14 | Name string
15 | Args []string `yaml:"args"`
16 | Execer fakeruntime.Execer
17 | }
18 |
19 | // Available check if support current platform
20 | func (d *CommonInstaller) Available() (ok bool) {
21 | if d.Execer.OS() == fakeruntime.OSLinux {
22 | _, err := d.Execer.LookPath(SnapName)
23 | ok = err == nil
24 | }
25 | return
26 | }
27 |
28 | // Install installs the target package
29 | func (d *CommonInstaller) Install() (err error) {
30 | args := []string{"install", d.Name}
31 | args = append(args, d.Args...)
32 | if err = d.Execer.RunCommand(SnapName, args...); err != nil {
33 | return
34 | }
35 | return
36 | }
37 |
38 | // Uninstall uninstalls the target package
39 | func (d *CommonInstaller) Uninstall() (err error) {
40 | err = d.Execer.RunCommand(SnapName, "remove", d.Name)
41 | return
42 | }
43 |
44 | // WaitForStart waits for the service be started
45 | func (d *CommonInstaller) WaitForStart() (ok bool, err error) {
46 | ok = true
47 | return
48 | }
49 |
50 | // Start starts the target service
51 | func (d *CommonInstaller) Start() error {
52 | fmt.Println("not supported yet")
53 | return nil
54 | }
55 |
56 | // Stop stops the target service
57 | func (d *CommonInstaller) Stop() error {
58 | fmt.Println("not supported yet")
59 | return nil
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/os/snap/common_test.go:
--------------------------------------------------------------------------------
1 | package snap
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestCommon(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | installer CommonInstaller
15 | expectAvailable bool
16 | hasErr bool
17 | }{{
18 | name: "normal",
19 | installer: CommonInstaller{
20 | Execer: fakeruntime.FakeExecer{
21 | ExpectError: nil,
22 | ExpectOutput: "",
23 | ExpectOS: "linux",
24 | ExpectArch: "amd64",
25 | },
26 | },
27 | expectAvailable: true,
28 | hasErr: false,
29 | }, {
30 | name: "not is linux",
31 | installer: CommonInstaller{
32 | Execer: fakeruntime.FakeExecer{ExpectOS: "darwin"},
33 | },
34 | expectAvailable: false,
35 | hasErr: false,
36 | }, {
37 | name: "command not found",
38 | installer: CommonInstaller{
39 | Execer: fakeruntime.FakeExecer{ExpectError: errors.New("error")},
40 | },
41 | expectAvailable: false,
42 | hasErr: true,
43 | }}
44 | for _, tt := range tests {
45 | t.Run(tt.name, func(t *testing.T) {
46 | assert.Equal(t, tt.expectAvailable, tt.installer.Available())
47 | assert.Nil(t, tt.installer.Start())
48 | assert.Nil(t, tt.installer.Stop())
49 |
50 | ok, err := tt.installer.WaitForStart()
51 | assert.True(t, ok)
52 | assert.Nil(t, err)
53 | assert.Equal(t, tt.hasErr, tt.installer.Install() != nil)
54 | assert.Equal(t, tt.hasErr, tt.installer.Uninstall() != nil)
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/os/snap/doc.go:
--------------------------------------------------------------------------------
1 | // Package snap provides the common usage of snap package manager.
2 | // See also https://snapcraft.io/.
3 | package snap
4 |
--------------------------------------------------------------------------------
/pkg/os/winget/common.go:
--------------------------------------------------------------------------------
1 | package winget
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // Tool is the tool name of this intergration
10 | const Tool = "winget"
11 |
12 | // CommonInstaller is the installer of Conntrack in CentOS
13 | type CommonInstaller struct {
14 | Name string
15 | Execer fakeruntime.Execer
16 | }
17 |
18 | // Available check if support current platform
19 | func (d *CommonInstaller) Available() (ok bool) {
20 | if d.Execer.OS() == fakeruntime.OSWindows {
21 | _, err := d.Execer.LookPath(Tool)
22 | ok = err == nil
23 | }
24 | return
25 | }
26 |
27 | // Install installs the Conntrack
28 | func (d *CommonInstaller) Install() (err error) {
29 | if err = d.Execer.RunCommand(Tool, "install", "--silent", "--accept-package-agreements", d.Name); err != nil {
30 | return
31 | }
32 | return
33 | }
34 |
35 | // Uninstall uninstalls the Conntrack
36 | func (d *CommonInstaller) Uninstall() (err error) {
37 | err = d.Execer.RunCommand(Tool, "uninstall", "--silent", d.Name)
38 | return
39 | }
40 |
41 | // WaitForStart waits for the service be started
42 | func (d *CommonInstaller) WaitForStart() (ok bool, err error) {
43 | ok = true
44 | return
45 | }
46 |
47 | // Start starts the Conntrack service
48 | func (d *CommonInstaller) Start() error {
49 | fmt.Println("not supported yet")
50 | return nil
51 | }
52 |
53 | // Stop stops the Conntrack service
54 | func (d *CommonInstaller) Stop() error {
55 | fmt.Println("not supported yet")
56 | return nil
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/os/winget/doc.go:
--------------------------------------------------------------------------------
1 | // Package winget implements the winget integration
2 | package winget
3 |
--------------------------------------------------------------------------------
/pkg/os/yum/bash-completion.go:
--------------------------------------------------------------------------------
1 | package yum
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // bashCompletionInstallerInCentOS is the installer of bashCompletion in CentOS
10 | type bashCompletionInstallerInCentOS struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *bashCompletionInstallerInCentOS) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("yum")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the bashCompletion
24 | func (d *bashCompletionInstallerInCentOS) Install() (err error) {
25 | if err = d.Execer.RunCommand("yum", "install", "-y", "bash-completion"); err != nil {
26 | return
27 | }
28 | return
29 | }
30 |
31 | // Uninstall uninstalls the bashCompletion
32 | func (d *bashCompletionInstallerInCentOS) Uninstall() (err error) {
33 | err = d.Execer.RunCommand("yum", "remove", "-y", "bash-completion")
34 | return
35 | }
36 |
37 | // WaitForStart waits for the service be started
38 | func (d *bashCompletionInstallerInCentOS) WaitForStart() (ok bool, err error) {
39 | ok = true
40 | return
41 | }
42 |
43 | // Start starts the bashCompletion service
44 | func (d *bashCompletionInstallerInCentOS) Start() error {
45 | fmt.Println("not supported yet")
46 | return nil
47 | }
48 |
49 | // Stop stops the bashCompletion service
50 | func (d *bashCompletionInstallerInCentOS) Stop() error {
51 | fmt.Println("not supported yet")
52 | return nil
53 | }
54 |
--------------------------------------------------------------------------------
/pkg/os/yum/common.go:
--------------------------------------------------------------------------------
1 | package yum
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | const (
10 | // Tool is the tool name of yum
11 | Tool = "yum"
12 | )
13 |
14 | // CommonInstaller is the installer of Conntrack in CentOS
15 | type CommonInstaller struct {
16 | Name string
17 | Execer fakeruntime.Execer
18 | }
19 |
20 | // Available check if support current platform
21 | func (d *CommonInstaller) Available() (ok bool) {
22 | if d.Execer.OS() == fakeruntime.OSLinux {
23 | _, err := d.Execer.LookPath(Tool)
24 | ok = err == nil
25 | }
26 | return
27 | }
28 |
29 | // Install installs the Conntrack
30 | func (d *CommonInstaller) Install() (err error) {
31 | if err = d.Execer.RunCommand(Tool, "update", "-y"); err != nil {
32 | return
33 | }
34 | if err = d.Execer.RunCommand(Tool, "install", "-y", d.Name); err != nil {
35 | return
36 | }
37 | return
38 | }
39 |
40 | // Uninstall uninstalls the Conntrack
41 | func (d *CommonInstaller) Uninstall() (err error) {
42 | err = d.Execer.RunCommand(Tool, "remove", "-y", d.Name)
43 | return
44 | }
45 |
46 | // WaitForStart waits for the service be started
47 | func (d *CommonInstaller) WaitForStart() (ok bool, err error) {
48 | ok = true
49 | return
50 | }
51 |
52 | // Start starts the Conntrack service
53 | func (d *CommonInstaller) Start() error {
54 | fmt.Println("not supported yet")
55 | return nil
56 | }
57 |
58 | // Stop stops the Conntrack service
59 | func (d *CommonInstaller) Stop() error {
60 | fmt.Println("not supported yet")
61 | return nil
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/os/yum/common_test.go:
--------------------------------------------------------------------------------
1 | package yum
2 |
3 | import (
4 | "errors"
5 | "testing"
6 |
7 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestCommon(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | installer CommonInstaller
15 | expectAvailable bool
16 | hasErr bool
17 | }{{
18 | name: "normal",
19 | installer: CommonInstaller{
20 | Execer: fakeruntime.FakeExecer{
21 | ExpectError: nil,
22 | ExpectOutput: "",
23 | ExpectOS: "linux",
24 | ExpectArch: "amd64",
25 | },
26 | },
27 | expectAvailable: true,
28 | hasErr: false,
29 | }, {
30 | name: "not is linux",
31 | installer: CommonInstaller{
32 | Execer: fakeruntime.FakeExecer{ExpectOS: "darwin"},
33 | },
34 | expectAvailable: false,
35 | hasErr: false,
36 | }, {
37 | name: "command not found",
38 | installer: CommonInstaller{
39 | Execer: fakeruntime.FakeExecer{ExpectError: errors.New("error")},
40 | },
41 | expectAvailable: false,
42 | hasErr: true,
43 | }}
44 | for _, tt := range tests {
45 | t.Run(tt.name, func(t *testing.T) {
46 | assert.Equal(t, tt.expectAvailable, tt.installer.Available())
47 | assert.Nil(t, tt.installer.Start())
48 | assert.Nil(t, tt.installer.Stop())
49 |
50 | ok, err := tt.installer.WaitForStart()
51 | assert.True(t, ok)
52 | assert.Nil(t, err)
53 | assert.Equal(t, tt.hasErr, tt.installer.Install() != nil)
54 | assert.Equal(t, tt.hasErr, tt.installer.Uninstall() != nil)
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/os/yum/conntrack.go:
--------------------------------------------------------------------------------
1 | package yum
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // conntrackInstallerInCentOS is the installer of Docker in CentOS
10 | type conntrackInstallerInCentOS struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *conntrackInstallerInCentOS) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("yum")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the Docker
24 | func (d *conntrackInstallerInCentOS) Install() (err error) {
25 | if err = d.Execer.RunCommand("yum", "install", "-y",
26 | "conntrack"); err != nil {
27 | return
28 | }
29 | return
30 | }
31 |
32 | // Uninstall uninstalls the Docker
33 | func (d *conntrackInstallerInCentOS) Uninstall() (err error) {
34 | err = d.Execer.RunCommand("yum", "remove", "-y",
35 | "conntrack")
36 | return
37 | }
38 |
39 | // WaitForStart waits for the service be started
40 | func (d *conntrackInstallerInCentOS) WaitForStart() (ok bool, err error) {
41 | ok = true
42 | return
43 | }
44 |
45 | // Start starts the Docker service
46 | func (d *conntrackInstallerInCentOS) Start() error {
47 | fmt.Println("not supported yet")
48 | return nil
49 | }
50 |
51 | // Stop stops the Docker service
52 | func (d *conntrackInstallerInCentOS) Stop() error {
53 | fmt.Println("not supported yet")
54 | return nil
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/os/yum/doc.go:
--------------------------------------------------------------------------------
1 | // Package yum provides the common usage of yum package manager.
2 | // See also https://en.wikipedia.org/wiki/Yum_(software).
3 | package yum
4 |
--------------------------------------------------------------------------------
/pkg/os/yum/docker.go:
--------------------------------------------------------------------------------
1 | package yum
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "time"
7 |
8 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
9 | )
10 |
11 | // dockerInstallerInCentOS is the installer of Docker in CentOS
12 | type dockerInstallerInCentOS struct {
13 | Execer fakeruntime.Execer
14 | count int
15 | }
16 |
17 | // Available check if support current platform
18 | func (d *dockerInstallerInCentOS) Available() (ok bool) {
19 | if d.Execer.OS() == "linux" {
20 | _, err := d.Execer.LookPath("yum")
21 | ok = err == nil
22 | }
23 | return
24 | }
25 |
26 | // Install installs the Docker
27 | func (d *dockerInstallerInCentOS) Install() (err error) {
28 | if err = d.Execer.RunCommand("yum", "install", "-y",
29 | "yum-utils"); err != nil {
30 | return
31 | }
32 | if err = d.Execer.RunCommand("yum-config-manager", "--add-repo",
33 | "https://download.docker.com/linux/centos/docker-ce.repo"); err != nil {
34 | return
35 | }
36 | if err = d.Execer.RunCommand("yum", "install", "-y",
37 | "docker-ce",
38 | "docker-ce-cli",
39 | "containerd.io"); err != nil {
40 | return
41 | }
42 | return
43 | }
44 |
45 | // Uninstall uninstalls the Docker
46 | func (d *dockerInstallerInCentOS) Uninstall() (err error) {
47 | err = d.Execer.RunCommand("yum", "remove", "-y",
48 | "docker",
49 | "docker-client",
50 | "docker-client-latest",
51 | "docker-common",
52 | "docker-latest",
53 | "docker-latest-logrotate",
54 | "docker-logrotate",
55 | "docker-engine",
56 | "docker-ce",
57 | "docker-ce-cli",
58 | "containerd.io")
59 | return
60 | }
61 |
62 | // WaitForStart waits for the service be started
63 | func (d *dockerInstallerInCentOS) WaitForStart() (ok bool, err error) {
64 | var result string
65 | if result, err = d.Execer.RunCommandAndReturn("systemctl", "", "status", "docker"); err != nil {
66 | return
67 | } else if strings.Contains(result, "Unit docker.service could not be found") {
68 | err = fmt.Errorf("unit docker.service could not be found")
69 | } else if strings.Contains(result, "Active: active") {
70 | ok = true
71 | } else {
72 | if d.count > 0 {
73 | fmt.Println("waiting for Docker service start")
74 | } else if d.count > 9 {
75 | return
76 | }
77 |
78 | d.count++
79 | time.Sleep(time.Second * 1)
80 | return d.WaitForStart()
81 | }
82 | return
83 | }
84 |
85 | // Start starts the Docker service
86 | func (d *dockerInstallerInCentOS) Start() error {
87 | return d.Execer.RunCommand("systemctl", "start", "docker")
88 | }
89 |
90 | // Stop stops the Docker service
91 | func (d *dockerInstallerInCentOS) Stop() error {
92 | return d.Execer.RunCommand("systemctl", "stop", "docker")
93 | }
94 |
--------------------------------------------------------------------------------
/pkg/os/yum/git.go:
--------------------------------------------------------------------------------
1 | package yum
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // gitInstallerInCentOS is the installer of git in CentOS
10 | type gitInstallerInCentOS struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *gitInstallerInCentOS) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("yum")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the git
24 | func (d *gitInstallerInCentOS) Install() (err error) {
25 | if err = d.Execer.RunCommand("yum", "install", "-y",
26 | "git"); err != nil {
27 | return
28 | }
29 | return
30 | }
31 |
32 | // Uninstall uninstalls the git
33 | func (d *gitInstallerInCentOS) Uninstall() (err error) {
34 | err = d.Execer.RunCommand("yum", "remove", "-y",
35 | "git")
36 | return
37 | }
38 |
39 | // WaitForStart waits for the service be started
40 | func (d *gitInstallerInCentOS) WaitForStart() (ok bool, err error) {
41 | ok = true
42 | return
43 | }
44 |
45 | // Start starts the git service
46 | func (d *gitInstallerInCentOS) Start() error {
47 | fmt.Println("not supported yet")
48 | return nil
49 | }
50 |
51 | // Stop stops the git service
52 | func (d *gitInstallerInCentOS) Stop() error {
53 | fmt.Println("not supported yet")
54 | return nil
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/os/yum/golang.go:
--------------------------------------------------------------------------------
1 | package yum
2 |
3 | import (
4 | // Enable go embed
5 | _ "embed"
6 | "fmt"
7 | "os"
8 |
9 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
10 | )
11 |
12 | //go:embed resource/go-repo.repo
13 | var goRepo string
14 |
15 | // golangInstallerInCentOS is the installer of golang in CentOS
16 | type golangInstallerInCentOS struct {
17 | Execer fakeruntime.Execer
18 | }
19 |
20 | // Available check if support current platform
21 | func (d *golangInstallerInCentOS) Available() (ok bool) {
22 | if d.Execer.OS() == "linux" {
23 | _, err := d.Execer.LookPath("yum")
24 | ok = err == nil
25 | }
26 | return
27 | }
28 |
29 | // Install installs the golang
30 | func (d *golangInstallerInCentOS) Install() (err error) {
31 | if err = d.Execer.RunCommand("rpm", "--import", "https://mirror.go-repo.io/centos/RPM-GPG-KEY-GO-REPO"); err != nil {
32 | return
33 | }
34 |
35 | if err = os.WriteFile("/etc/yum.repos.d/go-repo.repo", []byte(goRepo), 0644); err != nil {
36 | err = fmt.Errorf("failed to save go-repo.repo, error: %v", err)
37 | return
38 | }
39 | if err = d.Execer.RunCommand("yum", "install", "-y", "golang"); err != nil {
40 | return
41 | }
42 | return
43 | }
44 |
45 | // Uninstall uninstalls the golang
46 | func (d *golangInstallerInCentOS) Uninstall() (err error) {
47 | err = d.Execer.RunCommand("yum", "remove", "-y", "golang")
48 | return
49 | }
50 |
51 | // WaitForStart waits for the service be started
52 | func (d *golangInstallerInCentOS) WaitForStart() (ok bool, err error) {
53 | ok = true
54 | return
55 | }
56 |
57 | // Start starts the golang service
58 | func (d *golangInstallerInCentOS) Start() error {
59 | fmt.Println("not supported yet")
60 | return nil
61 | }
62 |
63 | // Stop stops the golang service
64 | func (d *golangInstallerInCentOS) Stop() error {
65 | fmt.Println("not supported yet")
66 | return nil
67 | }
68 |
--------------------------------------------------------------------------------
/pkg/os/yum/init.go:
--------------------------------------------------------------------------------
1 | package yum
2 |
3 | import (
4 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
5 | "github.com/linuxsuren/http-downloader/pkg/os/core"
6 | )
7 |
8 | // SetInstallerRegistry sets the installer of registry
9 | func SetInstallerRegistry(registry core.InstallerRegistry, defaultExecer fakeruntime.Execer) {
10 | registry.Registry("docker", &dockerInstallerInCentOS{Execer: defaultExecer})
11 | registry.Registry("conntrack", &conntrackInstallerInCentOS{Execer: defaultExecer})
12 | registry.Registry("socat", &socatInstallerInCentOS{Execer: defaultExecer})
13 | registry.Registry("vim", &vimInstallerInCentOS{Execer: defaultExecer})
14 | registry.Registry("golang", &golangInstallerInCentOS{Execer: defaultExecer})
15 | registry.Registry("git", &gitInstallerInCentOS{Execer: defaultExecer})
16 | registry.Registry("bash-completion", &bashCompletionInstallerInCentOS{Execer: defaultExecer})
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/os/yum/init_test.go:
--------------------------------------------------------------------------------
1 | package yum
2 |
3 | import (
4 | "testing"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | "github.com/linuxsuren/http-downloader/pkg/os/core"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestCommonCase(t *testing.T) {
12 | registry := &core.FakeRegistry{}
13 | SetInstallerRegistry(registry, fakeruntime.FakeExecer{
14 | ExpectOutput: "Active: active",
15 | ExpectOS: "linux",
16 | })
17 |
18 | registry.Walk(func(s string, i core.Installer) {
19 | t.Run(s, func(t *testing.T) {
20 | assert.True(t, i.Available())
21 | if s != "golang" {
22 | assert.Nil(t, i.Install())
23 | }
24 | assert.Nil(t, i.Uninstall())
25 | if s != "docker" {
26 | assert.Nil(t, i.Start())
27 | assert.Nil(t, i.Stop())
28 | }
29 | ok, err := i.WaitForStart()
30 | assert.True(t, ok)
31 | assert.Nil(t, err)
32 | })
33 | })
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/os/yum/resource/go-repo.repo:
--------------------------------------------------------------------------------
1 | [go-repo]
2 | name=go-repo - CentOS
3 | baseurl=https://mirror.go-repo.io/centos/$releasever/$basearch/
4 | enabled=1
5 | gpgcheck=1
6 | gpgkey=https://mirror.go-repo.io/centos/RPM-GPG-KEY-GO-REPO
7 |
--------------------------------------------------------------------------------
/pkg/os/yum/resource/kubernetes.repo:
--------------------------------------------------------------------------------
1 | [kubernetes]
2 | name=Kubernetes
3 | baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
4 | enabled=1
5 | gpgcheck=1
6 | repo_gpgcheck=1
7 | gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
8 |
--------------------------------------------------------------------------------
/pkg/os/yum/socat.go:
--------------------------------------------------------------------------------
1 | package yum
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // socatInstallerInCentOS is the installer of socat in CentOS
10 | type socatInstallerInCentOS struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *socatInstallerInCentOS) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("yum")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the socat
24 | func (d *socatInstallerInCentOS) Install() (err error) {
25 | if err = d.Execer.RunCommand("yum", "install", "-y",
26 | "socat"); err != nil {
27 | return
28 | }
29 | return
30 | }
31 |
32 | // Uninstall uninstalls the socat
33 | func (d *socatInstallerInCentOS) Uninstall() (err error) {
34 | err = d.Execer.RunCommand("yum", "remove", "-y",
35 | "socat")
36 | return
37 | }
38 |
39 | // WaitForStart waits for the service be started
40 | func (d *socatInstallerInCentOS) WaitForStart() (ok bool, err error) {
41 | ok = true
42 | return
43 | }
44 |
45 | // Start starts the socat service
46 | func (d *socatInstallerInCentOS) Start() error {
47 | fmt.Println("not supported yet")
48 | return nil
49 | }
50 |
51 | // Stop stops the socat service
52 | func (d *socatInstallerInCentOS) Stop() error {
53 | fmt.Println("not supported yet")
54 | return nil
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/os/yum/vim.go:
--------------------------------------------------------------------------------
1 | package yum
2 |
3 | import (
4 | "fmt"
5 |
6 | fakeruntime "github.com/linuxsuren/go-fake-runtime"
7 | )
8 |
9 | // vimInstallerInCentOS is the installer of vim in CentOS
10 | type vimInstallerInCentOS struct {
11 | Execer fakeruntime.Execer
12 | }
13 |
14 | // Available check if support current platform
15 | func (d *vimInstallerInCentOS) Available() (ok bool) {
16 | if d.Execer.OS() == "linux" {
17 | _, err := d.Execer.LookPath("yum")
18 | ok = err == nil
19 | }
20 | return
21 | }
22 |
23 | // Install installs the vim
24 | func (d *vimInstallerInCentOS) Install() (err error) {
25 | if err = d.Execer.RunCommand("yum", "install", "-y",
26 | "vim"); err != nil {
27 | return
28 | }
29 | return
30 | }
31 |
32 | // Uninstall uninstalls the vim
33 | func (d *vimInstallerInCentOS) Uninstall() (err error) {
34 | err = d.Execer.RunCommand("yum", "remove", "-y",
35 | "vim")
36 | return
37 | }
38 |
39 | // WaitForStart waits for the service be started
40 | func (d *vimInstallerInCentOS) WaitForStart() (ok bool, err error) {
41 | ok = true
42 | return
43 | }
44 |
45 | // Start starts the vim service
46 | func (d *vimInstallerInCentOS) Start() error {
47 | fmt.Println("not supported yet")
48 | return nil
49 | }
50 |
51 | // Stop stops the vim service
52 | func (d *vimInstallerInCentOS) Stop() error {
53 | fmt.Println("not supported yet")
54 | return nil
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/progress.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | /**
4 | * This file was deprecated, please use the following package instead
5 | * github.com/linuxsuren/http-downloader/pkg/net
6 | */
7 |
8 | import (
9 | "github.com/linuxsuren/http-downloader/pkg/net"
10 | )
11 |
12 | // ProgressIndicator hold the progress of io operation
13 | type ProgressIndicator net.ProgressIndicator
14 |
15 | // Init set the default value for progress indicator
16 | func (i *ProgressIndicator) Init() {
17 | (*net.ProgressIndicator)(i).Init()
18 | }
19 |
20 | // Write writes the progress
21 | func (i *ProgressIndicator) Write(p []byte) (n int, err error) {
22 | return (*net.ProgressIndicator)(i).Write(p)
23 | }
24 |
25 | // Read reads the progress
26 | func (i *ProgressIndicator) Read(p []byte) (n int, err error) {
27 | return (*net.ProgressIndicator)(i).Read(p)
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/release.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | import (
4 | "context"
5 | "github.com/google/go-github/v29/github"
6 | )
7 |
8 | // ReleaseClient is the client of jcli github
9 | type ReleaseClient struct {
10 | Client *github.Client
11 | Org string
12 | Repo string
13 |
14 | ctx context.Context
15 | }
16 |
17 | // ReleaseAsset is the asset from GitHub release
18 | type ReleaseAsset struct {
19 | TagName string
20 | Body string
21 | }
22 |
23 | // Init init the GitHub client
24 | func (g *ReleaseClient) Init() {
25 | g.Client = github.NewClient(nil)
26 | g.ctx = context.TODO()
27 | }
28 |
29 | // ListReleases returns the release list
30 | func (g *ReleaseClient) ListReleases(owner, repo string, count int) (list []ReleaseAsset, err error) {
31 | opt := &github.ListOptions{
32 | PerPage: count,
33 | }
34 |
35 | var releaseList []*github.RepositoryRelease
36 | if releaseList, _, err = g.Client.Repositories.ListReleases(g.ctx, owner, repo, opt); err == nil {
37 | for i := range releaseList {
38 | list = append(list, ReleaseAsset{
39 | TagName: releaseList[i].GetTagName(),
40 | Body: releaseList[i].GetBody(),
41 | })
42 | }
43 | }
44 | return
45 | }
46 |
47 | // GetLatestJCLIAsset returns the latest jcli asset
48 | // deprecated, please use GetLatestAsset instead
49 | func (g *ReleaseClient) GetLatestJCLIAsset() (*ReleaseAsset, error) {
50 | return g.GetLatestReleaseAsset(g.Org, g.Repo)
51 | }
52 |
53 | // GetLatestAsset returns the latest release asset which can accept preRelease or not
54 | func (g *ReleaseClient) GetLatestAsset(acceptPreRelease bool) (*ReleaseAsset, error) {
55 | if acceptPreRelease {
56 | return g.GetLatestPreReleaseAsset(g.Org, g.Repo)
57 | }
58 | return g.GetLatestReleaseAsset(g.Org, g.Repo)
59 | }
60 |
61 | // GetLatestPreReleaseAsset returns the release asset that could be preRelease
62 | func (g *ReleaseClient) GetLatestPreReleaseAsset(owner, repo string) (ra *ReleaseAsset, err error) {
63 | ctx := context.Background()
64 |
65 | var list []*github.RepositoryRelease
66 | if list, _, err = g.Client.Repositories.ListReleases(ctx, owner, repo, &github.ListOptions{
67 | Page: 1,
68 | PerPage: 5,
69 | }); err == nil {
70 | ra = &ReleaseAsset{
71 | TagName: list[0].GetTagName(),
72 | Body: list[0].GetBody(),
73 | }
74 | }
75 | return
76 | }
77 |
78 | // GetLatestReleaseAsset returns the latest release asset
79 | func (g *ReleaseClient) GetLatestReleaseAsset(owner, repo string) (ra *ReleaseAsset, err error) {
80 | ctx := context.Background()
81 |
82 | var release *github.RepositoryRelease
83 | if release, _, err = g.Client.Repositories.GetLatestRelease(ctx, owner, repo); err == nil {
84 | ra = &ReleaseAsset{
85 | TagName: release.GetTagName(),
86 | Body: release.GetBody(),
87 | }
88 | }
89 | return
90 | }
91 |
92 | // GetJCLIAsset returns the asset from a tag name
93 | func (g *ReleaseClient) GetJCLIAsset(tagName string) (*ReleaseAsset, error) {
94 | return g.GetReleaseAssetByTagName(g.Org, g.Repo, tagName)
95 | }
96 |
97 | // GetReleaseAssetByTagName returns the release asset by tag name
98 | func (g *ReleaseClient) GetReleaseAssetByTagName(owner, repo, tagName string) (ra *ReleaseAsset, err error) {
99 | var list []ReleaseAsset
100 | if list, err = g.ListReleases(owner, repo, 99999); err == nil {
101 | for _, item := range list {
102 | if item.TagName == tagName {
103 | ra = &ReleaseAsset{
104 | TagName: item.TagName,
105 | Body: item.Body,
106 | }
107 | break
108 | }
109 | }
110 | }
111 | return
112 | }
113 |
--------------------------------------------------------------------------------
/pkg/setup_test.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/onsi/ginkgo/reporters"
7 |
8 | . "github.com/onsi/ginkgo"
9 | . "github.com/onsi/gomega"
10 | )
11 |
12 | func TestUtils(t *testing.T) {
13 | RegisterFailHandler(Fail)
14 | junitReporter := reporters.NewJUnitReporter("test-utils.xml")
15 | RunSpecsWithDefaultAndCustomReporters(t, "util", []Reporter{junitReporter})
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/version/doc.go:
--------------------------------------------------------------------------------
1 | // Package version provides the functions which related to version parse.
2 | package version
3 |
--------------------------------------------------------------------------------
/pkg/version/parser.go:
--------------------------------------------------------------------------------
1 | package version
2 |
3 | import (
4 | "regexp"
5 | "strings"
6 |
7 | "github.com/blang/semver/v4"
8 | )
9 |
10 | // GetVersion returns the version string from a string
11 | func GetVersion(output string) string {
12 | ver, _ := GetSemVersion(output)
13 | return ver.String()
14 | }
15 |
16 | // GreatThan return true if target is great than output
17 | func GreatThan(target, output string) (ok bool) {
18 | var (
19 | targetVer semver.Version
20 | currentVer semver.Version
21 | err error
22 | )
23 |
24 | if targetVer, err = semver.ParseTolerant(target); err == nil {
25 | if currentVer, err = GetSemVersion(output); err == nil {
26 | ok = targetVer.GT(currentVer)
27 | }
28 | }
29 | return
30 | }
31 |
32 | // GetSemVersion parses the output and returns the semversion
33 | func GetSemVersion(output string) (semVersion semver.Version, err error) {
34 | var verReg *regexp.Regexp
35 | verReg, err = regexp.Compile(`(v\d.+\d+.\d+)|(\d.+\d+.\d+)`)
36 | if err == nil {
37 | for _, line := range strings.Split(output, "\n") {
38 | line = verReg.FindString(line)
39 | if line == "" {
40 | continue
41 | }
42 |
43 | semVersion, err = semver.ParseTolerant(line)
44 | if err == nil {
45 | break
46 | }
47 | }
48 | }
49 | return
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/version/parser_test.go:
--------------------------------------------------------------------------------
1 | package version_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/linuxsuren/http-downloader/pkg/version"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestGetVersion(t *testing.T) {
11 | tests := []struct {
12 | name string
13 | output string
14 | expectVer string
15 | }{{
16 | name: "invalid",
17 | output: "abc",
18 | expectVer: "0.0.0",
19 | }, {
20 | name: "normal",
21 | output: `minikube version: v1.28.0
22 | commit: 986b1ebd987211ed16f8cc10aed7d2c42fc8392f`,
23 | expectVer: "1.28.0",
24 | }, {
25 | name: "complex",
26 | output: `
27 | ____ __.________
28 | | |/ _/ __ \______
29 | | < \____ / ___/
30 | | | \ / /\___ \
31 | |____|__ \ /____//____ >
32 | \/ \/
33 |
34 | Version: v0.26.3
35 | Commit: 0893f13b3ca6b563dd0c38fdebaefdb8be594825
36 | Date: 2022-08-04T05:18:24Z`,
37 | expectVer: "0.26.3",
38 | }, {
39 | name: "without prefix v",
40 | output: "git version 2.34.1",
41 | expectVer: "2.34.1",
42 | }}
43 | for _, tt := range tests {
44 | t.Run(tt.name, func(t *testing.T) {
45 | result := version.GetVersion(tt.output)
46 | assert.Equal(t, tt.expectVer, result)
47 | })
48 | }
49 | }
50 |
51 | func TestGreatThan(t *testing.T) {
52 | tests := []struct {
53 | name string
54 | target string
55 | output string
56 | expect bool
57 | }{{
58 | name: "normal",
59 | target: "v1.28.1",
60 | output: `minikube version: v1.28.0
61 | commit: 986b1ebd987211ed16f8cc10aed7d2c42fc8392f`,
62 | expect: true,
63 | }, {
64 | name: "normal",
65 | target: "v1.28.0",
66 | output: `minikube version: v1.28.1
67 | commit: 986b1ebd987211ed16f8cc10aed7d2c42fc8392f`,
68 | expect: false,
69 | }, {
70 | name: "version is equal",
71 | target: "v1.28.0",
72 | output: `minikube version: v1.28.0
73 | commit: 986b1ebd987211ed16f8cc10aed7d2c42fc8392f`,
74 | expect: false,
75 | }}
76 | for _, tt := range tests {
77 | t.Run(tt.name, func(t *testing.T) {
78 | result := version.GreatThan(tt.target, tt.output)
79 | assert.Equal(t, tt.expect, result)
80 | })
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/wix.json:
--------------------------------------------------------------------------------
1 | {
2 | "product": "HTTP downloader",
3 | "company": "LinuxSuRen",
4 | "license": "LICENSE",
5 | "upgrade-code": "7c0a5736-5b8e-4176-b350-613fa2d8a1b3",
6 | "files": {
7 | "guid": "6e6dcb19-3cf6-46d1-ac56-c6fb39485c9d",
8 | "items": [
9 | "hd.exe"
10 | ]
11 | },
12 | "env": {
13 | "guid": "94faac3d-4478-431c-8497-fba55dcfb249",
14 | "vars": [
15 | {
16 | "name": "PATH",
17 | "value": "[INSTALLDIR]",
18 | "permanent": "yes",
19 | "system": "no",
20 | "action": "set",
21 | "part": "last"
22 | },
23 | {
24 | "name": "PATH",
25 | "value": "C:\\Program Files (x86)\\Common Files",
26 | "permanent": "yes",
27 | "system": "no",
28 | "action": "set",
29 | "part": "last"
30 | }
31 | ]
32 | },
33 | "shortcuts": {}
34 | }
35 |
--------------------------------------------------------------------------------