Compare commits

..

82 Commits

Author SHA1 Message Date
Dane Everitt
6e74123c65 Update CHANGELOG.md 2021-01-06 21:42:09 -08:00
Dane Everitt
b82f5f9a32 [security] deny downloading files from internal locations 2021-01-06 21:34:18 -08:00
Dane Everitt
1937d0366d cleanup; fix environment stats not reporting network TX correctly 2021-01-06 20:47:44 -08:00
Dane Everitt
963a906c30 Less obtuse logic for polling resource usage when attaching a container 2021-01-06 20:36:29 -08:00
Jakob
3f6eb7e41a no need for additional decode (#81)
file paths used to be url-encoded twice, which is no longer the case.
2021-01-03 17:20:16 -08:00
Omar Kamel
a822c7c340 typo in docker-compose file (#82)
minor typo i noticed while messing around
2021-01-03 16:24:28 -08:00
Matthew Penner
b8fb86f5a4 Update Dockerfile to use busybox 1.33.0 2021-01-03 12:46:06 -07:00
Matthew Penner
ee0c7f09b3 Fix user problems when running inside of Docker 2021-01-02 12:58:58 -07:00
Matthew Penner
d3ddf8cf39 Mark server as not transferring after archive failure 2021-01-02 10:11:25 -07:00
Dane Everitt
bf554e8ed2 Update CHANGELOG.md 2020-12-30 18:24:37 -08:00
Dane Everitt
d6e189df5e Update commentary 2020-12-27 16:53:40 -08:00
Dane Everitt
68749616ad Ignore symlink errors 2020-12-27 16:30:00 -08:00
Dane Everitt
7549eb13a0 Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-12-27 16:17:17 -08:00
Dane Everitt
902f9f5944 Include more (and customizable) context with backup logs 2020-12-27 16:16:40 -08:00
Matthew Penner
2cf24994d9 Uncomment important code 2020-12-27 13:56:45 -07:00
Matthew Penner
3a7c4822f8 Improve logged stacktraces 2020-12-27 13:55:58 -07:00
Matthew Penner
b26db99ee7 Fix part_id field in logs 2020-12-27 13:02:50 -07:00
Matthew Penner
de4d2f4724 Make logs follow a more consistent format 2020-12-27 13:01:34 -07:00
Matthew Penner
640e30de8a Ignore symlinks with missing target, add better backup logging, update copyright year to 2021 2020-12-27 12:54:18 -07:00
Matthew Penner
e75118e0f0 Maybe this will fix shit? 2020-12-27 12:21:26 -07:00
Matthew Penner
a4c8b8714b Bump all dependencies 2020-12-27 11:49:08 -07:00
Matthew Penner
ddb683efb6 Remove UploadID field from backups (#80) 2020-12-26 12:27:52 -08:00
Matthew Penner
5be6e20b03 Send archive status request before sending the transfer status event 2020-12-26 11:42:44 -07:00
Dane Everitt
1ba3631cc1 Don't follow if there is an error 2020-12-25 17:09:35 -08:00
Dane Everitt
94d41bc1f5 Use the system.ScanReader function to replace this logic 2020-12-25 17:07:57 -08:00
Dane Everitt
a4c68eed16 Use sync.Once here to instantiate the event handler 2020-12-25 17:07:40 -08:00
Dane Everitt
22c53c365a Fix installer to not explode on long lines; closes pterodactyl/panel#2773 2020-12-25 17:05:01 -08:00
Dane Everitt
59c30c2842 Fix use of atomics in codebase 2020-12-25 17:04:18 -08:00
Dane Everitt
3842f054a5 These need spaces? 2020-12-25 15:14:21 -08:00
Dane Everitt
8fa4c50379 Fix analysis file per Github rec 2020-12-25 15:10:31 -08:00
Dane Everitt
510d46289b security: don't reveal wings version in remote API calls 2020-12-25 15:06:17 -08:00
Dane Everitt
6e5b14c466 Prevent malicious code from causing a server UUID to be used that is not valid 2020-12-25 15:01:43 -08:00
Dane Everitt
b3922864f2 Simplify origin checking 2020-12-25 14:40:28 -08:00
Dane Everitt
31d4c1d34f Close the file when done 2020-12-25 14:01:25 -08:00
Dane Everitt
0cdfdc725c Cleanup ignored file reader 2020-12-25 14:00:01 -08:00
Dane Everitt
bb132243ed Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-12-25 13:57:33 -08:00
Dane Everitt
d96115325a Add file helper 2020-12-25 13:57:26 -08:00
Matthew Penner
a450abc080 Don't read directories either, add completion log for transfers 2020-12-25 14:46:33 -07:00
Matthew Penner
0a45ea44a4 Return a user-friendly error for an invalid file mode 2020-12-25 14:41:38 -07:00
Matthew Penner
e05c601325 Lets not read an entire file into memory 2020-12-25 14:40:53 -07:00
Matthew Penner
5c78cb9ab3 Add transfer logging output (#77)
Co-authored-by: Dane Everitt <dane@daneeveritt.com>
2020-12-25 13:32:41 -08:00
Matthew Penner
901ab1157d Rework archiving logic to be more consistent and less impactful on disk IO (#79)
Co-authored-by: Dane Everitt <dane@daneeveritt.com>
2020-12-25 11:52:57 -08:00
Dane Everitt
c0523df696 Add a server context that gets canceled when a server is deleted 2020-12-25 11:21:09 -08:00
Dane Everitt
f7f5623c71 Set UA and limit simultaneous downloads 2020-12-24 09:01:00 -08:00
Dane Everitt
184013b652 Include endpoints for getting active downloads for a server 2020-12-20 13:14:07 -08:00
Dane Everitt
f8282c56cb Avoid race conditions from mismatched use of atomic & sync.Mutex 2020-12-20 13:13:10 -08:00
Dane Everitt
c8d297a056 Support canceling an in-progress download 2020-12-20 12:53:40 -08:00
Dane Everitt
c718da20e3 Check disk space before trying a write from the downloader; don't make empty directories if we can't even write the file 2020-12-20 11:17:53 -08:00
Dane Everitt
9c53436470 Check disk before even trying to run the download 2020-12-20 11:08:01 -08:00
Dane Everitt
17daa2071f Update base logic for handling server file downloads 2020-12-20 10:59:07 -08:00
Dane Everitt
3495fb1c76 Less racey logic for resource usage 2020-12-16 22:03:35 -08:00
Dane Everitt
0c93e5ed02 Properly handle decoding paths 2020-12-16 21:38:56 -08:00
Dane Everitt
737e1fcef6 Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-12-16 20:21:24 -08:00
Dane Everitt
16118874cf Don't obliterate the lock 2020-12-16 20:21:22 -08:00
Matthew Penner
b161ccafaf Remove 'arm' build from release workflow 2020-12-16 08:43:55 -07:00
Dane Everitt
59a111de39 Invite our lord and savior emperror back to the table 2020-12-15 21:56:53 -08:00
Dane Everitt
c0a641247b Different handling of errors on routes; push towards using middleware 2020-12-15 21:53:34 -08:00
Dane Everitt
057cdbd927 Use the underlying error 2020-12-15 21:14:20 -08:00
Dane Everitt
0ecc166dcd Update error reporting middleware 2020-12-15 21:08:00 -08:00
Dane Everitt
3a26a5d39d Convert all filesystem error types into the same Error struct 2020-12-15 20:51:13 -08:00
Dane Everitt
9ae75a399b Update middleware.go 2020-12-15 20:20:47 -08:00
Dane Everitt
01b766dacc Remove dupe 2020-12-15 20:20:22 -08:00
Dane Everitt
904e0a574d Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-12-15 20:20:16 -08:00
Dane Everitt
acd6dc62d0 Update middleware logic 2020-12-15 20:19:09 -08:00
Caleb
8f26c31df6 Support downloading remote files to a server via the API
Co-authored-by: Dane Everitt <dane@daneeveritt.com>
2020-12-15 19:59:18 -08:00
Matthew Penner
84c05efaa5 Update Dockerfile 2020-12-15 16:45:40 -07:00
Matthew Penner
d72d96f9d0 😊 is for me? 2020-12-15 16:24:15 -07:00
Matthew Penner
981f04fbd8 PauseChamp 2020-12-15 16:15:54 -07:00
Matthew Penner
463dd6f4ec ( .. •˘___˘• .. ) 2020-12-15 16:08:06 -07:00
Matthew Penner
02034211c1 Update GitHub Actions workflows 2020-12-15 15:59:06 -07:00
Matthew Penner
fafda283b1 Update Dockerfile, add .dockerignore 2020-12-15 15:58:47 -07:00
Dane Everitt
199be20717 Use custom flag for ignoring certificate errors, not the debug flag 2020-12-12 09:56:01 -08:00
Dane Everitt
1c825d2a74 Merge pull request #75 from BrainStone/patch-1
Explictily disable log compression
2020-12-12 09:21:53 -08:00
Matthew Penner
e936f22419 Tweaks to server transfer logging 2020-12-11 17:24:35 -07:00
Yannick Schinko
c253a4bac0 Explictily disable log compression
Log compression is only relevant when `max-file` is 2 or higher.

Older versions of docker (Docker version 19.03.14, build 5eb3275d40 for example (currently the latest version on Debian 9)) error out when compression is enabled in the ``/etc/docker/daemon.json`` file:

```
Error response from daemon: failed to initialize logging driver: compress cannot be true when max-file is less than 2 or max-size is not set
```

While newer docker versions just don't care setting this explicitly to false like in ``intsall.go``, it doesn't hurt being explicit here:

de51fd1c51/server/install.go (L481-L485)
2020-12-12 00:51:11 +01:00
Dane Everitt
ecb15a224a Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-12-08 21:24:33 -08:00
Dane Everitt
73f1399e47 Don't expect double-encoded values here 2020-12-08 21:24:31 -08:00
Matthew Penner
8e29ffed50 Add configurable disk write speed limit for backups (#74)
* Add configurable disk write speed limit for backups
2020-12-08 09:13:48 -07:00
Matthew Penner
2d4dd05ec9 Remove arm from build-test workflow 2020-12-08 09:12:45 -07:00
Matthew Penner
fb3460f5f6 Fix build-test workflow 2020-12-08 09:12:16 -07:00
Matthew Penner
cff7667155 fix build-test workflow running twice on pull_requests 2020-12-08 09:11:15 -07:00
Dane Everitt
7871c0928f Remove --brute; hella slow and not much of a difference in the end 2020-12-06 15:57:03 -08:00
76 changed files with 2546 additions and 1613 deletions

56
.dockerignore Normal file
View File

@@ -0,0 +1,56 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
.idea/*
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
# dep related files and folders
/vendor*
# ignore logfiles (/* so the .gitkeep override works)
/logs/*
# ignore configuration file
/config.yml
# Ignore Vagrant stuff
/.vagrant
# Builds by gox
/build
# Go Coverage tool
/coverage.out
# The built executable
wings
wings.exe
# IDE/Editor files (VS Code)
/.vscode
# test files
test_*/
# Keep all gitkeep files (This needs to stay at the bottom)
!.gitkeep
debug
.DS_Store
*.pprof
*.pdf
Dockerfile
CHANGELOG.md
Makefile
README.md
wings-api.paw

View File

@@ -1,28 +1,33 @@
name: Run Tests name: Run Tests
on: on:
push: push:
branches-ignore: branches:
- 'master' - 'develop'
- 'release/**'
pull_request: pull_request:
branches:
- 'develop'
jobs: jobs:
build: build:
strategy: strategy:
# Default is true, cancels jobs for other platforms in the matrix if one fails
fail-fast: false fail-fast: false
matrix: matrix:
os: [ ubuntu-20.04 ] os: [ ubuntu-20.04 ]
go: [ 1.15.6 ] go: [ 1.15.6 ]
goos: [ linux ] goos: [ linux ]
goarch: [ amd64, arm, arm64 ] goarch: [ amd64, arm64 ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - name: Code Checkout
uses: actions/checkout@v2
- uses: actions/setup-go@v2 - name: Setup Go v${{ matrix.go }}
uses: actions/setup-go@v2
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
@@ -60,22 +65,14 @@ jobs:
SRC_PATH: github.com/pterodactyl/wings SRC_PATH: github.com/pterodactyl/wings
run: | run: |
go build -v -trimpath -ldflags="-s -w -X ${SRC_PATH}/system.Version=dev-${GIT_COMMIT:0:7}" -o build/wings_${{ matrix.goos }}_${{ matrix.goarch }} wings.go go build -v -trimpath -ldflags="-s -w -X ${SRC_PATH}/system.Version=dev-${GIT_COMMIT:0:7}" -o build/wings_${{ matrix.goos }}_${{ matrix.goarch }} wings.go
upx build/wings_${{ matrix.goos }}_${{ matrix.goarch }}
chmod +x build/wings_${{ matrix.goos }}_${{ matrix.goarch }}
- name: Test - name: Test
run: go test ./... run: go test ./...
- name: Compress binary and make it executable - name: Upload Artifact
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }} uses: actions/upload-artifact@v2
run: |
upx build/wings_${{ matrix.goos }}_${{ matrix.goarch }} && chmod +x build/wings_${{ matrix.goos }}_${{ matrix.goarch }}
- uses: actions/upload-artifact@v2
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }}
with:
name: wings_${{ matrix.goos }}_${{ matrix.goarch }}
path: build/wings_${{ matrix.goos }}_${{ matrix.goarch }}
- uses: actions/upload-artifact@v2
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }} if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }}
with: with:
name: wings_${{ matrix.goos }}_${{ matrix.goarch }} name: wings_${{ matrix.goos }}_${{ matrix.goarch }}

View File

@@ -1,30 +1,32 @@
name: CodeQL Scanning name: CodeQL
on: on:
push: push:
branches: branches:
- 'develop' - 'develop'
pull_request: pull_request:
branches:
- 'develop'
schedule:
- cron: '0 9 * * 4'
jobs: jobs:
CodeQL-Build: analyze:
runs-on: ubuntu-latest name: Analyze
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
language:
- go
steps: steps:
- name: Checkout repository - name: Code Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
with: - name: Checkout Head
# We must fetch at least the immediate parents so that if this is run: git checkout HEAD^2
# a pull request then we can checkout the head. if: ${{ github.event_name == 'pull_request' }}
fetch-depth: 2 - name: Initialize CodeQL
# If this run was triggered by a pull request event, then checkout uses: github/codeql-action/init@v1
# the head of the pull request instead of the merge commit. with:
- run: git checkout HEAD^2 languages: ${{ matrix.language }}
if: ${{ github.event_name == 'pull_request' }} - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
# Override language selection by uncommenting this and choosing your languages
with:
languages: go
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -1,47 +1,68 @@
name: Publish Docker Image name: Publish Docker Image
on: on:
push: push:
branches: branches:
- 'develop' - 'develop'
tags: tags:
- 'v*' - 'v*'
jobs: jobs:
push_to_registry: push:
name: Push Image to GitHub Packages name: Push
runs-on: ubuntu-latest runs-on: ubuntu-20.04
# Always run against a tag, even if the commit into the tag has [docker skip]
# within the commit message. # Always run against a tag, even if the commit into the tag has [docker skip] within the commit message.
if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))" if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
steps: steps:
- uses: actions/checkout@v2 - name: Code Checkout
- uses: crazy-max/ghaction-docker-meta@v1 uses: actions/checkout@v2
- name: Docker Meta
id: docker_meta id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with: with:
images: ghcr.io/pterodactyl/wings images: ghcr.io/pterodactyl/wings
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1 - name: Set up QEMU
- uses: docker/login-action@v1 uses: docker/setup-qemu-action@v1
- name: Install buildx
uses: docker/setup-buildx-action@v1
with:
version: v0.5.1
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.REGISTRY_TOKEN }} password: ${{ secrets.REGISTRY_TOKEN }}
- name: Get Build Information
id: build_info
run: |
echo "::set-output name=version_tag::${GITHUB_REF/refs\/tags\//}"
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
- name: Release Production Build - name: Release Production Build
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
if: "!contains(github.ref, 'develop')" if: "!contains(github.ref, 'develop')"
env:
REF: ${{ github.ref }}
with: with:
push: true
build-args: | build-args: |
VERSION=${REF:11} VERSION=${{ steps.build_info.outputs.version_tag }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.docker_meta.outputs.labels }}
push: true
tags: ${{ steps.docker_meta.outputs.tags }}
- name: Release Development Build - name: Release Development Build
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
if: "contains(github.ref, 'develop')" if: "contains(github.ref, 'develop')"
with: with:
push: ${{ github.event_name != 'pull_request' }}
build-args: | build-args: |
VERSION=dev-${GIT_COMMIT:0:7} VERSION=dev-${{ steps.build_info.outputs.short_sha }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.docker_meta.outputs.labels }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}

View File

@@ -1,18 +1,21 @@
name: Create Release name: Create Release
on: on:
push: push:
tags: tags:
- 'v*' - 'v*'
jobs: jobs:
release: release:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - name: Code Checkout
uses: actions/checkout@v2
- uses: actions/setup-go@v2 - uses: actions/setup-go@v2
with: with:
go-version: '1.15.2' go-version: '1.15.6'
- name: Build - name: Build
env: env:
@@ -20,16 +23,14 @@ jobs:
run: | run: |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_amd64 -v wings.go CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_amd64 -v wings.go
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_arm64 -v wings.go CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_arm64 -v wings.go
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_arm -v wings.go
- name: Test - name: Test
run: go test ./... run: go test ./...
- name: Compress binary and make it executable - name: Compress binary and make it executable
run: | run: |
upx --brute build/wings_linux_amd64 && chmod +x build/wings_linux_amd64 upx build/wings_linux_amd64 && chmod +x build/wings_linux_amd64
upx build/wings_linux_arm64 && chmod +x build/wings_linux_arm64 upx build/wings_linux_arm64 && chmod +x build/wings_linux_arm64
upx build/wings_linux_arm && chmod +x build/wings_linux_arm
- name: Extract changelog - name: Extract changelog
env: env:
@@ -42,9 +43,8 @@ jobs:
run: | run: |
SUM=`cd build && sha256sum wings_linux_amd64` SUM=`cd build && sha256sum wings_linux_amd64`
SUM2=`cd build && sha256sum wings_linux_arm64` SUM2=`cd build && sha256sum wings_linux_arm64`
SUM3=`cd build && sha256sum wings_linux_arm` echo -e "\n#### SHA256 Checksum\n\`\`\`\n$SUM\n$SUM2\n\`\`\`\n" >> ./RELEASE_CHANGELOG
echo -e "\n#### SHA256 Checksum\n\`\`\`\n$SUM\n$SUM2\n$SUM3\n\`\`\`\n" >> ./RELEASE_CHANGELOG echo -e "$SUM\n$SUM2" > checksums.txt
echo -e "$SUM\n$SUM2\n$SUM3" > checksums.txt
- name: Create release branch - name: Create release branch
env: env:
@@ -92,16 +92,6 @@ jobs:
asset_name: wings_linux_arm64 asset_name: wings_linux_arm64
asset_content_type: application/octet-stream asset_content_type: application/octet-stream
- name: Upload arm Binary
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: build/wings_linux_arm
asset_name: wings_linux_arm
asset_content_type: application/octet-stream
- name: Upload checksum - name: Upload checksum
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
env: env:

1
.gitignore vendored
View File

@@ -45,7 +45,6 @@ test_*/
# Keep all gitkeep files (This needs to stay at the bottom) # Keep all gitkeep files (This needs to stay at the bottom)
!.gitkeep !.gitkeep
debug debug
data/.states.json
.DS_Store .DS_Store
*.pprof *.pprof
*.pdf *.pdf

View File

@@ -1,5 +1,34 @@
# Changelog # Changelog
## v1.2.1
### Fixed
* Fixes servers not be properly marked as no longer transfering if an error occurs during the archive process.
* Fixes problems with user detection when running Wings inside a Docker container.
* Fixes filename decoding issues with multiple endpoints related to the file manager (namely move/copy/delete).
* **[Security]** Fixes vulnerability allowing a malicious user to abuse the remote file download utilitity to scan or access resources on the local network.
* Fixes network `tx` stats not correctly being reported (was previously reporting `rx` for both `rx` and `tx`).
### Changed
* Cleans up the logic related to polling resources for the server to make a little more sense and not do pointless `io.Copy()` operations.
## v1.2.0
### Fixed
* Fixes log compression being set on the Docker containers being created to avoid errors on some versions of Docker.
* Cleaned up logic handling server resource usage to avoid race conditions in the future and make the logic simpler.
* Fixes directories being created when writing a file before checking if there was space for the file to even be written to the disk.
* Significant performance and resource usage fixes to backups and server transfers to avoid obliterating machine `i/o` and causing excessive resource exhaustion on busy systems or low end machines.
* Fixes server install process to not unintentionally exit and cause invalid states if a line during the install process was too long.
* Fixes symlink error handling in backups to not unexpectedly tank a request. Any errors due to a symlink are now ignored and will not impact the generation of a backup (including for server transfers).
### Changed
* Changed `--debug` flag to no longer ignore certificate errors on requests. Use `--ignore-certificate-errors` to ignore any certificate errors encountered when in development environments.
* Changed all Filesystem related errors to be of the same internal error type making error checking significantly easier and less error prone.
* Improves log output stacktraces to be more accurate as to the source of the issue.
### Added
* Adds support for downloading files to a server's data directory and optionally checking the status of or canceling in-progress downloads.
* Adds a `context.Context` to `server.Server` structs allowing for cancelation of long running background tasks when a server is deleted without additional complexity on developer's end.
## v1.1.3 ## v1.1.3
### Fixed ### Fixed
* Fixes `archive/tar: write too long` error when creating a server backup. * Fixes `archive/tar: write too long` error when creating a server backup.

View File

@@ -1,11 +1,33 @@
FROM golang:1.15-alpine # Stage 1 (Build)
ARG VERSION="develop" FROM golang:1.15-alpine3.12 AS builder
COPY . /go/wings/
WORKDIR /go/wings/
RUN apk add --no-cache upx \
&& CGO_ENABLED=0 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${VERSION}" \
&& upx wings
FROM alpine:latest ARG VERSION
COPY --from=0 /go/wings/wings /usr/bin/
CMD ["wings", "--config", "/etc/pterodactyl/config.yml"] RUN apk add --update --no-cache git=2.26.2-r0 make=4.3-r0 upx=3.96-r0
WORKDIR /app/
COPY go.mod go.sum /app/
RUN go mod download
COPY . /app/
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=$VERSION" \
-v \
-trimpath \
-o wings \
wings.go
RUN upx wings
# --------------------------------------- #
# Stage 2 (Final)
FROM busybox:1.33.0
RUN echo "ID=\"busybox\"" > /etc/os-release
COPY --from=builder /app/wings /usr/bin/
CMD [ "wings", "--config", "/etc/pterodactyl/config.yml" ]

View File

@@ -2,6 +2,10 @@ build:
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_linux_amd64 -v wings.go GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_linux_amd64 -v wings.go
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_linux_arm64 -v wings.go GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_linux_arm64 -v wings.go
debug:
go build -race
./wings --debug --ignore-certificate-errors --config config.yml
compress: compress:
upx --brute build/wings_* upx --brute build/wings_*

View File

@@ -2,10 +2,10 @@ package api
import ( import (
"bytes" "bytes"
"emperror.dev/errors"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/apex/log" "github.com/apex/log"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
"io" "io"

View File

@@ -2,16 +2,10 @@ package api
import ( import (
"fmt" "fmt"
"github.com/patrickmn/go-cache"
"strconv" "strconv"
"time"
) )
// backupUploadIDs stores a cache of active S3 backups.
var backupUploadIDs = cache.New(time.Hour*3, time.Minute*5)
type BackupRemoteUploadResponse struct { type BackupRemoteUploadResponse struct {
UploadID string `json:"upload_id"`
Parts []string `json:"parts"` Parts []string `json:"parts"`
PartSize int64 `json:"part_size"` PartSize int64 `json:"part_size"`
} }
@@ -32,16 +26,10 @@ func (r *Request) GetBackupRemoteUploadURLs(backup string, size int64) (*BackupR
return nil, err return nil, err
} }
// Store the backup upload id for later use, this is a janky way to be able to use it later with SendBackupStatus.
// Yes, the timeout of 3 hours is intentional, if this value is removed before the backup completes,
// the backup will fail even if it uploaded properly.
backupUploadIDs.Set(backup, res.UploadID, 0)
return &res, nil return &res, nil
} }
type BackupRequest struct { type BackupRequest struct {
UploadID string `json:"upload_id"`
Checksum string `json:"checksum"` Checksum string `json:"checksum"`
ChecksumType string `json:"checksum_type"` ChecksumType string `json:"checksum_type"`
Size int64 `json:"size"` Size int64 `json:"size"`
@@ -51,11 +39,6 @@ type BackupRequest struct {
// Notifies the panel that a specific backup has been completed and is now // Notifies the panel that a specific backup has been completed and is now
// available for a user to view and download. // available for a user to view and download.
func (r *Request) SendBackupStatus(backup string, data BackupRequest) error { func (r *Request) SendBackupStatus(backup string, data BackupRequest) error {
// Set the UploadID on the data.
if v, ok := backupUploadIDs.Get(backup); ok {
data.UploadID = v.(string)
}
resp, err := r.Post(fmt.Sprintf("/backups/%s", backup), data) resp, err := r.Post(fmt.Sprintf("/backups/%s", backup), data)
if err != nil { if err != nil {
return err return err

View File

@@ -189,22 +189,15 @@ func (r *Request) SendArchiveStatus(uuid string, successful bool) error {
return resp.Error() return resp.Error()
} }
func (r *Request) SendTransferFailure(uuid string) error { func (r *Request) SendTransferStatus(uuid string, successful bool) error {
resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/failure", uuid), nil) state := "failure"
if successful {
state = "success"
}
resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/%s", uuid, state), nil)
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
return resp.Error()
}
func (r *Request) SendTransferSuccess(uuid string) error {
resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/success", uuid), nil)
if err != nil {
return err
}
defer resp.Body.Close()
return resp.Error() return resp.Error()
} }

View File

@@ -1,8 +1,8 @@
package api package api
import ( import (
"emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/pkg/errors"
"regexp" "regexp"
) )

View File

@@ -2,41 +2,40 @@ package cmd
import ( import (
"crypto/tls" "crypto/tls"
"emperror.dev/errors"
"fmt" "fmt"
"github.com/NYTimes/logrotate" "github.com/NYTimes/logrotate"
"github.com/apex/log"
"github.com/apex/log/handlers/multi" "github.com/apex/log/handlers/multi"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/gammazero/workerpool" "github.com/gammazero/workerpool"
"golang.org/x/crypto/acme"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"github.com/apex/log"
"github.com/mitchellh/colorstring" "github.com/mitchellh/colorstring"
"github.com/pterodactyl/wings/loggers/cli"
"golang.org/x/crypto/acme/autocert"
"github.com/pkg/errors"
"github.com/pkg/profile" "github.com/pkg/profile"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/loggers/cli"
"github.com/pterodactyl/wings/router" "github.com/pterodactyl/wings/router"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/sftp" "github.com/pterodactyl/wings/sftp"
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
"net/http"
"os"
"path"
"path/filepath"
"strings"
) )
var ( var (
profiler = "" profiler = ""
configPath = config.DefaultLocation configPath = config.DefaultLocation
debug = false debug = false
useAutomaticTls = false useAutomaticTls = false
tlsHostname = "" tlsHostname = ""
showVersion = false showVersion = false
ignoreCertificateErrors = false
) )
var root = &cobra.Command{ var root = &cobra.Command{
@@ -59,6 +58,7 @@ func init() {
root.PersistentFlags().StringVar(&profiler, "profiler", "", "the profiler to run for this instance") root.PersistentFlags().StringVar(&profiler, "profiler", "", "the profiler to run for this instance")
root.PersistentFlags().BoolVar(&useAutomaticTls, "auto-tls", false, "pass in order to have wings generate and manage it's own SSL certificates using Let's Encrypt") root.PersistentFlags().BoolVar(&useAutomaticTls, "auto-tls", false, "pass in order to have wings generate and manage it's own SSL certificates using Let's Encrypt")
root.PersistentFlags().StringVar(&tlsHostname, "tls-hostname", "", "required with --auto-tls, the FQDN for the generated SSL certificate") root.PersistentFlags().StringVar(&tlsHostname, "tls-hostname", "", "required with --auto-tls, the FQDN for the generated SSL certificate")
root.PersistentFlags().BoolVar(&ignoreCertificateErrors, "ignore-certificate-errors", false, "if passed any SSL certificate errors will be ignored by wings")
root.AddCommand(configureCmd) root.AddCommand(configureCmd)
root.AddCommand(diagnosticsCmd) root.AddCommand(diagnosticsCmd)
@@ -66,7 +66,7 @@ func init() {
// Get the configuration path based on the arguments provided. // Get the configuration path based on the arguments provided.
func readConfiguration() (*config.Configuration, error) { func readConfiguration() (*config.Configuration, error) {
var p = configPath p := configPath
if !strings.HasPrefix(p, "/") { if !strings.HasPrefix(p, "/") {
d, err := os.Getwd() d, err := os.Getwd()
if err != nil { if err != nil {
@@ -139,8 +139,10 @@ func rootCmdRun(*cobra.Command, []string) {
log.WithField("path", c.GetPath()).Info("loading configuration from path") log.WithField("path", c.GetPath()).Info("loading configuration from path")
if c.Debug { if c.Debug {
log.Debug("running in debug mode") log.Debug("running in debug mode")
log.Warn("certificate checking is disabled") }
if ignoreCertificateErrors {
log.Warn("running with --ignore-certificate-errors: TLS certificate host chains and name will not be verified")
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: true,
} }
@@ -354,6 +356,12 @@ func rootCmdRun(*cobra.Command, []string) {
if err := s.ListenAndServe(); err != nil { if err := s.ListenAndServe(); err != nil {
log.WithField("error", err).Fatal("failed to configure HTTP server") log.WithField("error", err).Fatal("failed to configure HTTP server")
} }
// Cancel the context on all of the running servers at this point, even though the
// program is just shutting down.
for _, s := range server.GetServers().All() {
s.CtxCancel()
}
} }
// Execute calls cobra to handle cli commands // Execute calls cobra to handle cli commands
@@ -400,7 +408,7 @@ __ [blue][bold]Pterodactyl[reset] _____/___/_______ _______ ______
\___/\___/___/___/___/___ /______/ \___/\___/___/___/___/___ /______/
/_______/ [bold]v%s[reset] /_______/ [bold]v%s[reset]
Copyright © 2018 - 2020 Dane Everitt & Contributors Copyright © 2018 - 2021 Dane Everitt & Contributors
Website: https://pterodactyl.io Website: https://pterodactyl.io
Source: https://github.com/pterodactyl/wings Source: https://github.com/pterodactyl/wings

View File

@@ -1,11 +1,11 @@
package config package config
import ( import (
"emperror.dev/errors"
"fmt" "fmt"
"github.com/cobaugh/osrelease" "github.com/cobaugh/osrelease"
"github.com/creasty/defaults" "github.com/creasty/defaults"
"github.com/gbrlsnchs/jwt/v3" "github.com/gbrlsnchs/jwt/v3"
"github.com/pkg/errors"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -223,6 +223,36 @@ func (c *Configuration) GetPath() string {
// If files are not owned by this user there will be issues with permissions on Docker // If files are not owned by this user there will be issues with permissions on Docker
// mount points. // mount points.
func (c *Configuration) EnsurePterodactylUser() (*user.User, error) { func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
sysName, err := getSystemName()
if err != nil {
return nil, err
}
// Our way of detecting if wings is running inside of Docker.
if sysName == "busybox" {
uid := os.Getenv("WINGS_UID")
if uid == "" {
uid = "988"
}
gid := os.Getenv("WINGS_GID")
if gid == "" {
gid = "988"
}
username := os.Getenv("WINGS_USERNAME")
if username == "" {
username = "pterodactyl"
}
u := &user.User{
Uid: uid,
Gid: gid,
Username: username,
}
return u, c.setSystemUser(u)
}
u, err := user.Lookup(c.System.Username) u, err := user.Lookup(c.System.Username)
// If an error is returned but it isn't the unknown user error just abort // If an error is returned but it isn't the unknown user error just abort
@@ -233,17 +263,12 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
return nil, err return nil, err
} }
sysName, err := getSystemName() command := fmt.Sprintf("useradd --system --no-create-home --shell /usr/sbin/nologin %s", c.System.Username)
if err != nil {
return nil, err
}
var command = fmt.Sprintf("useradd --system --no-create-home --shell /bin/false %s", c.System.Username)
// Alpine Linux is the only OS we currently support that doesn't work with the useradd command, so // Alpine Linux is the only OS we currently support that doesn't work with the useradd command, so
// in those cases we just modify the command a bit to work as expected. // in those cases we just modify the command a bit to work as expected.
if strings.HasPrefix(sysName, "alpine") { if strings.HasPrefix(sysName, "alpine") {
command = fmt.Sprintf("adduser -S -D -H -G %[1]s -s /bin/false %[1]s", c.System.Username) command = fmt.Sprintf("adduser -S -D -H -G %[1]s -s /sbin/nologin %[1]s", c.System.Username)
// We have to create the group first on Alpine, so do that here before continuing on // We have to create the group first on Alpine, so do that here before continuing on
// to the user creation process. // to the user creation process.
@@ -267,8 +292,15 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
// Set the system user into the configuration and then write it to the disk so that // Set the system user into the configuration and then write it to the disk so that
// it is persisted on boot. // it is persisted on boot.
func (c *Configuration) setSystemUser(u *user.User) error { func (c *Configuration) setSystemUser(u *user.User) error {
uid, _ := strconv.Atoi(u.Uid) uid, err := strconv.Atoi(u.Uid)
gid, _ := strconv.Atoi(u.Gid) if err != nil {
return err
}
gid, err := strconv.Atoi(u.Gid)
if err != nil {
return err
}
c.Lock() c.Lock()
c.System.Username = u.Username c.System.Username = u.Username

View File

@@ -2,9 +2,9 @@ package config
import ( import (
"context" "context"
"emperror.dev/errors"
"fmt" "fmt"
"github.com/apex/log" "github.com/apex/log"
"github.com/pkg/errors"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -75,6 +75,10 @@ type SystemConfiguration struct {
Sftp SftpConfiguration `yaml:"sftp"` Sftp SftpConfiguration `yaml:"sftp"`
CrashDetection CrashDetection `yaml:"crash_detection"` CrashDetection CrashDetection `yaml:"crash_detection"`
Backups Backups `yaml:"backups"`
Transfers Transfers `yaml:"transfers"`
} }
type CrashDetection struct { type CrashDetection struct {
@@ -89,6 +93,28 @@ type CrashDetection struct {
Timeout int `default:"60" json:"timeout"` Timeout int `default:"60" json:"timeout"`
} }
type Backups struct {
// WriteLimit imposes a Disk I/O write limit on backups to the disk, this affects all
// backup drivers as the archiver must first write the file to the disk in order to
// upload it to any external storage provider.
//
// If the value is less than 1, the write speed is unlimited,
// if the value is greater than 0, the write speed is the value in MiB/s.
//
// Defaults to 0 (unlimited)
WriteLimit int `default:"0" yaml:"write_limit"`
}
type Transfers struct {
// DownloadLimit imposes a Network I/O read limit when downloading a transfer archive.
//
// If the value is less than 1, the write speed is unlimited,
// if the value is greater than 0, the write speed is the value in MiB/s.
//
// Defaults to 0 (unlimited)
DownloadLimit int `default:"0" yaml:"download_limit"`
}
// Ensures that all of the system directories exist on the system. These directories are // Ensures that all of the system directories exist on the system. These directories are
// created so that only the owner can read the data, and no other users. // created so that only the owner can read the data, and no other users.
func (sc *SystemConfiguration) ConfigureDirectories() error { func (sc *SystemConfiguration) ConfigureDirectories() error {

View File

@@ -22,6 +22,6 @@ type ConsoleThrottles struct {
DecayInterval uint64 `json:"decay_interval" yaml:"decay_interval" default:"10000"` DecayInterval uint64 `json:"decay_interval" yaml:"decay_interval" default:"10000"`
// The amount of time that a server is allowed to be stopping for before it is terminated // The amount of time that a server is allowed to be stopping for before it is terminated
// forfully if it triggers output throttles. // forcefully if it triggers output throttles.
StopGracePeriod uint `json:"stop_grace_period" yaml:"stop_grace_period" default:"15"` StopGracePeriod uint `json:"stop_grace_period" yaml:"stop_grace_period" default:"15"`
} }

View File

@@ -1,4 +1,5 @@
version: '3.8' version: '3.8'
services: services:
wings: wings:
image: ghcr.io/pterodactyl/wings:latest image: ghcr.io/pterodactyl/wings:latest
@@ -11,7 +12,9 @@ services:
tty: true tty: true
environment: environment:
TZ: "UTC" TZ: "UTC"
DEBUG: "false" WINGS_UID: 988
WINGS_GID: 988
WINGS_USERNAME: pterodactyl
volumes: volumes:
- "/var/run/docker.sock:/var/run/docker.sock" - "/var/run/docker.sock:/var/run/docker.sock"
- "/var/lib/docker/containers/:/var/lib/docker/containers/" - "/var/lib/docker/containers/:/var/lib/docker/containers/"
@@ -21,8 +24,9 @@ services:
- "/tmp/pterodactyl/:/tmp/pterodactyl/" - "/tmp/pterodactyl/:/tmp/pterodactyl/"
# you may need /srv/daemon-data if you are upgrading from an old daemon # you may need /srv/daemon-data if you are upgrading from an old daemon
#- "/srv/daemon-data/:/srv/daemon-data/" #- "/srv/daemon-data/:/srv/daemon-data/"
# Required for ssl if you user let's encrypt. uncomment to use. # Required for ssl if you use let's encrypt. uncomment to use.
#- "/etc/letsencrypt/:/etc/letsencrypt/" #- "/etc/letsencrypt/:/etc/letsencrypt/"
networks: networks:
wings0: wings0:
name: wings0 name: wings0

View File

@@ -1,20 +0,0 @@
package docker
import "io"
type Console struct {
HandlerFunc *func(string)
}
var _ io.Writer = Console{}
func (c Console) Write(b []byte) (int, error) {
if c.HandlerFunc != nil {
l := make([]byte, len(b))
copy(l, b)
(*c.HandlerFunc)(string(l))
}
return len(b), nil
}

View File

@@ -2,8 +2,8 @@ package docker
import ( import (
"bufio" "bufio"
"bytes"
"context" "context"
"emperror.dev/errors"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/apex/log" "github.com/apex/log"
@@ -12,9 +12,9 @@ import (
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/docker/daemon/logger/jsonfilelog" "github.com/docker/docker/daemon/logger/jsonfilelog"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/system"
"io" "io"
"strconv" "strconv"
"strings" "strings"
@@ -30,6 +30,10 @@ type imagePullStatus struct {
// of the process stream. This should not be used for reading console data as you *will* // of the process stream. This should not be used for reading console data as you *will*
// miss important output at the beginning because of the time delay with attaching to the // miss important output at the beginning because of the time delay with attaching to the
// output. // output.
//
// Calling this function will poll resources for the container in the background until the
// provided context is canceled by the caller. Failure to cancel said context will cause
// background memory leaks as the goroutine will not exit.
func (e *Environment) Attach() error { func (e *Environment) Attach() error {
if e.IsAttached() { if e.IsAttached() {
return nil return nil
@@ -53,38 +57,47 @@ func (e *Environment) Attach() error {
e.SetStream(&st) e.SetStream(&st)
} }
c := new(Console) go func() {
go func(console *Console) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
defer e.stream.Close()
defer func() { defer func() {
e.stream.Close()
e.SetState(environment.ProcessOfflineState) e.SetState(environment.ProcessOfflineState)
e.SetStream(nil) e.SetStream(nil)
}() }()
// Poll resources in a separate thread since this will block the copy call below go func() {
// from being reached until it is completed if not run in a separate process. However,
// we still want it to be stopped when the copy operation below is finished running which
// indicates that the container is no longer running.
go func(ctx context.Context) {
if err := e.pollResources(ctx); err != nil { if err := e.pollResources(ctx); err != nil {
l := log.WithField("environment_id", e.Id)
if !errors.Is(err, context.Canceled) { if !errors.Is(err, context.Canceled) {
l.WithField("error", err).Error("error during environment resource polling") e.log().WithField("error", err).Error("error during environment resource polling")
} else { } else {
l.Warn("stopping server resource polling: context canceled") e.log().Warn("stopping server resource polling: context canceled")
} }
} }
}(ctx) }()
// Stream the reader output to the console which will then fire off events and handle console // Block the completion of this routine until the container is no longer running. This allows
// throttling and sending the output to the user. // the pollResources function to run until it needs to be stopped. Because the container
if _, err := io.Copy(console, e.stream.Reader); err != nil { // can be polled for resource usage, even when sropped, we need to have this logic present
log.WithField("environment_id", e.Id).WithField("error", err).Error("error while copying environment output to console") // in order to cancel the context and therefore stop the routine that is spawned.
ok, err := e.client.ContainerWait(ctx, e.Id, container.WaitConditionNotRunning)
select {
case <-ctx.Done():
// Do nothing, the context was canceled by a different process, there is no error
// to report at this point.
e.log().Debug("terminating ContainerWait blocking process, context canceled")
return
case _ = <-err:
// An error occurred with the ContainerWait call, report it here and then hope
// for the fucking best I guess?
e.log().WithField("error", err).Error("error while blocking using ContainerWait")
return
case <-ok:
// Do nothing, everything is running as expected. This will allow us to keep
// blocking the termination of this function until the container stops at which
// point all of our deferred functions can run.
} }
}(c) }()
return nil return nil
} }
@@ -212,6 +225,7 @@ func (e *Environment) Create() error {
Config: map[string]string{ Config: map[string]string{
"max-size": "5m", "max-size": "5m",
"max-file": "1", "max-file": "1",
"compress": "false",
}, },
}, },
@@ -224,7 +238,7 @@ func (e *Environment) Create() error {
NetworkMode: container.NetworkMode(config.Get().Docker.Network.Mode), NetworkMode: container.NetworkMode(config.Get().Docker.Network.Mode),
} }
if _, err := e.client.ContainerCreate(context.Background(), conf, hostConf, nil, e.Id); err != nil { if _, err := e.client.ContainerCreate(context.Background(), conf, hostConf, nil, nil, e.Id); err != nil {
return err return err
} }
@@ -279,7 +293,6 @@ func (e *Environment) followOutput() error {
if err != nil { if err != nil {
return err return err
} }
return errors.New(fmt.Sprintf("no such container: %s", e.Id)) return errors.New(fmt.Sprintf("no such container: %s", e.Id))
} }
@@ -291,69 +304,20 @@ func (e *Environment) followOutput() error {
} }
reader, err := e.client.ContainerLogs(context.Background(), e.Id, opts) reader, err := e.client.ContainerLogs(context.Background(), e.Id, opts)
if err != nil {
return err
}
go func(reader io.ReadCloser) { go func(reader io.ReadCloser) {
defer reader.Close() defer reader.Close()
evts := e.Events()
r := bufio.NewReader(reader) err := system.ScanReader(reader, func(line string) {
evts.Publish(environment.ConsoleOutputEvent, line)
// Micro-optimization to create these replacements one time when this routine })
// fires up, rather than on every line that is executed.
cr := []byte(" \r")
crr := []byte("\r\n")
// Avoid constantly re-allocating memory when we're flooding lines through this
// function by using the same buffer for the duration of the call and just truncating
// the value back to 0 every loop.
var str strings.Builder
ParentLoop:
for {
str.Reset()
var line []byte
var isPrefix bool
for {
// Read the line and write it to the buffer.
line, isPrefix, err = r.ReadLine()
// Certain games like Minecraft output absolutely random carriage returns in the output seemingly
// in line with that it thinks is the terminal size. Those returns break a lot of output handling,
// so we'll just replace them with proper new-lines and then split it later and send each line as
// its own event in the response.
str.Write(bytes.Replace(line, cr, crr, -1))
// Finish this loop and begin outputting the line if there is no prefix (the line fit into
// the default buffer), or if we hit the end of the line.
if !isPrefix || err == io.EOF {
break
}
// If we encountered an error with something in ReadLine that was not an EOF just abort
// the entire process here.
if err != nil {
break ParentLoop
}
}
// Publish the line for this loop. Break on new-line characters so every line is sent as a single
// output event, otherwise you get funky handling in the browser console.
for _, line := range strings.Split(str.String(), "\r\n") {
e.Events().Publish(environment.ConsoleOutputEvent, line)
}
// If the error we got previously that lead to the line being output is an io.EOF we want to
// exit the entire looping process.
if err == io.EOF {
break
}
}
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output") log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output")
} }
}(reader) }(reader)
return nil
return err
} }
// Pulls the image from Docker. If there is an error while pulling the image from the source // Pulls the image from Docker. If there is an error while pulling the image from the source

View File

@@ -2,6 +2,7 @@ package docker
import ( import (
"context" "context"
"github.com/apex/log"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
@@ -23,7 +24,7 @@ var _ environment.ProcessEnvironment = (*Environment)(nil)
type Environment struct { type Environment struct {
mu sync.RWMutex mu sync.RWMutex
eventMu sync.Mutex eventMu sync.Once
// The public identifier for this environment. In this case it is the Docker container // The public identifier for this environment. In this case it is the Docker container
// name that will be used for all instances created under it. // name that will be used for all instances created under it.
@@ -70,6 +71,10 @@ func New(id string, m *Metadata, c *environment.Configuration) (*Environment, er
return e, nil return e, nil
} }
func (e *Environment) log() *log.Entry {
return log.WithField("environment", e.Type()).WithField("container_id", e.Id)
}
func (e *Environment) Type() string { func (e *Environment) Type() string {
return "docker" return "docker"
} }
@@ -90,13 +95,9 @@ func (e *Environment) IsAttached() bool {
} }
func (e *Environment) Events() *events.EventBus { func (e *Environment) Events() *events.EventBus {
e.eventMu.Lock() e.eventMu.Do(func() {
defer e.eventMu.Unlock()
if e.emitter == nil {
e.emitter = events.New() e.emitter = events.New()
} })
return e.emitter return e.emitter
} }

View File

@@ -2,11 +2,11 @@ package docker
import ( import (
"context" "context"
"emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"os" "os"
@@ -147,8 +147,8 @@ func (e *Environment) Stop() error {
} }
t := time.Second * 30 t := time.Second * 30
err := e.client.ContainerStop(context.Background(), e.Id, &t)
if err != nil { if err := e.client.ContainerStop(context.Background(), e.Id, &t); err != nil {
// If the container does not exist just mark the process as stopped and return without // If the container does not exist just mark the process as stopped and return without
// an error. // an error.
if client.IsErrNotFound(err) { if client.IsErrNotFound(err) {

View File

@@ -1,8 +1,8 @@
package docker package docker
import ( import (
"emperror.dev/errors"
"fmt" "fmt"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
) )

View File

@@ -2,14 +2,12 @@ package docker
import ( import (
"context" "context"
"emperror.dev/errors"
"encoding/json" "encoding/json"
"github.com/apex/log"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"io" "io"
"math" "math"
"sync/atomic"
) )
// Attach to the instance and then automatically emit an event whenever the resource usage for the // Attach to the instance and then automatically emit an event whenever the resource usage for the
@@ -19,63 +17,51 @@ func (e *Environment) pollResources(ctx context.Context) error {
return errors.New("cannot enable resource polling on a stopped server") return errors.New("cannot enable resource polling on a stopped server")
} }
l := log.WithField("container_id", e.Id) e.log().Info("starting resource polling for container")
l.Debug("starting resource polling for container") defer e.log().Debug("stopped resource polling for container")
defer l.Debug("stopped resource polling for container")
stats, err := e.client.ContainerStats(context.Background(), e.Id, true) stats, err := e.client.ContainerStats(ctx, e.Id, true)
if err != nil { if err != nil {
return err return err
} }
defer stats.Body.Close() defer stats.Body.Close()
dec := json.NewDecoder(stats.Body) dec := json.NewDecoder(stats.Body)
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
default: default:
var v *types.StatsJSON var v types.StatsJSON
if err := dec.Decode(&v); err != nil { if err := dec.Decode(&v); err != nil {
if err != io.EOF { if err != io.EOF && !errors.Is(err, context.Canceled) {
l.WithField("error", err).Warn("error while processing Docker stats output for container") e.log().WithField("error", err).Warn("error while processing Docker stats output for container")
} else { } else {
l.Debug("io.EOF encountered during stats decode, stopping polling...") e.log().Debug("io.EOF encountered during stats decode, stopping polling...")
} }
return nil return nil
} }
// Disable collection if the server is in an offline state and this process is still running. // Disable collection if the server is in an offline state and this process is still running.
if e.st.Load() == environment.ProcessOfflineState { if e.st.Load() == environment.ProcessOfflineState {
l.Debug("process in offline state while resource polling is still active; stopping poll") e.log().Debug("process in offline state while resource polling is still active; stopping poll")
return nil return nil
} }
var rx uint64 st := environment.Stats{
var tx uint64
for _, nw := range v.Networks {
atomic.AddUint64(&rx, nw.RxBytes)
atomic.AddUint64(&tx, nw.RxBytes)
}
st := &environment.Stats{
Memory: calculateDockerMemory(v.MemoryStats), Memory: calculateDockerMemory(v.MemoryStats),
MemoryLimit: v.MemoryStats.Limit, MemoryLimit: v.MemoryStats.Limit,
CpuAbsolute: calculateDockerAbsoluteCpu(&v.PreCPUStats, &v.CPUStats), CpuAbsolute: calculateDockerAbsoluteCpu(v.PreCPUStats, v.CPUStats),
Network: struct { Network: environment.NetworkStats{},
RxBytes uint64 `json:"rx_bytes"` }
TxBytes uint64 `json:"tx_bytes"`
}{ for _, nw := range v.Networks {
RxBytes: rx, st.Network.RxBytes += nw.RxBytes
TxBytes: tx, st.Network.TxBytes += nw.TxBytes
},
} }
if b, err := json.Marshal(st); err != nil { if b, err := json.Marshal(st); err != nil {
l.WithField("error", err).Warn("error while marshaling stats object for environment") e.log().WithField("error", err).Warn("error while marshaling stats object for environment")
} else { } else {
e.Events().Publish(environment.ResourceEvent, string(b)) e.Events().Publish(environment.ResourceEvent, string(b))
} }
@@ -108,7 +94,7 @@ func calculateDockerMemory(stats types.MemoryStats) uint64 {
// by the defined CPU limits on the container. // by the defined CPU limits on the container.
// //
// @see https://github.com/docker/cli/blob/aa097cf1aa19099da70930460250797c8920b709/cli/command/container/stats_helpers.go#L166 // @see https://github.com/docker/cli/blob/aa097cf1aa19099da70930460250797c8920b709/cli/command/container/stats_helpers.go#L166
func calculateDockerAbsoluteCpu(pStats *types.CPUStats, stats *types.CPUStats) float64 { func calculateDockerAbsoluteCpu(pStats types.CPUStats, stats types.CPUStats) float64 {
// Calculate the change in CPU usage between the current and previous reading. // Calculate the change in CPU usage between the current and previous reading.
cpuDelta := float64(stats.CPUUsage.TotalUsage) - float64(pStats.CPUUsage.TotalUsage) cpuDelta := float64(stats.CPUUsage.TotalUsage) - float64(pStats.CPUUsage.TotalUsage)

View File

@@ -4,9 +4,9 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"emperror.dev/errors"
"encoding/json" "encoding/json"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"strconv" "strconv"
) )

View File

@@ -1,13 +1,9 @@
package environment package environment
import "sync"
// Defines the current resource usage for a given server instance. If a server is offline you // Defines the current resource usage for a given server instance. If a server is offline you
// should obviously expect memory and CPU usage to be 0. However, disk will always be returned // should obviously expect memory and CPU usage to be 0. However, disk will always be returned
// since that is not dependent on the server being running to collect that data. // since that is not dependent on the server being running to collect that data.
type Stats struct { type Stats struct {
mu sync.RWMutex
// The total amount of memory, in bytes, that this server instance is consuming. This is // The total amount of memory, in bytes, that this server instance is consuming. This is
// calculated slightly differently than just using the raw Memory field that the stats // calculated slightly differently than just using the raw Memory field that the stats
// return from the container, so please check the code setting this value for how that // return from the container, so please check the code setting this value for how that
@@ -28,20 +24,10 @@ type Stats struct {
// Disk int64 `json:"disk_bytes"` // Disk int64 `json:"disk_bytes"`
// Current network transmit in & out for a container. // Current network transmit in & out for a container.
Network struct { Network NetworkStats `json:"network"`
RxBytes uint64 `json:"rx_bytes"`
TxBytes uint64 `json:"tx_bytes"`
} `json:"network"`
} }
// Resets the usages values to zero, used when a server is stopped to ensure we don't hold type NetworkStats struct {
// onto any values incorrectly. RxBytes uint64 `json:"rx_bytes"`
func (s *Stats) Empty() { TxBytes uint64 `json:"tx_bytes"`
s.mu.Lock()
defer s.mu.Unlock()
s.Memory = 0
s.CpuAbsolute = 0
s.Network.TxBytes = 0
s.Network.RxBytes = 0
} }

102
go.mod
View File

@@ -3,79 +3,79 @@ module github.com/pterodactyl/wings
go 1.13 go 1.13
require ( require (
github.com/AlecAivazis/survey/v2 v2.1.0 emperror.dev/errors v0.8.0
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect github.com/AlecAivazis/survey/v2 v2.2.7
github.com/Jeffail/gabs/v2 v2.5.1 github.com/Jeffail/gabs/v2 v2.6.0
github.com/Microsoft/go-winio v0.4.14 // indirect github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/Microsoft/hcsshim v0.8.14 // indirect
github.com/NYTimes/logrotate v1.0.0 github.com/NYTimes/logrotate v1.0.0
github.com/andybalholm/brotli v1.0.0 // indirect github.com/andybalholm/brotli v1.0.1 // indirect
github.com/apex/log v1.8.0 github.com/apex/log v1.9.0
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/beevik/etree v1.1.0 github.com/beevik/etree v1.1.0
github.com/buger/jsonparser v1.0.0 github.com/buger/jsonparser v1.1.0
github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249
github.com/containerd/containerd v1.3.7 // indirect github.com/containerd/containerd v1.4.3 // indirect
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b // indirect github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c // indirect
github.com/creasty/defaults v1.5.0 github.com/creasty/defaults v1.5.1
github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible github.com/docker/docker v20.10.1+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/fatih/color v1.10.0
github.com/fatih/color v1.9.0
github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd
github.com/frankban/quicktest v1.10.2 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gabriel-vasile/mimetype v1.1.1 github.com/gabriel-vasile/mimetype v1.1.2
github.com/gammazero/deque v0.0.0-20200721202602-07291166fe33 // indirect github.com/gammazero/deque v0.0.0-20201010052221-3932da5530cc // indirect
github.com/gammazero/workerpool v1.0.0 github.com/gammazero/workerpool v1.1.1
github.com/gbrlsnchs/jwt/v3 v3.0.0-rc.2 github.com/gbrlsnchs/jwt/v3 v3.0.0
github.com/gin-gonic/gin v1.6.3 github.com/gin-gonic/gin v1.6.3
github.com/go-playground/validator/v10 v10.3.0 // indirect github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/snappy v0.0.2 // indirect
github.com/google/uuid v1.1.1 github.com/google/go-cmp v0.5.2 // indirect
github.com/google/uuid v1.1.2
github.com/gorilla/mux v1.7.4 // indirect github.com/gorilla/mux v1.7.4 // indirect
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 github.com/iancoleman/strcase v0.1.2
github.com/icza/dyno v0.0.0-20200205103839-49cb13720835 github.com/icza/dyno v0.0.0-20200205103839-49cb13720835
github.com/imdario/mergo v0.3.8 github.com/imdario/mergo v0.3.9
github.com/juju/ratelimit v1.0.1
github.com/karrick/godirwalk v1.16.1 github.com/karrick/godirwalk v1.16.1
github.com/klauspost/compress v1.10.10 // indirect github.com/klauspost/compress v1.11.4 // indirect
github.com/klauspost/pgzip v1.2.4 github.com/klauspost/pgzip v1.2.5
github.com/leodido/go-urn v1.2.1 // indirect
github.com/magefile/mage v1.10.0 // indirect github.com/magefile/mage v1.10.0 // indirect
github.com/magiconair/properties v1.8.1 github.com/magiconair/properties v1.8.4
github.com/mattn/go-colorable v0.1.7 github.com/mattn/go-colorable v0.1.8
github.com/mattn/go-shellwords v1.0.10 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/archiver/v3 v3.3.0 github.com/mholt/archiver/v3 v3.5.0
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.5.0 github.com/pkg/profile v1.5.0
github.com/pkg/sftp v1.11.0 github.com/pkg/sftp v1.12.0
github.com/prometheus/common v0.11.1 // indirect github.com/prometheus/client_golang v1.9.0 // indirect
github.com/remeh/sizedwaitgroup v1.0.0 github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 github.com/sirupsen/logrus v1.7.0 // indirect
github.com/spf13/cobra v1.0.0 github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5 // indirect github.com/ugorji/go v1.2.2 // indirect
github.com/ulikunitz/xz v0.5.7 // indirect github.com/ulikunitz/xz v0.5.9 // indirect
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/text v0.3.3 // indirect golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
golang.org/x/text v0.3.4 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 // indirect google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d // indirect
google.golang.org/grpc v1.31.0 // indirect google.golang.org/grpc v1.34.0 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/ini.v1 v1.57.0 gopkg.in/ini.v1 v1.62.0
gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2 v2.4.0
gotest.tools v2.2.0+incompatible // indirect
) )

324
go.sum
View File

@@ -1,16 +1,33 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AlecAivazis/survey/v2 v2.1.0 h1:AT4+23hOFopXYZaNGugbk7MWItkz0SfTmH/Hk92KeeE= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
github.com/AlecAivazis/survey/v2 v2.1.0/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
emperror.dev/errors v0.8.0 h1:4lycVEx0sdJkwDUfQ9pdu6SR0x7rgympt5f4+ok8jDk=
emperror.dev/errors v0.8.0/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE=
github.com/AlecAivazis/survey/v2 v2.2.7 h1:5NbxkF4RSKmpywYdcRgUmos1o+roJY8duCLZXbVjoig=
github.com/AlecAivazis/survey/v2 v2.2.7/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Jeffail/gabs/v2 v2.5.1 h1:ANfZYjpMlfTTKebycu4X1AgkVWumFVDYQl7JwOr4mDk= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Jeffail/gabs/v2 v2.5.1/go.mod h1:xCn81vdHKxFUuWWAaD5jCTQDNPBMh5pPs9IJ+NcziBI= github.com/Jeffail/gabs/v2 v2.6.0 h1:WdCnGaDhNa4LSRTMwhLZzJ7SRDXjABNP13SOKvCpL5w=
github.com/Jeffail/gabs/v2 v2.6.0/go.mod h1:xCn81vdHKxFUuWWAaD5jCTQDNPBMh5pPs9IJ+NcziBI=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Microsoft/hcsshim v0.8.14 h1:lbPVK25c1cu5xTLITwpUcxoA9vKrKErASPYygvouJns=
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
github.com/NYTimes/logrotate v1.0.0 h1:6jFGbon6jOtpy3t3kwZZKS4Gdmf1C/Wv5J4ll4Xn5yk= github.com/NYTimes/logrotate v1.0.0 h1:6jFGbon6jOtpy3t3kwZZKS4Gdmf1C/Wv5J4ll4Xn5yk=
github.com/NYTimes/logrotate v1.0.0/go.mod h1:GxNz1cSw1c6t99PXoZlw+nm90H6cyQyrH66pjVv7x88= github.com/NYTimes/logrotate v1.0.0/go.mod h1:GxNz1cSw1c6t99PXoZlw+nm90H6cyQyrH66pjVv7x88=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
@@ -25,24 +42,23 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFAK3Q08CMvv8y3/8ATaEqv2nGoc6yff6c=
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U=
github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apex/log v1.8.0 h1:+W4j+dttibFvynPLlctdnYFUn1eLKT37BZWWW2iMfEM= github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
github.com/apex/log v1.8.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA= github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA=
github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
@@ -56,8 +72,9 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/buger/jsonparser v1.0.0 h1:etJTGF5ESxjI0Ic2UaLQs2LQQpa8G9ykQScukbh4L8A= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/buger/jsonparser v1.0.0/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ= github.com/buger/jsonparser v1.1.0 h1:EPAGdKZgZCON4ZcMD+h4l/NN4ndr6ijSpj4INh8PbUY=
github.com/buger/jsonparser v1.1.0/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -65,29 +82,42 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 h1:R0IDH8daQ3lODvu8YtxnIqqth5qMGCJyADoUQvmLx4o= github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 h1:R0IDH8daQ3lODvu8YtxnIqqth5qMGCJyADoUQvmLx4o=
github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249/go.mod h1:EHKW9yNEYSBpTKzuu7Y9oOrft/UlzH57rMIB03oev6M= github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249/go.mod h1:EHKW9yNEYSBpTKzuu7Y9oOrft/UlzH57rMIB03oev6M=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/containerd/containerd v1.3.7 h1:eFSOChY8TTcxvkzp8g+Ov1RL0MYww7XEeK0y+zqGpVc= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
github.com/containerd/containerd v1.3.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b h1:qUtCegLdOUVfVJOw+KDg6eJyE1TGvLlkGEd1091kSSQ= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/containerd v1.4.3 h1:ijQT13JedHSHrQGWFcGEwzcNKrAGIiZ+jSD5QQG07SY=
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c h1:1c6xmkNiu6Jnr6AKGM91GGNsfU+nPNFvw9BZFSo0E+c=
github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creasty/defaults v1.5.0 h1:DW6NAGGaKuNSKkntc8BCBrR2KOUAcXVnfcwu/LmJhaQ= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creasty/defaults v1.5.0/go.mod h1:FPZ+Y0WNrbqOVw+c6av63eyHUAl6pMHZwqLPvXUZGfY= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creasty/defaults v1.5.1 h1:j8WexcS3d/t4ZmllX4GEkl4wIB/trOr035ajcLHCISM=
github.com/creasty/defaults v1.5.1/go.mod h1:FPZ+Y0WNrbqOVw+c6av63eyHUAl6pMHZwqLPvXUZGfY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -95,8 +125,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8= github.com/docker/docker v20.10.1+incompatible h1:u0HIBLwOJdemyBdTCkoBX34u3lb5KyBo0rQE3a5Yg+E=
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
@@ -114,38 +144,37 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd h1:b/30UOB56Rhfe185ZfgvZT0/HOql0OzxuiNOxRKXRXc= github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd h1:b/30UOB56Rhfe185ZfgvZT0/HOql0OzxuiNOxRKXRXc=
github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk=
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.1.1 h1:qbN9MPuRf3bstHu9zkI9jDWNfH//9+9kHxr9oRBBBOA= github.com/gabriel-vasile/mimetype v1.1.2 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU=
github.com/gabriel-vasile/mimetype v1.1.1/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
github.com/gammazero/deque v0.0.0-20200227231300-1e9af0e52b46 h1:iX4+rD9Fjdx8SkmSO/O5WAIX/j79ll3kuqv5VdYt9J8=
github.com/gammazero/deque v0.0.0-20200227231300-1e9af0e52b46/go.mod h1:D90+MBHVc9Sk1lJAbEVgws0eYEurY4mv2TDso3Nxh3w=
github.com/gammazero/deque v0.0.0-20200721202602-07291166fe33 h1:UG4wNrJX9xSKnm/Gck5yTbxnOhpNleuE4MQRdmcGySo= github.com/gammazero/deque v0.0.0-20200721202602-07291166fe33 h1:UG4wNrJX9xSKnm/Gck5yTbxnOhpNleuE4MQRdmcGySo=
github.com/gammazero/deque v0.0.0-20200721202602-07291166fe33/go.mod h1:D90+MBHVc9Sk1lJAbEVgws0eYEurY4mv2TDso3Nxh3w= github.com/gammazero/deque v0.0.0-20200721202602-07291166fe33/go.mod h1:D90+MBHVc9Sk1lJAbEVgws0eYEurY4mv2TDso3Nxh3w=
github.com/gammazero/workerpool v1.0.0 h1:MfkJc6KL0tAmjrRDS203AZz3F+84Uod9YbL8KjpcQ00= github.com/gammazero/deque v0.0.0-20201010052221-3932da5530cc h1:F7BbnLACph7UYiz9ZHi6npcROwKaZUyviDjsNERsoMM=
github.com/gammazero/workerpool v1.0.0/go.mod h1:/XWO2YAUUpPi3smDlFBl0vpX0JHwUomDM/oRMwRmnSs= github.com/gammazero/deque v0.0.0-20201010052221-3932da5530cc/go.mod h1:IlBLfYXnuw9sspy1XS6ctu5exGb6WHGKQsyo4s7bOEA=
github.com/gbrlsnchs/jwt/v3 v3.0.0-rc.2 h1:3t7jvTkeQfk1FdP0noXSNiM6AdBokLz7QmZDmnCHAAA= github.com/gammazero/workerpool v1.1.1 h1:MN29GcZtZZAgzTU+Zk54Y+J9XkE54MoXON/NCZvNulo=
github.com/gbrlsnchs/jwt/v3 v3.0.0-rc.2/go.mod h1:AncDcjXz18xetI3A6STfXq2w+LuTx8pQ8bGEwRN8zVM= github.com/gammazero/workerpool v1.1.1/go.mod h1:5BN0IJVRjSFAypo9QTJCaWdijjNz9Jjl6VFS1PRjCeg=
github.com/gbrlsnchs/jwt/v3 v3.0.0 h1:gtPjdT3gAbBLjVckJsgNf+a46sqrCBfRebg2r/NysIo=
github.com/gbrlsnchs/jwt/v3 v3.0.0/go.mod h1:AncDcjXz18xetI3A6STfXq2w+LuTx8pQ8bGEwRN8zVM=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
@@ -161,10 +190,11 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -172,13 +202,13 @@ github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721 h1:KRMr9A3qfbVM7iV/WcLY/rL5LICqwMHLhwRXKu99fXw=
github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -192,9 +222,13 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -207,10 +241,17 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@@ -219,8 +260,6 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -228,7 +267,9 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
@@ -253,12 +294,12 @@ github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDG
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8= github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/icza/dyno v0.0.0-20200205103839-49cb13720835 h1:f1irK5f03uGGj+FjgQfZ5VhdKNVQVJ4skHsedzVohQ4= github.com/icza/dyno v0.0.0-20200205103839-49cb13720835 h1:f1irK5f03uGGj+FjgQfZ5VhdKNVQVJ4skHsedzVohQ4=
github.com/icza/dyno v0.0.0-20200205103839-49cb13720835/go.mod h1:c1tRKs5Tx7E2+uHGSyyncziFjvGpgv4H2HrqXeUQ/Uk= github.com/icza/dyno v0.0.0-20200205103839-49cb13720835/go.mod h1:c1tRKs5Tx7E2+uHGSyyncziFjvGpgv4H2HrqXeUQ/Uk=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@@ -273,8 +314,11 @@ github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGn
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY=
github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
@@ -286,17 +330,18 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.2 h1:LfVyl+ZlLlLDeQ/d2AqfGIIH4qEDu0Ed2S5GyhCWIWY=
github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I= github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM=
github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A= github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A=
github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
@@ -305,8 +350,6 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -314,6 +357,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
@@ -321,35 +366,31 @@ github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE=
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g= github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig= github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE=
github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
@@ -361,6 +402,8 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -380,8 +423,6 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
@@ -393,10 +434,13 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@@ -415,8 +459,10 @@ github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4/v4 v4.0.3 h1:vNQKSVZNYUEAvRY9FaUXAF1XPbSOHJtDTiP41kzDz2E=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
@@ -426,8 +472,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug= github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pkg/sftp v1.11.0 h1:4Zv0OGbpkg4yNuUtH0s8rvoYxRCNyT29NVUo6pgPmxI= github.com/pkg/sftp v1.12.0 h1:/f3b24xrDhkhddlaobPe2JgBqfdt+gC/NYl0QY9IOuI=
github.com/pkg/sftp v1.11.0/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.12.0/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
@@ -440,6 +486,8 @@ github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQ
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
@@ -456,8 +504,9 @@ github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkp
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.11.1 h1:0ZISXCMRuCZcxF77aT1BXY5m74mX2vrGYl1dSwBI0Jo= github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM=
github.com/prometheus/common v0.11.1/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -467,17 +516,17 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0= github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f h1:8P2MkG70G76gnZBOPGwmMIgwBb/rESQuwsJ7K8ds4NE=
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ= github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -488,6 +537,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
@@ -502,15 +553,15 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
@@ -521,8 +572,10 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0 h1:Rw8kxzWo1mr6FSaYXjQELRe88y2KdfynXdnK72rdjtA= github.com/tj/assert v0.0.0-20171129193455-018094318fb0 h1:Rw8kxzWo1mr6FSaYXjQELRe88y2KdfynXdnK72rdjtA=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
@@ -534,34 +587,45 @@ github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.2 h1:60ZHIOcsJlo3bJm9CbTVu7OSqT2mxaEmyQbK2NwCkn0=
github.com/ugorji/go v1.2.2/go.mod h1:bitgyERdV7L7Db/Z5gfd5v2NQMNhhiFiZwpgMw2SP7k=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.2 h1:08Gah8d+dXj4cZNUHhtuD/S4PXD5WpVbj5B8/ClELAQ=
github.com/ugorji/go/codec v1.2.2/go.mod h1:OM8g7OAy52uYl3Yk+RE/3AS1nXFn1Wh4PPLtupCxbuU=
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4=
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
@@ -572,22 +636,33 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -601,16 +676,19 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -619,8 +697,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -631,37 +709,58 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -669,14 +768,24 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -686,32 +795,45 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d h1:HV9Z9qMhQEsdlvxNFELgQ11RkMzO3CMkjEySjCtuLes=
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0= google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -735,8 +857,9 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
@@ -750,15 +873,22 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

View File

@@ -1,10 +1,10 @@
package installer package installer
import ( import (
"emperror.dev/errors"
"encoding/json" "encoding/json"
"github.com/asaskevich/govalidator" "github.com/asaskevich/govalidator"
"github.com/buger/jsonparser" "github.com/buger/jsonparser"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"

View File

@@ -1,15 +1,15 @@
package cli package cli
import ( import (
"emperror.dev/errors"
"fmt" "fmt"
"github.com/apex/log" "github.com/apex/log"
"github.com/apex/log/handlers/cli" "github.com/apex/log/handlers/cli"
color2 "github.com/fatih/color" color2 "github.com/fatih/color"
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
"github.com/pkg/errors"
"io" "io"
"math"
"os" "os"
"strings"
"sync" "sync"
"time" "time"
) )
@@ -42,10 +42,6 @@ func New(w io.Writer, useColors bool) *Handler {
return &Handler{Writer: colorable.NewNonColorable(w), Padding: 2} return &Handler{Writer: colorable.NewNonColorable(w), Padding: 2}
} }
type tracer interface {
StackTrace() errors.StackTrace
}
// HandleLog implements log.Handler. // HandleLog implements log.Handler.
func (h *Handler) HandleLog(e *log.Entry) error { func (h *Handler) HandleLog(e *log.Entry) error {
color := cli.Colors[e.Level] color := cli.Colors[e.Level]
@@ -70,15 +66,63 @@ func (h *Handler) HandleLog(e *log.Entry) error {
if name != "error" { if name != "error" {
continue continue
} }
if err, ok := e.Fields.Get("error").(error); ok { if err, ok := e.Fields.Get("error").(error); ok {
if e, ok := errors.Cause(err).(tracer); ok { // Attach the stacktrace if it is missing at this point, but don't point
st := e.StackTrace() // it specifically to this line since that is irrelevant.
l := math.Min(float64(len(st)), 10) err = errors.WithStackDepthIf(err, 4)
fmt.Fprintf(h.Writer, "\n%s%+v\n\n", boldred.Sprintf("Stacktrace:"), st[0:int(l)]) formatted := fmt.Sprintf("\n%s\n%+v\n\n", boldred.Sprintf("Stacktrace:"), err)
} else {
fmt.Fprintf(h.Writer, "\n%s\n%+v\n\n", boldred.Sprintf("Stacktrace:"), err) if !strings.Contains(formatted, "runtime.goexit") {
_, _ = fmt.Fprint(h.Writer, formatted)
break
} }
// Inserts a new-line between sections of a stack.
// When wrapping errors, you get multiple separate stacks that start with their message,
// this allows us to separate them with a new-line and view them more easily.
//
// For example:
//
// Stacktrace:
// readlink test: no such file or directory
// failed to read symlink target for 'test'
// github.com/pterodactyl/wings/server/filesystem.(*Archive).addToArchive
// github.com/pterodactyl/wings/server/filesystem/archive.go:166
// ... (Truncated the stack for easier reading)
// runtime.goexit
// runtime/asm_amd64.s:1374
// **NEW LINE INSERTED HERE**
// backup: error while generating server backup
// github.com/pterodactyl/wings/server.(*Server).Backup
// github.com/pterodactyl/wings/server/backup.go:84
// ... (Truncated the stack for easier reading)
// runtime.goexit
// runtime/asm_amd64.s:1374
//
var b strings.Builder
var endOfStack bool
for _, s := range strings.Split(formatted, "\n") {
b.WriteString(s + "\n")
if s == "runtime.goexit" {
endOfStack = true
continue
}
if !endOfStack {
continue
}
b.WriteString("\n")
endOfStack = false
}
_, _ = fmt.Fprint(h.Writer, b.String())
} }
// Only one key with the name "error" can be in the map.
break
} }
return nil return nil

View File

@@ -2,11 +2,11 @@ package parser
import ( import (
"bytes" "bytes"
"emperror.dev/errors"
"github.com/Jeffail/gabs/v2" "github.com/Jeffail/gabs/v2"
"github.com/apex/log" "github.com/apex/log"
"github.com/buger/jsonparser" "github.com/buger/jsonparser"
"github.com/iancoleman/strcase" "github.com/iancoleman/strcase"
"github.com/pkg/errors"
"io/ioutil" "io/ioutil"
"os" "os"
"regexp" "regexp"
@@ -150,7 +150,7 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
return errors.WithMessage(err, "error while parsing array element at path") return errors.WithMessage(err, "error while parsing array element at path")
} }
var t = make([]interface{}, 1) t := make([]interface{}, 1)
// If the length of matches is 4 it means we're trying to access an object down in this array // If the length of matches is 4 it means we're trying to access an object down in this array
// key, so make sure we generate the array as an array of objects, and not just a generic nil // key, so make sure we generate the array as an array of objects, and not just a generic nil
// array. // array.

View File

@@ -2,13 +2,13 @@ package parser
import ( import (
"bufio" "bufio"
"emperror.dev/errors"
"encoding/json" "encoding/json"
"github.com/apex/log" "github.com/apex/log"
"github.com/beevik/etree" "github.com/beevik/etree"
"github.com/buger/jsonparser" "github.com/buger/jsonparser"
"github.com/icza/dyno" "github.com/icza/dyno"
"github.com/magiconair/properties" "github.com/magiconair/properties"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@@ -219,7 +219,7 @@ func (f *ConfigurationFile) parseXmlFile(path string) error {
parts := strings.Split(replacement.Match, ".") parts := strings.Split(replacement.Match, ".")
// Set the initial element to be the root element, and then work from there. // Set the initial element to be the root element, and then work from there.
var element = doc.Root() element := doc.Root()
// Iterate over the path to create the required structure for the given element's path. // Iterate over the path to create the required structure for the given element's path.
// This does not set a value, only ensures that the base structure exists. We start at index // This does not set a value, only ensures that the base structure exists. We start at index

View File

@@ -0,0 +1,304 @@
package downloader
import (
"context"
"emperror.dev/errors"
"encoding/json"
"fmt"
"github.com/google/uuid"
"github.com/pterodactyl/wings/server"
"io"
"net"
"net/http"
"net/url"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
)
var client = &http.Client{Timeout: time.Hour * 12}
var instance = &Downloader{
// Tracks all of the active downloads.
downloadCache: make(map[string]*Download),
// Tracks all of the downloads active for a given server instance. This is
// primarily used to make things quicker and keep the code a little more
// legible throughout here.
serverCache: make(map[string][]string),
}
// Regex to match the end of an IPv4/IPv6 address. This allows the port to be removed
// so that we are just working with the raw IP address in question.
var ipMatchRegex = regexp.MustCompile(`(:\d+)$`)
// Internal IP ranges that should be blocked if the resource requested resolves within.
var internalRanges = []*net.IPNet{
mustParseCIDR("127.0.0.1/8"),
mustParseCIDR("10.0.0.0/8"),
mustParseCIDR("172.16.0.0/12"),
mustParseCIDR("192.168.0.0/16"),
mustParseCIDR("169.254.0.0/16"),
mustParseCIDR("::1/128"),
mustParseCIDR("fe80::/10"),
mustParseCIDR("fc00::/7"),
}
const ErrInternalResolution = errors.Sentinel("downloader: destination resolves to internal network location")
const ErrInvalidIPAddress = errors.Sentinel("downloader: invalid IP address")
const ErrDownloadFailed = errors.Sentinel("downloader: download request failed")
type Counter struct {
total int
onWrite func(total int)
}
func (c *Counter) Write(p []byte) (int, error) {
n := len(p)
c.total += n
c.onWrite(c.total)
return n, nil
}
type DownloadRequest struct {
URL *url.URL
Directory string
}
type Download struct {
Identifier string
mu sync.RWMutex
req DownloadRequest
server *server.Server
progress float64
cancelFunc *context.CancelFunc
}
// Starts a new tracked download which allows for cancelation later on by calling
// the Downloader.Cancel function.
func New(s *server.Server, r DownloadRequest) *Download {
dl := Download{
Identifier: uuid.Must(uuid.NewRandom()).String(),
req: r,
server: s,
}
instance.track(&dl)
return &dl
}
// Returns all of the tracked downloads for a given server instance.
func ByServer(sid string) []*Download {
instance.mu.Lock()
defer instance.mu.Unlock()
var downloads []*Download
if v, ok := instance.serverCache[sid]; ok {
for _, id := range v {
if dl, dlok := instance.downloadCache[id]; dlok {
downloads = append(downloads, dl)
}
}
}
return downloads
}
// Returns a single Download matching a given identifier. If no download is found
// the second argument in the response will be false.
func ByID(dlid string) *Download {
return instance.find(dlid)
}
//goland:noinspection GoVetCopyLock
func (dl Download) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Identifier string
Progress float64
}{
Identifier: dl.Identifier,
Progress: dl.Progress(),
})
}
// Executes a given download for the server and begins writing the file to the disk. Once
// completed the download will be removed from the cache.
func (dl *Download) Execute() error {
ctx, cancel := context.WithTimeout(context.Background(), time.Hour*12)
dl.cancelFunc = &cancel
defer dl.Cancel()
// Always ensure that we're checking the destination for the download to avoid a malicious
// user from accessing internal network resources.
if err := dl.isExternalNetwork(ctx); err != nil {
return err
}
// At this point we have verified the destination is not within the local network, so we can
// now make a request to that URL and pull down the file, saving it to the server's data
// directory.
req, err := http.NewRequestWithContext(ctx, http.MethodGet, dl.req.URL.String(), nil)
if err != nil {
return errors.WrapIf(err, "downloader: failed to create request")
}
req.Header.Set("User-Agent", "Pterodactyl Panel (https://pterodactyl.io)")
res, err := client.Do(req)
if err != nil {
return ErrDownloadFailed
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return errors.New("downloader: got bad response status from endpoint: " + res.Status)
}
// If there is a Content-Length header on this request go ahead and check that we can
// even write the whole file before beginning this process. If there is no header present
// we'll just have to give it a spin and see how it goes.
if res.ContentLength > 0 {
if err := dl.server.Filesystem().HasSpaceFor(res.ContentLength); err != nil {
return errors.WrapIf(err, "downloader: failed to write file: not enough space")
}
}
fnameparts := strings.Split(dl.req.URL.Path, "/")
p := filepath.Join(dl.req.Directory, fnameparts[len(fnameparts)-1])
dl.server.Log().WithField("path", p).Debug("writing remote file to disk")
r := io.TeeReader(res.Body, dl.counter(res.ContentLength))
if err := dl.server.Filesystem().Writefile(p, r); err != nil {
return errors.WrapIf(err, "downloader: failed to write file to server directory")
}
return nil
}
// Cancels a running download and frees up the associated resources. If a file is being
// written a partial file will remain present on the disk.
func (dl *Download) Cancel() {
if dl.cancelFunc != nil {
(*dl.cancelFunc)()
}
instance.remove(dl.Identifier)
}
// Checks if the given download belongs to the provided server.
func (dl *Download) BelongsTo(s *server.Server) bool {
return dl.server.Id() == s.Id()
}
// Returns the current progress of the download as a float value between 0 and 1 where
// 1 indicates that the download is completed.
func (dl *Download) Progress() float64 {
dl.mu.RLock()
defer dl.mu.RUnlock()
return dl.progress
}
// Handles a write event by updating the progress completed percentage and firing off
// events to the server websocket as needed.
func (dl *Download) counter(contentLength int64) *Counter {
onWrite := func(t int) {
dl.mu.Lock()
defer dl.mu.Unlock()
dl.progress = float64(t) / float64(contentLength)
}
return &Counter{
onWrite: onWrite,
}
}
// Verifies that a given download resolves to a location not within the current local
// network for the machine. If the final destination of a resource is within the local
// network an ErrInternalResolution error is returned.
func (dl *Download) isExternalNetwork(ctx context.Context) error {
dialer := &net.Dialer{
LocalAddr: nil,
}
host := dl.req.URL.Host
if !ipMatchRegex.MatchString(host) {
if dl.req.URL.Scheme == "https" {
host = host + ":443"
} else {
host = host + ":80"
}
}
c, err := dialer.DialContext(ctx, "tcp", host)
if err != nil {
return errors.WithStack(err)
}
c.Close()
ip := net.ParseIP(ipMatchRegex.ReplaceAllString(c.RemoteAddr().String(), ""))
if ip == nil {
return errors.WithStack(ErrInvalidIPAddress)
}
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast() {
return errors.WithStack(ErrInternalResolution)
}
for _, block := range internalRanges {
if block.Contains(ip) {
return errors.WithStack(ErrInternalResolution)
}
}
return nil
}
// Defines a global downloader struct that keeps track of all currently processing downloads
// for the machine.
type Downloader struct {
mu sync.RWMutex
downloadCache map[string]*Download
serverCache map[string][]string
}
// Tracks a download in the internal cache for this instance.
func (d *Downloader) track(dl *Download) {
d.mu.Lock()
defer d.mu.Unlock()
sid := dl.server.Id()
if _, ok := d.downloadCache[dl.Identifier]; !ok {
d.downloadCache[dl.Identifier] = dl
if _, ok := d.serverCache[sid]; !ok {
d.serverCache[sid] = []string{}
}
d.serverCache[sid] = append(d.serverCache[sid], dl.Identifier)
}
}
// Finds a given download entry using the provided ID and returns it.
func (d *Downloader) find(dlid string) *Download {
d.mu.RLock()
defer d.mu.RUnlock()
if entry, ok := d.downloadCache[dlid]; ok {
return entry
}
return nil
}
// Remove the given download reference from the cache storing them. This also updates
// the slice of active downloads for a given server to not include this download.
func (d *Downloader) remove(dlid string) {
d.mu.Lock()
defer d.mu.Unlock()
if _, ok := d.downloadCache[dlid]; !ok {
return
}
sid := d.downloadCache[dlid].server.Id()
delete(d.downloadCache, dlid)
if tracked, ok := d.serverCache[sid]; ok {
var out []string
for _, k := range tracked {
if k != dlid {
out = append(out, k)
}
}
d.serverCache[sid] = out
}
}
func mustParseCIDR(ip string) *net.IPNet {
_, block, err := net.ParseCIDR(ip)
if err != nil {
panic(fmt.Errorf("downloader: failed to parse CIDR: %s", err))
}
return block
}

View File

@@ -1,11 +1,11 @@
package router package router
import ( import (
"emperror.dev/errors"
"fmt" "fmt"
"github.com/apex/log" "github.com/apex/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem" "github.com/pterodactyl/wings/server/filesystem"
"net/http" "net/http"
@@ -14,45 +14,51 @@ import (
) )
type RequestError struct { type RequestError struct {
Err error err error
Uuid string uuid string
Message string message string
server *server.Server server *server.Server
} }
// Attaches an error to the gin.Context object for the request and ensures that it
// has a proper stacktrace associated with it when doing so.
//
// If you just call c.Error(err) without using this function you'll likely end up
// with an error that has no annotated stack on it.
func WithError(c *gin.Context, err error) error {
return c.Error(errors.WithStackDepthIf(err, 1))
}
// Generates a new tracked error, which simply tracks the specific error that // Generates a new tracked error, which simply tracks the specific error that
// is being passed in, and also assigned a UUID to the error so that it can be // is being passed in, and also assigned a UUID to the error so that it can be
// cross referenced in the logs. // cross referenced in the logs.
func TrackedError(err error) *RequestError { func NewTrackedError(err error) *RequestError {
return &RequestError{ return &RequestError{
Err: err, err: err,
Uuid: uuid.Must(uuid.NewRandom()).String(), uuid: uuid.Must(uuid.NewRandom()).String(),
Message: "",
} }
} }
// Same as TrackedError, except this will also attach the server instance that // Same as NewTrackedError, except this will also attach the server instance that
// generated this server for the purposes of logging. // generated this server for the purposes of logging.
func TrackedServerError(err error, s *server.Server) *RequestError { func NewServerError(err error, s *server.Server) *RequestError {
return &RequestError{ return &RequestError{
Err: err, err: err,
Uuid: uuid.Must(uuid.NewRandom()).String(), uuid: uuid.Must(uuid.NewRandom()).String(),
Message: "", server: s,
server: s,
} }
} }
func (e *RequestError) logger() *log.Entry { func (e *RequestError) logger() *log.Entry {
if e.server != nil { if e.server != nil {
return e.server.Log().WithField("error_id", e.Uuid) return e.server.Log().WithField("error_id", e.uuid).WithField("error", e.err)
} }
return log.WithField("error_id", e.uuid).WithField("error", e.err)
return log.WithField("error_id", e.Uuid)
} }
// Sets the output message to display to the user in the error. // Sets the output message to display to the user in the error.
func (e *RequestError) SetMessage(msg string) *RequestError { func (e *RequestError) SetMessage(msg string) *RequestError {
e.Message = msg e.message = msg
return e return e
} }
@@ -60,78 +66,86 @@ func (e *RequestError) SetMessage(msg string) *RequestError {
// will also include the error UUID in the output so that the user can report that // will also include the error UUID in the output so that the user can report that
// and link the response to a specific error in the logs. // and link the response to a specific error in the logs.
func (e *RequestError) AbortWithStatus(status int, c *gin.Context) { func (e *RequestError) AbortWithStatus(status int, c *gin.Context) {
// In instances where the status has already been set just use that existing status
// since we cannot change it at this point, and trying to do so will emit a gin warning
// into the program output.
if c.Writer.Status() != 200 {
status = c.Writer.Status()
}
// If this error is because the resource does not exist, we likely do not need to log // If this error is because the resource does not exist, we likely do not need to log
// the error anywhere, just return a 404 and move on with our lives. // the error anywhere, just return a 404 and move on with our lives.
if errors.Is(e.Err, os.ErrNotExist) { if errors.Is(e.err, os.ErrNotExist) {
e.logger().WithField("error", e.Err).Debug("encountered os.IsNotExist error while handling request") e.logger().Debug("encountered os.IsNotExist error while handling request")
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{ c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"error": "The requested resource was not found on the system.", "error": "The requested resource was not found on the system.",
}) })
return return
} }
if strings.HasPrefix(e.err.Error(), "invalid URL escape") {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "Some of the data provided in the request appears to be escaped improperly.",
})
return
}
// If this is a Filesystem error just return it without all of the tracking code nonsense
// since we don't need to be logging it into the logs or anything, its just a normal error
// that the user can solve on their end.
if st, msg := e.getAsFilesystemError(); st != 0 {
c.AbortWithStatusJSON(st, gin.H{"error": msg})
return
}
// Otherwise, log the error to zap, and then report the error back to the user. // Otherwise, log the error to zap, and then report the error back to the user.
if status >= 500 { if status >= 500 {
e.logger().WithField("error", e.Err).Error("encountered HTTP/500 error while handling request") e.logger().Error("unexpected error while handling HTTP request")
c.Error(e)
} else { } else {
e.logger().WithField("error", e.Err).Debug("encountered non-HTTP/500 error while handling request") e.logger().Debug("non-server error encountered while handling HTTP request")
} }
msg := "An unexpected error was encountered while processing this request." if e.message == "" {
if e.Message != "" { e.message = "An unexpected error was encountered while processing this request."
msg = e.Message
} }
c.AbortWithStatusJSON(status, gin.H{ c.AbortWithStatusJSON(status, gin.H{"error": e.message, "error_id": e.uuid})
"error": msg,
"error_id": e.Uuid,
})
} }
// Helper function to just abort with an internal server error. This is generally the response // Helper function to just abort with an internal server error. This is generally the response
// from most errors encountered by the API. // from most errors encountered by the API.
func (e *RequestError) AbortWithServerError(c *gin.Context) { func (e *RequestError) Abort(c *gin.Context) {
e.AbortWithStatus(http.StatusInternalServerError, c) e.AbortWithStatus(http.StatusInternalServerError, c)
} }
// Looks at the given RequestError and determines if it is a specific filesystem error that
// we can process and return differently for the user.
func (e *RequestError) getAsFilesystemError() (int, string) {
err := errors.Unwrap(e.err)
if err == nil {
return 0, ""
}
if errors.Is(err, os.ErrNotExist) || filesystem.IsErrorCode(err, filesystem.ErrCodePathResolution) {
return http.StatusNotFound, "The requested resource was not found on the system."
}
if filesystem.IsErrorCode(err, filesystem.ErrCodeDiskSpace) {
return http.StatusConflict, "There is not enough disk space available to perform that action."
}
if strings.HasSuffix(err.Error(), "file name too long") {
return http.StatusBadRequest, "Cannot perform that action: file name is too long."
}
if e, ok := err.(*os.SyscallError); ok && e.Syscall == "readdirent" {
return http.StatusNotFound, "The requested directory does not exist."
}
return 0, ""
}
// Handle specific filesystem errors for a server. // Handle specific filesystem errors for a server.
func (e *RequestError) AbortFilesystemError(c *gin.Context) { func (e *RequestError) AbortFilesystemError(c *gin.Context) {
if errors.Is(e.Err, os.ErrNotExist) || filesystem.IsBadPathResolutionError(e.Err) { e.Abort(c)
if filesystem.IsBadPathResolutionError(e.Err) {
e.logger().Warn(e.Err.Error())
}
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "The requested resource was not found."})
return
}
if errors.Is(e.Err, filesystem.ErrNotEnoughDiskSpace) {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": "There is not enough disk space available to perform that action."})
return
}
if strings.HasSuffix(e.Err.Error(), "file name too long") {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "File name is too long."})
return
}
if e, ok := e.Err.(*os.SyscallError); ok && e.Syscall == "readdirent" {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "The requested directory does not exist."})
return
}
if strings.HasSuffix(e.Err.Error(), "file name too long") {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Cannot perform that action: file name is too long."})
return
}
e.AbortWithServerError(c)
} }
// Format the error to a string and include the UUID. // Format the error to a string and include the UUID.
func (e *RequestError) Error() string { func (e *RequestError) Error() string {
return fmt.Sprintf("%v (uuid: %s)", e.Err, e.Uuid) return fmt.Sprintf("%v (uuid: %s)", e.err, e.uuid)
} }

View File

@@ -1,65 +1,100 @@
package router package router
import ( import (
"emperror.dev/errors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"io"
"net/http" "net/http"
"strings" "strings"
) )
// Set the access request control headers on all of the requests. type Middleware struct{}
func SetAccessControlHeaders(c *gin.Context) {
c.Header("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
o := c.GetHeader("Origin") // A custom handler function allowing for errors bubbled up by c.Error() to be returned in a
if o != config.Get().PanelLocation { // standardized format with tracking UUIDs on them for easier log searching.
for _, origin := range config.Get().AllowedOrigins { func (m *Middleware) ErrorHandler() gin.HandlerFunc {
if origin != "*" && o != origin { return func(c *gin.Context) {
continue c.Next()
} err := c.Errors.Last()
if err == nil || err.Err == nil {
c.Header("Access-Control-Allow-Origin", origin)
c.Next()
return return
} }
tracked := NewTrackedError(err.Err)
// If there is a server in the context for this request pull it out so that we can
// track the error specifically for that server.
if s, ok := c.Get("server"); ok {
tracked = NewServerError(err.Err, s.(*server.Server))
}
// This error occurs if you submit invalid JSON data to an endpoint.
if err.Err.Error() == io.EOF.Error() {
c.JSON(c.Writer.Status(), gin.H{"error": "A JSON formatted body is required for this endpoint."})
return
}
tracked.Abort(c)
return
} }
}
c.Header("Access-Control-Allow-Origin", config.Get().PanelLocation) // Set the access request control headers on all of the requests.
c.Next() func (m *Middleware) SetAccessControlHeaders() gin.HandlerFunc {
origins := config.Get().AllowedOrigins
location := config.Get().PanelLocation
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Allow-Methods", "GET, POST, PATCH, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Accept, Accept-Encoding, Authorization, Cache-Control, Content-Type, Content-Length, Origin, X-Real-IP, X-CSRF-Token")
o := c.GetHeader("Origin")
if o != location {
for _, origin := range origins {
if origin != "*" && o != origin {
continue
}
c.Header("Access-Control-Allow-Origin", origin)
c.Next()
return
}
}
c.Header("Access-Control-Allow-Origin", location)
c.Next()
}
} }
// Authenticates the request token against the given permission string, ensuring that // Authenticates the request token against the given permission string, ensuring that
// if it is a server permission, the token has control over that server. If it is a global // if it is a server permission, the token has control over that server. If it is a global
// token, this will ensure that the request is using a properly signed global token. // token, this will ensure that the request is using a properly signed global token.
func AuthorizationMiddleware(c *gin.Context) { func (m *Middleware) RequireAuthorization() gin.HandlerFunc {
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2) token := config.Get().AuthenticationToken
return func(c *gin.Context) {
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2)
if len(auth) != 2 || auth[0] != "Bearer" {
c.Header("WWW-Authenticate", "Bearer")
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "The required authorization heads were not present in the request.",
})
return
}
if len(auth) != 2 || auth[0] != "Bearer" { // All requests to Wings must be authorized with the authentication token present in
c.Header("WWW-Authenticate", "Bearer") // the Wings configuration file. Remeber, all requests to Wings come from the Panel
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ // backend, or using a signed JWT for temporary authentication.
"error": "The required authorization heads were not present in the request.", if auth[1] == token {
c.Next()
return
}
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"error": "You are not authorized to access this endpoint.",
}) })
return
} }
// Try to match the request against the global token for the Daemon, regardless
// of the permission type. If nothing is matched we will fall through to the Panel
// API to try and validate permissions for a server.
if auth[1] == config.Get().AuthenticationToken {
c.Next()
return
}
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"error": "You are not authorized to access this endpoint.",
})
} }
// Helper function to fetch a server out of the servers collection stored in memory. // Helper function to fetch a server out of the servers collection stored in memory.
//
// This function should not be used in new controllers, prefer ExtractServer where
// possible.
func GetServer(uuid string) *server.Server { func GetServer(uuid string) *server.Server {
return server.GetServers().Find(func(s *server.Server) bool { return server.GetServers().Find(func(s *server.Server) bool {
return uuid == s.Id() return uuid == s.Id()
@@ -68,14 +103,28 @@ func GetServer(uuid string) *server.Server {
// Ensure that the requested server exists in this setup. Returns a 404 if we cannot // Ensure that the requested server exists in this setup. Returns a 404 if we cannot
// locate it. // locate it.
func ServerExists(c *gin.Context) { func (m *Middleware) ServerExists() gin.HandlerFunc {
u, err := uuid.Parse(c.Param("server")) return func(c *gin.Context) {
if err != nil || GetServer(u.String()) == nil { u, err := uuid.Parse(c.Param("server"))
if err == nil {
if s := GetServer(u.String()); s != nil {
c.Set("server", s)
c.Next()
return
}
}
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{ c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"error": "The resource you requested does not exist.", "error": "The resource you requested does not exist.",
}) })
return
} }
}
c.Next()
// Returns the server instance from the gin context. If there is no server set in the
// context (e.g. calling from a controller not protected by ServerExists) this function
// will panic.
func ExtractServer(c *gin.Context) *server.Server {
if s, ok := c.Get("server"); ok {
return s.(*server.Server)
}
panic(errors.New("cannot extract server, missing on gin context"))
} }

View File

@@ -9,10 +9,9 @@ import (
func Configure() *gin.Engine { func Configure() *gin.Engine {
gin.SetMode("release") gin.SetMode("release")
m := Middleware{}
router := gin.New() router := gin.New()
router.Use(gin.Recovery(), m.ErrorHandler(), m.SetAccessControlHeaders())
router.Use(gin.Recovery())
router.Use(SetAccessControlHeaders)
// @todo log this into a different file so you can setup IP blocking for abusive requests and such. // @todo log this into a different file so you can setup IP blocking for abusive requests and such.
// This should still dump requests in debug mode since it does help with understanding the request // This should still dump requests in debug mode since it does help with understanding the request
// lifecycle and quickly seeing what was called leading to the logs. However, it isn't feasible to mix // lifecycle and quickly seeing what was called leading to the logs. However, it isn't feasible to mix
@@ -40,16 +39,16 @@ func Configure() *gin.Engine {
// This route is special it sits above all of the other requests because we are // This route is special it sits above all of the other requests because we are
// using a JWT to authorize access to it, therefore it needs to be publicly // using a JWT to authorize access to it, therefore it needs to be publicly
// accessible. // accessible.
router.GET("/api/servers/:server/ws", ServerExists, getServerWebsocket) router.GET("/api/servers/:server/ws", m.ServerExists(), getServerWebsocket)
// This request is called by another daemon when a server is going to be transferred out. // This request is called by another daemon when a server is going to be transferred out.
// This request does not need the AuthorizationMiddleware as the panel should never call it // This request does not need the AuthorizationMiddleware as the panel should never call it
// and requests are authenticated through a JWT the panel issues to the other daemon. // and requests are authenticated through a JWT the panel issues to the other daemon.
router.GET("/api/servers/:server/archive", ServerExists, getServerArchive) router.GET("/api/servers/:server/archive", m.ServerExists(), getServerArchive)
// All of the routes beyond this mount will use an authorization middleware // All of the routes beyond this mount will use an authorization middleware
// and will not be accessible without the correct Authorization header provided. // and will not be accessible without the correct Authorization header provided.
protected := router.Use(AuthorizationMiddleware) protected := router.Use(m.RequireAuthorization())
protected.POST("/api/update", postUpdateConfiguration) protected.POST("/api/update", postUpdateConfiguration)
protected.GET("/api/system", getSystemInformation) protected.GET("/api/system", getSystemInformation)
protected.GET("/api/servers", getAllServers) protected.GET("/api/servers", getAllServers)
@@ -59,7 +58,7 @@ func Configure() *gin.Engine {
// These are server specific routes, and require that the request be authorized, and // These are server specific routes, and require that the request be authorized, and
// that the server exist on the Daemon. // that the server exist on the Daemon.
server := router.Group("/api/servers/:server") server := router.Group("/api/servers/:server")
server.Use(AuthorizationMiddleware, ServerExists) server.Use(m.RequireAuthorization(), m.ServerExists())
{ {
server.GET("", getServer) server.GET("", getServer)
server.PATCH("", patchServer) server.PATCH("", patchServer)
@@ -88,6 +87,10 @@ func Configure() *gin.Engine {
files.POST("/compress", postServerCompressFiles) files.POST("/compress", postServerCompressFiles)
files.POST("/decompress", postServerDecompressFiles) files.POST("/decompress", postServerDecompressFiles)
files.POST("/chmod", postServerChmodFile) files.POST("/chmod", postServerChmodFile)
files.GET("/pull", getServerPullingFiles)
files.POST("/pull", postServerPullRemoteFile)
files.DELETE("/pull/:download", deleteServerPullRemoteFile)
} }
backup := server.Group("/backup") backup := server.Group("/backup")

View File

@@ -15,7 +15,7 @@ import (
func getDownloadBackup(c *gin.Context) { func getDownloadBackup(c *gin.Context) {
token := tokens.BackupPayload{} token := tokens.BackupPayload{}
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil { if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
TrackedError(err).AbortWithServerError(c) NewTrackedError(err).Abort(c)
return return
} }
@@ -36,13 +36,13 @@ func getDownloadBackup(c *gin.Context) {
return return
} }
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }
f, err := os.Open(b.Path()) f, err := os.Open(b.Path())
if err != nil { if err != nil {
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }
defer f.Close() defer f.Close()
@@ -58,7 +58,7 @@ func getDownloadBackup(c *gin.Context) {
func getDownloadFile(c *gin.Context) { func getDownloadFile(c *gin.Context) {
token := tokens.FilePayload{} token := tokens.FilePayload{}
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil { if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
TrackedError(err).AbortWithServerError(c) NewTrackedError(err).Abort(c)
return return
} }
@@ -75,7 +75,7 @@ func getDownloadFile(c *gin.Context) {
// If there is an error or we're somehow trying to download a directory, just // If there is an error or we're somehow trying to download a directory, just
// respond with the appropriate error. // respond with the appropriate error.
if err != nil { if err != nil {
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} else if st.IsDir() { } else if st.IsDir() {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{ c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
@@ -86,7 +86,7 @@ func getDownloadFile(c *gin.Context) {
f, err := os.Open(p) f, err := os.Open(p)
if err != nil { if err != nil {
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }

View File

@@ -3,9 +3,10 @@ package router
import ( import (
"bytes" "bytes"
"context" "context"
"emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pkg/errors" "github.com/pterodactyl/wings/router/downloader"
"github.com/pterodactyl/wings/router/tokens" "github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"net/http" "net/http"
@@ -23,7 +24,7 @@ func getServer(c *gin.Context) {
s := GetServer(c.Param("server")) s := GetServer(c.Param("server"))
c.JSON(http.StatusOK, serverProcData{ c.JSON(http.StatusOK, serverProcData{
ResourceUsage: *s.Proc(), ResourceUsage: s.Proc(),
Suspended: s.IsSuspended(), Suspended: s.IsSuspended(),
}) })
} }
@@ -41,7 +42,7 @@ func getServerLogs(c *gin.Context) {
out, err := s.ReadLogfile(l) out, err := s.ReadLogfile(l)
if err != nil { if err != nil {
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }
@@ -110,7 +111,7 @@ func postServerCommands(c *gin.Context) {
s := GetServer(c.Param("server")) s := GetServer(c.Param("server"))
if running, err := s.Environment.IsRunning(); err != nil { if running, err := s.Environment.IsRunning(); err != nil {
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} else if !running { } else if !running {
c.AbortWithStatusJSON(http.StatusBadGateway, gin.H{ c.AbortWithStatusJSON(http.StatusBadGateway, gin.H{
@@ -144,7 +145,7 @@ func patchServer(c *gin.Context) {
buf.ReadFrom(c.Request.Body) buf.ReadFrom(c.Request.Body)
if err := s.UpdateDataStructure(buf.Bytes()); err != nil { if err := s.UpdateDataStructure(buf.Bytes()); err != nil {
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }
@@ -188,16 +189,18 @@ func postServerReinstall(c *gin.Context) {
// Deletes a server from the wings daemon and dissociate it's objects. // Deletes a server from the wings daemon and dissociate it's objects.
func deleteServer(c *gin.Context) { func deleteServer(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
// Immediately suspend the server to prevent a user from attempting // Immediately suspend the server to prevent a user from attempting
// to start it while this process is running. // to start it while this process is running.
s.Config().SetSuspended(true) s.Config().SetSuspended(true)
// If the server is currently installing, abort it. // Stop all running background tasks for this server that are using the context on
if s.IsInstalling() { // the server struct. This will cancel any running install processes for the server
s.AbortInstallation() // as well.
} s.CtxCancel()
s.Events().Destroy()
s.Websockets().CancelAll()
// Delete the server's archive if it exists. We intentionally don't return // Delete the server's archive if it exists. We intentionally don't return
// here, if the archive fails to delete, the server can still be removed. // here, if the archive fails to delete, the server can still be removed.
@@ -205,16 +208,17 @@ func deleteServer(c *gin.Context) {
s.Log().WithField("error", err).Warn("failed to delete server archive during deletion process") s.Log().WithField("error", err).Warn("failed to delete server archive during deletion process")
} }
// Unsubscribe all of the event listeners. // Remove any pending remote file downloads for the server.
s.Events().Destroy() for _, dl := range downloader.ByServer(s.Id()) {
s.Throttler().StopTimer() dl.Cancel()
s.Websockets().CancelAll() }
// Destroy the environment; in Docker this will handle a running container and // Destroy the environment; in Docker this will handle a running container and
// forcibly terminate it before removing the container, so we do not need to handle // forcibly terminate it before removing the container, so we do not need to handle
// that here. // that here.
if err := s.Environment.Destroy(); err != nil { if err := s.Environment.Destroy(); err != nil {
TrackedServerError(err, s).AbortWithServerError(c) WithError(c, err)
return
} }
// Once the environment is terminated, remove the server files from the system. This is // Once the environment is terminated, remove the server files from the system. This is
@@ -225,14 +229,11 @@ func deleteServer(c *gin.Context) {
// so we don't want to block the HTTP call while waiting on this. // so we don't want to block the HTTP call while waiting on this.
go func(p string) { go func(p string) {
if err := os.RemoveAll(p); err != nil { if err := os.RemoveAll(p); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{"path": p, "error": err}).Warn("failed to remove server files during deletion process")
"path": p,
"error": err,
}).Warn("failed to remove server files during deletion process")
} }
}(s.Filesystem().Path()) }(s.Filesystem().Path())
var uuid = s.Id() uuid := s.Id()
server.GetServers().Remove(func(s2 *server.Server) bool { server.GetServers().Remove(func(s2 *server.Server) bool {
return s2.Id() == uuid return s2.Id() == uuid
}) })

View File

@@ -1,7 +1,7 @@
package router package router
import ( import (
"errors" "emperror.dev/errors"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
@@ -34,13 +34,18 @@ func postServerBackup(c *gin.Context) {
} }
if err != nil { if err != nil {
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }
// Attach the server ID to the backup log output for easier parsing.
adapter.WithLogContext(map[string]interface{}{
"server": s.Id(),
})
go func(b backup.BackupInterface, serv *server.Server) { go func(b backup.BackupInterface, serv *server.Server) {
if err := serv.Backup(b); err != nil { if err := serv.Backup(b); err != nil {
serv.Log().WithField("error", err).Error("failed to generate backup for server") serv.Log().WithField("error", errors.WithStackIf(err)).Error("failed to generate backup for server")
} }
}(adapter, s) }(adapter, s)
@@ -63,7 +68,7 @@ func deleteServerBackup(c *gin.Context) {
return return
} }
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }
@@ -72,7 +77,7 @@ func deleteServerBackup(c *gin.Context) {
// the backup previously and it is now missing when we go to delete, just treat it as having // the backup previously and it is now missing when we go to delete, just treat it as having
// been successful, rather than returning a 404. // been successful, rather than returning a 404.
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }
} }

View File

@@ -2,13 +2,6 @@ package router
import ( import (
"context" "context"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
"golang.org/x/sync/errgroup"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/url" "net/url"
@@ -17,22 +10,25 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/downloader"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
"golang.org/x/sync/errgroup"
) )
// Returns the contents of a file on the server. // Returns the contents of a file on the server.
func getServerFileContents(c *gin.Context) { func getServerFileContents(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
f := c.Query("file")
p, err := url.QueryUnescape(c.Query("file")) p := "/" + strings.TrimLeft(f, "/")
if err != nil {
TrackedServerError(err, s).AbortWithServerError(c)
return
}
p = "/" + strings.TrimLeft(p, "/")
st, err := s.Filesystem().Stat(p) st, err := s.Filesystem().Stat(p)
if err != nil { if err != nil {
TrackedServerError(err, s).AbortFilesystemError(c) WithError(c, err)
return return
} }
@@ -56,28 +52,21 @@ func getServerFileContents(c *gin.Context) {
// happen since we're doing so much before this point that would normally throw an error if there // happen since we're doing so much before this point that would normally throw an error if there
// was a problem with the file. // was a problem with the file.
if err := s.Filesystem().Readfile(p, c.Writer); err != nil { if err := s.Filesystem().Readfile(p, c.Writer); err != nil {
TrackedServerError(err, s).AbortFilesystemError(c) WithError(c, err)
return return
} }
c.Writer.Flush()
} }
// Returns the contents of a directory for a server. // Returns the contents of a directory for a server.
func getServerListDirectory(c *gin.Context) { func getServerListDirectory(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
dir := c.Query("directory")
d, err := url.QueryUnescape(c.Query("directory")) if stats, err := s.Filesystem().ListDirectory(dir); err != nil {
if err != nil { WithError(c, err)
TrackedServerError(err, s).AbortWithServerError(c) } else {
return c.JSON(http.StatusOK, stats)
} }
stats, err := s.Filesystem().ListDirectory(d)
if err != nil {
TrackedServerError(err, s).AbortFilesystemError(c)
return
}
c.JSON(http.StatusOK, stats)
} }
type renameFile struct { type renameFile struct {
@@ -140,7 +129,7 @@ func putServerRenameFiles(c *gin.Context) {
return return
} }
TrackedServerError(err, s).AbortFilesystemError(c) NewServerError(err, s).AbortFilesystemError(c)
return return
} }
@@ -160,7 +149,7 @@ func postServerCopyFile(c *gin.Context) {
} }
if err := s.Filesystem().Copy(data.Location); err != nil { if err := s.Filesystem().Copy(data.Location); err != nil {
TrackedServerError(err, s).AbortFilesystemError(c) NewServerError(err, s).AbortFilesystemError(c)
return return
} }
@@ -205,7 +194,7 @@ func postServerDeleteFiles(c *gin.Context) {
} }
if err := g.Wait(); err != nil { if err := g.Wait(); err != nil {
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }
@@ -216,28 +205,99 @@ func postServerDeleteFiles(c *gin.Context) {
func postServerWriteFile(c *gin.Context) { func postServerWriteFile(c *gin.Context) {
s := GetServer(c.Param("server")) s := GetServer(c.Param("server"))
f, err := url.QueryUnescape(c.Query("file")) f := c.Query("file")
if err != nil {
TrackedServerError(err, s).AbortWithServerError(c)
return
}
f = "/" + strings.TrimLeft(f, "/") f = "/" + strings.TrimLeft(f, "/")
if err := s.Filesystem().Writefile(f, c.Request.Body); err != nil { if err := s.Filesystem().Writefile(f, c.Request.Body); err != nil {
if errors.Is(err, filesystem.ErrIsDirectory) { if filesystem.IsErrorCode(err, filesystem.ErrCodeIsDirectory) {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "Cannot write file, name conflicts with an existing directory by the same name.", "error": "Cannot write file, name conflicts with an existing directory by the same name.",
}) })
return return
} }
TrackedServerError(err, s).AbortFilesystemError(c) NewServerError(err, s).AbortFilesystemError(c)
return return
} }
c.Status(http.StatusNoContent) c.Status(http.StatusNoContent)
} }
// Returns all of the currently in-progress file downloads and their current download
// progress. The progress is also pushed out via a websocket event allowing you to just
// call this once to get current downloads, and then listen to targeted websocket events
// with the current progress for everything.
func getServerPullingFiles(c *gin.Context) {
s := ExtractServer(c)
c.JSON(http.StatusOK, gin.H{
"downloads": downloader.ByServer(s.Id()),
})
}
// Writes the contents of the remote URL to a file on a server.
func postServerPullRemoteFile(c *gin.Context) {
s := ExtractServer(c)
var data struct {
URL string `binding:"required" json:"url"`
Directory string `binding:"required,omitempty" json:"directory"`
}
if err := c.BindJSON(&data); err != nil {
return
}
u, err := url.Parse(data.URL)
if err != nil {
if e, ok := err.(*url.Error); ok {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "An error occurred while parsing that URL: " + e.Err.Error(),
})
return
}
WithError(c, err)
return
}
if err := s.Filesystem().HasSpaceErr(true); err != nil {
WithError(c, err)
return
}
// Do not allow more than three simultaneous remote file downloads at one time.
if len(downloader.ByServer(s.Id())) >= 3 {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "This server has reached its limit of 3 simultaneous remote file downloads at once. Please wait for one to complete before trying again.",
})
return
}
dl := downloader.New(s, downloader.DownloadRequest{
URL: u,
Directory: data.Directory,
})
// Execute this pull in a seperate thread since it may take a long time to complete.
go func() {
s.Log().WithField("download_id", dl.Identifier).WithField("url", u.String()).Info("starting pull of remote file to disk")
if err := dl.Execute(); err != nil {
s.Log().WithField("download_id", dl.Identifier).WithField("error", err).Error("failed to pull remote file")
} else {
s.Log().WithField("download_id", dl.Identifier).Info("completed pull of remote file")
}
}()
c.JSON(http.StatusAccepted, gin.H{
"identifier": dl.Identifier,
})
}
// Stops a remote file download if it exists and belongs to this server.
func deleteServerPullRemoteFile(c *gin.Context) {
s := ExtractServer(c)
if dl := downloader.ByID(c.Param("download")); dl != nil && dl.BelongsTo(s) {
dl.Cancel()
}
c.Status(http.StatusNoContent)
}
// Create a directory on a server. // Create a directory on a server.
func postServerCreateDirectory(c *gin.Context) { func postServerCreateDirectory(c *gin.Context) {
s := GetServer(c.Param("server")) s := GetServer(c.Param("server"))
@@ -259,7 +319,7 @@ func postServerCreateDirectory(c *gin.Context) {
return return
} }
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }
@@ -294,7 +354,7 @@ func postServerCompressFiles(c *gin.Context) {
f, err := s.Filesystem().CompressFiles(data.RootPath, data.Files) f, err := s.Filesystem().CompressFiles(data.RootPath, data.Files)
if err != nil { if err != nil {
TrackedServerError(err, s).AbortFilesystemError(c) NewServerError(err, s).AbortFilesystemError(c)
return return
} }
@@ -319,16 +379,15 @@ func postServerDecompressFiles(c *gin.Context) {
hasSpace, err := s.Filesystem().SpaceAvailableForDecompression(data.RootPath, data.File) hasSpace, err := s.Filesystem().SpaceAvailableForDecompression(data.RootPath, data.File)
if err != nil { if err != nil {
// Handle an unknown format error. // Handle an unknown format error.
if errors.Is(err, filesystem.ErrUnknownArchiveFormat) { if filesystem.IsErrorCode(err, filesystem.ErrCodeUnknownArchive) {
s.Log().WithField("error", err).Warn("failed to decompress file due to unknown format") s.Log().WithField("error", err).Warn("failed to decompress file due to unknown format")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "unknown archive format", "error": "unknown archive format",
}) })
return return
} }
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }
@@ -359,7 +418,7 @@ func postServerDecompressFiles(c *gin.Context) {
return return
} }
TrackedServerError(err, s).AbortFilesystemError(c) NewServerError(err, s).AbortFilesystemError(c)
return return
} }
@@ -371,6 +430,8 @@ type chmodFile struct {
Mode string `json:"mode"` Mode string `json:"mode"`
} }
var errInvalidFileMode = errors.New("invalid file mode")
func postServerChmodFile(c *gin.Context) { func postServerChmodFile(c *gin.Context) {
s := GetServer(c.Param("server")) s := GetServer(c.Param("server"))
@@ -402,7 +463,7 @@ func postServerChmodFile(c *gin.Context) {
default: default:
mode, err := strconv.ParseUint(p.Mode, 8, 32) mode, err := strconv.ParseUint(p.Mode, 8, 32)
if err != nil { if err != nil {
return err return errInvalidFileMode
} }
if err := s.Filesystem().Chmod(path.Join(data.Root, p.File), os.FileMode(mode)); err != nil { if err := s.Filesystem().Chmod(path.Join(data.Root, p.File), os.FileMode(mode)); err != nil {
@@ -421,7 +482,14 @@ func postServerChmodFile(c *gin.Context) {
} }
if err := g.Wait(); err != nil { if err := g.Wait(); err != nil {
TrackedServerError(err, s).AbortFilesystemError(c) if errors.Is(err, errInvalidFileMode) {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "Invalid file mode.",
})
return
}
NewServerError(err, s).AbortFilesystemError(c)
return return
} }
@@ -431,7 +499,7 @@ func postServerChmodFile(c *gin.Context) {
func postServerUploadFiles(c *gin.Context) { func postServerUploadFiles(c *gin.Context) {
token := tokens.UploadPayload{} token := tokens.UploadPayload{}
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil { if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
TrackedError(err).AbortWithServerError(c) NewTrackedError(err).Abort(c)
return return
} }
@@ -469,14 +537,14 @@ func postServerUploadFiles(c *gin.Context) {
for _, header := range headers { for _, header := range headers {
p, err := s.Filesystem().SafePath(filepath.Join(directory, header.Filename)) p, err := s.Filesystem().SafePath(filepath.Join(directory, header.Filename))
if err != nil { if err != nil {
TrackedServerError(err, s).AbortFilesystemError(c) NewServerError(err, s).AbortFilesystemError(c)
return return
} }
// We run this in a different method so I can use defer without any of // We run this in a different method so I can use defer without any of
// the consequences caused by calling it in a loop. // the consequences caused by calling it in a loop.
if err := handleFileUpload(p, s, header); err != nil { if err := handleFileUpload(p, s, header); err != nil {
TrackedServerError(err, s).AbortFilesystemError(c) NewServerError(err, s).AbortFilesystemError(c)
return return
} }
} }

View File

@@ -14,7 +14,7 @@ func getServerWebsocket(c *gin.Context) {
s := GetServer(c.Param("server")) s := GetServer(c.Param("server"))
handler, err := websocket.GetHandler(s, c.Writer, c.Request) handler, err := websocket.GetHandler(s, c.Writer, c.Request)
if err != nil { if err != nil {
TrackedServerError(err, s).AbortWithServerError(c) NewServerError(err, s).Abort(c)
return return
} }
defer handler.Connection.Close() defer handler.Connection.Close()
@@ -24,14 +24,14 @@ func getServerWebsocket(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
// Track this open connection on the server so that we can close them all programtically // Track this open connection on the server so that we can close them all programmatically
// if the server is deleted. // if the server is deleted.
s.Websockets().Push(handler.Uuid(), &cancel) s.Websockets().Push(handler.Uuid(), &cancel)
defer s.Websockets().Remove(handler.Uuid()) defer s.Websockets().Remove(handler.Uuid())
// Listen for the context being canceled and then close the websocket connection. This normally // Listen for the context being canceled and then close the websocket connection. This normally
// just happens because you're disconnecting from the socket in the browser, however in some // just happens because you're disconnecting from the socket in the browser, however in some
// cases we close the connections programatically (e.g. deleting the server) and need to send // cases we close the connections programmatically (e.g. deleting the server) and need to send
// a close message to the websocket so it disconnects. // a close message to the websocket so it disconnects.
go func(ctx context.Context, c *ws.Conn) { go func(ctx context.Context, c *ws.Conn) {
ListenerLoop: ListenerLoop:

View File

@@ -16,7 +16,7 @@ import (
func getSystemInformation(c *gin.Context) { func getSystemInformation(c *gin.Context) {
i, err := system.GetSystemInformation() i, err := system.GetSystemInformation()
if err != nil { if err != nil {
TrackedError(err).AbortWithServerError(c) NewTrackedError(err).Abort(c)
return return
} }
@@ -45,7 +45,7 @@ func postCreateServer(c *gin.Context) {
return return
} }
TrackedError(err).AbortWithServerError(c) NewTrackedError(err).Abort(c)
return return
} }
@@ -99,7 +99,7 @@ func postUpdateConfiguration(c *gin.Context) {
// before this code was run. // before this code was run.
config.Set(&ccopy) config.Set(&ccopy)
TrackedError(err).AbortWithServerError(c) NewTrackedError(err).Abort(c)
return return
} }

View File

@@ -2,28 +2,53 @@ package router
import ( import (
"bufio" "bufio"
"bytes"
"crypto/sha256" "crypto/sha256"
"emperror.dev/errors"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt"
"github.com/apex/log" "github.com/apex/log"
"github.com/buger/jsonparser"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/juju/ratelimit"
"github.com/mholt/archiver/v3" "github.com/mholt/archiver/v3"
"github.com/pkg/errors" "github.com/mitchellh/colorstring"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/installer" "github.com/pterodactyl/wings/installer"
"github.com/pterodactyl/wings/router/tokens" "github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/system"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync/atomic"
"time"
) )
// Number of ticks in the progress bar
const ticks = 25
// 100% / number of ticks = percentage represented by each tick
const tickPercentage = 100 / ticks
type downloadProgress struct {
size int64
progress int64
}
// Data passed over to initiate a server transfer.
type serverTransferRequest struct {
ServerID string `binding:"required" json:"server_id"`
URL string `binding:"required" json:"url"`
Token string `binding:"required" json:"token"`
Server json.RawMessage `json:"server"`
}
// Returns the archive for a server so that it can be transferred to a new node.
func getServerArchive(c *gin.Context) { func getServerArchive(c *gin.Context) {
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2) auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2)
@@ -37,46 +62,37 @@ func getServerArchive(c *gin.Context) {
token := tokens.TransferPayload{} token := tokens.TransferPayload{}
if err := tokens.ParseToken([]byte(auth[1]), &token); err != nil { if err := tokens.ParseToken([]byte(auth[1]), &token); err != nil {
TrackedError(err).AbortWithServerError(c) NewTrackedError(err).Abort(c)
return return
} }
if token.Subject != c.Param("server") { s := ExtractServer(c)
if token.Subject != s.Id() {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"error": "( .. •˘___˘• .. )", "error": "Missing required token subject, or subject is not valid for the requested server.",
}) })
return return
} }
s := GetServer(c.Param("server"))
st, err := s.Archiver.Stat() st, err := s.Archiver.Stat()
if err != nil { if err != nil {
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
TrackedServerError(err, s).SetMessage("failed to stat archive").AbortWithServerError(c) WithError(c, err)
return return
} }
c.AbortWithStatus(http.StatusNotFound) c.AbortWithStatus(http.StatusNotFound)
return return
} }
checksum, err := s.Archiver.Checksum() checksum, err := s.Archiver.Checksum()
if err != nil { if err != nil {
TrackedServerError(err, s).SetMessage("failed to calculate checksum").AbortWithServerError(c) NewServerError(err, s).SetMessage("failed to calculate checksum").Abort(c)
return return
} }
file, err := os.Open(s.Archiver.Path()) file, err := os.Open(s.Archiver.Path())
if err != nil { if err != nil {
tserr := TrackedServerError(err, s) WithError(c, err)
if !os.IsNotExist(err) {
tserr.SetMessage("failed to open archive for reading")
} else {
tserr.SetMessage("failed to open archive")
}
tserr.AbortWithServerError(c)
return return
} }
defer file.Close() defer file.Close()
@@ -91,60 +107,21 @@ func getServerArchive(c *gin.Context) {
} }
func postServerArchive(c *gin.Context) { func postServerArchive(c *gin.Context) {
s := GetServer(c.Param("server")) s := ExtractServer(c)
go func(s *server.Server) { go func(s *server.Server) {
r := api.New() r := api.New()
l := log.WithField("server", s.Id())
// Attempt to get an archive of the server. This **WILL NOT** modify the source files of a server, // This function automatically adds the Source Node prefix and Timestamp to the log
// this process is 100% safe and will not corrupt a server's files if it fails. // output before sending it over the websocket.
if err := s.Archiver.Archive(); err != nil { sendTransferLog := func(data string) {
s.Log().WithField("error", err).Error("failed to get archive for server") output := colorstring.Color(fmt.Sprintf("[yellow][bold]%s [Pterodactyl Transfer System] [Source Node]:[default] %s", time.Now().Format(time.RFC1123), data))
s.Events().Publish(server.TransferLogsEvent, output)
if err := r.SendArchiveStatus(s.Id(), false); err != nil {
if !api.IsRequestError(err) {
s.Log().WithField("error", err).Error("failed to notify panel of failed archive status")
return
}
s.Log().WithField("error", err.Error()).Error("panel returned an error when notifying it of a failed archive status")
return
}
s.Log().Info("successfully notified panel of failed archive status")
return
} }
s.Log().Debug("successfully created server archive, notifying panel") s.Events().Publish(server.TransferStatusEvent, "starting")
sendTransferLog("Attempting to archive server...")
if err := r.SendArchiveStatus(s.Id(), true); err != nil {
if !api.IsRequestError(err) {
s.Log().WithField("error", err).Error("failed to notify panel of successful archive status")
return
}
s.Log().WithField("error", err.Error()).Error("panel returned an error when notifying it of a successful archive status")
return
}
s.Log().Info("successfully notified panel of successful archive status")
}(s)
c.Status(http.StatusAccepted)
}
func postTransfer(c *gin.Context) {
buf := bytes.Buffer{}
buf.ReadFrom(c.Request.Body)
go func(data []byte) {
serverID, _ := jsonparser.GetString(data, "server_id")
url, _ := jsonparser.GetString(data, "url")
token, _ := jsonparser.GetString(data, "token")
l := log.WithField("server", serverID)
// Create an http client with no timeout.
client := &http.Client{Timeout: 0}
hasError := true hasError := true
defer func() { defer func() {
@@ -152,157 +129,342 @@ func postTransfer(c *gin.Context) {
return return
} }
l.Info("server transfer failed, notifying panel") // Mark the server as not being transferred so it can actually be used.
if err := api.New().SendTransferFailure(serverID); err != nil { s.SetTransferring(false)
s.Events().Publish(server.TransferStatusEvent, "failure")
sendTransferLog("Attempting to notify panel of archive failure..")
if err := r.SendArchiveStatus(s.Id(), false); err != nil {
if !api.IsRequestError(err) { if !api.IsRequestError(err) {
l.WithField("error", err).Error("failed to notify panel with transfer failure") sendTransferLog("Failed to notify panel of archive failure: " + err.Error())
l.WithField("error", err).Error("failed to notify panel of failed archive status")
return return
} }
l.WithField("error", err.Error()).Error("received error response from panel while notifying of transfer failure") sendTransferLog("Panel returned an error while notifying it of a failed archive: " + err.Error())
l.WithField("error", err.Error()).Error("panel returned an error when notifying it of a failed archive status")
return return
} }
l.Debug("notified panel of transfer failure") sendTransferLog("Successfully notified panel of failed archive status")
l.Info("successfully notified panel of failed archive status")
}() }()
// Make a new GET request to the URL the panel gave us. // Mark the server as transferring to prevent problems.
req, err := http.NewRequest("GET", url, nil) s.SetTransferring(true)
if err != nil {
log.WithField("error", err).Error("failed to create http request for archive transfer") // Ensure the server is offline. Sometimes a "No such container" error gets through
// which means the server is already stopped. We can ignore that.
if err := s.Environment.WaitForStop(60, false); err != nil && !strings.Contains(strings.ToLower(err.Error()), "no such container") {
sendTransferLog("Failed to stop server, aborting transfer..")
l.WithField("error", err).Error("failed to stop server")
return return
} }
// Add the authorization header. // Attempt to get an archive of the server.
req.Header.Set("Authorization", token) if err := s.Archiver.Archive(); err != nil {
sendTransferLog("An error occurred while archiving the server: " + err.Error())
l.WithField("error", err).Error("failed to get transfer archive for server")
return
}
// Execute the http request. sendTransferLog("Successfully created archive, attempting to notify panel..")
res, err := client.Do(req) l.Info("successfully created server transfer archive, notifying panel..")
if err := r.SendArchiveStatus(s.Id(), true); err != nil {
if !api.IsRequestError(err) {
sendTransferLog("Failed to notify panel of archive success: " + err.Error())
l.WithField("error", err).Error("failed to notify panel of successful archive status")
return
}
sendTransferLog("Panel returned an error while notifying it of a successful archive: " + err.Error())
l.WithField("error", err.Error()).Error("panel returned an error when notifying it of a successful archive status")
return
}
hasError = false
// This log may not be displayed by the client due to the status event being sent before or at the same time.
sendTransferLog("Successfully notified panel of successful archive status")
l.Info("successfully notified panel of successful transfer archive status")
s.Events().Publish(server.TransferStatusEvent, "archived")
}(s)
c.Status(http.StatusAccepted)
}
func (w *downloadProgress) Write(v []byte) (int, error) {
n := len(v)
atomic.AddInt64(&w.progress, int64(n))
return n, nil
}
// Log helper function to attach all errors and info output to a consistently formatted
// log string for easier querying.
func (str serverTransferRequest) log() *log.Entry {
return log.WithField("subsystem", "transfers").WithField("server_id", str.ServerID)
}
// Downloads an archive from the machine that the server currently lives on.
func (str serverTransferRequest) downloadArchive() (*http.Response, error) {
client := http.Client{Timeout: 0}
req, err := http.NewRequest(http.MethodGet, str.URL, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", str.Token)
res, err := client.Do(req) // lgtm [go/request-forgery]
if err != nil {
return nil, err
}
return res, nil
}
// Returns the path to the local archive on the system.
func (str serverTransferRequest) path() string {
return filepath.Join(config.Get().System.ArchiveDirectory, str.ServerID+".tar.gz")
}
// Creates the archive location on this machine by first checking that the required file
// does not already exist. If it does exist, the file is deleted and then re-created as
// an empty file.
func (str serverTransferRequest) createArchiveFile() (*os.File, error) {
p := str.path()
if _, err := os.Stat(p); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
} else if err := os.Remove(p); err != nil {
return nil, err
}
return os.Create(p)
}
// Deletes the archive from the local filesystem. This is executed as a deferred function.
func (str serverTransferRequest) removeArchivePath() {
p := str.path()
str.log().Debug("deleting temporary transfer archive")
if err := os.Remove(p); err != nil && !os.IsNotExist(err) {
str.log().WithField("path", p).WithField("error", err).Error("failed to delete temporary transfer archive file")
return
}
str.log().Debug("deleted temporary transfer archive successfully")
}
// Verifies that the SHA-256 checksum of the file on the local filesystem matches the
// expected value from the transfer request. The string value returned is the computed
// checksum on the system.
func (str serverTransferRequest) verifyChecksum(matches string) (bool, string, error) {
file, err := os.Open(str.path())
if err != nil {
return false, "", err
}
defer file.Close()
hash := sha256.New()
buf := make([]byte, 1024*4)
if _, err := io.CopyBuffer(hash, file, buf); err != nil {
return false, "", err
}
checksum := hex.EncodeToString(hash.Sum(nil))
return checksum == matches, checksum, nil
}
// Sends a notification to the Panel letting it know what the status of this transfer is.
func (str serverTransferRequest) sendTransferStatus(successful bool) error {
lg := str.log().WithField("transfer_successful", successful)
lg.Info("notifying Panel of server transfer state")
if err := api.New().SendTransferStatus(str.ServerID, successful); err != nil {
lg.WithField("error", err).Error("error notifying panel of transfer state")
return err
}
lg.Debug("notified panel of transfer state")
return nil
}
// Initiates a transfer between two nodes for a server by downloading an archive from the
// remote node and then applying the server details to this machine.
func postTransfer(c *gin.Context) {
var data serverTransferRequest
if err := c.BindJSON(&data); err != nil {
return
}
u, err := uuid.Parse(data.ServerID)
if err != nil {
WithError(c, err)
return
}
// Force the server ID to be a valid UUID string at this point. If it is not an error
// is returned to the caller. This limits injection vulnerabilities that would cause
// the str.path() function to return a location not within the server archive directory.
data.ServerID = u.String()
data.log().Info("handling incoming server transfer request")
go func(data *serverTransferRequest) {
hasError := true
// Create a new server installer. This will only configure the environment and not
// run the installer scripts.
i, err := installer.New(data.Server)
if err != nil { if err != nil {
l.WithField("error", err).Error("failed to send archive http request") _ = data.sendTransferStatus(false)
data.log().WithField("error", err).Error("failed to validate received server data")
return
}
// This function automatically adds the Target Node prefix and Timestamp to the log output before sending it
// over the websocket.
sendTransferLog := func(data string) {
output := colorstring.Color(fmt.Sprintf("[yellow][bold]%s [Pterodactyl Transfer System] [Target Node]:[default] %s", time.Now().Format(time.RFC1123), data))
i.Server().Events().Publish(server.TransferLogsEvent, output)
}
// Mark the server as transferring to prevent problems later on during the process and
// then push the server into the global server collection for this instance.
i.Server().SetTransferring(true)
server.GetServers().Add(i.Server())
defer func(s *server.Server) {
// In the event that this transfer call fails, remove the server from the global
// server tracking so that we don't have a dangling instance.
if err := data.sendTransferStatus(!hasError); hasError || err != nil {
sendTransferLog("Server transfer failed, check Wings logs for additional information.")
s.Events().Publish(server.TransferStatusEvent, "failure")
server.GetServers().Remove(func(s2 *server.Server) bool {
return s.Id() == s2.Id()
})
// If the transfer status was successful but the request failed, act like the transfer failed.
if !hasError && err != nil {
// Delete all extracted files.
if err := os.RemoveAll(s.Filesystem().Path()); err != nil && !os.IsNotExist(err) {
data.log().WithField("error", err).Warn("failed to delete local server files directory")
}
}
} else {
s.SetTransferring(false)
s.Events().Publish(server.TransferStatusEvent, "success")
sendTransferLog("Transfer completed.")
}
}(i.Server())
data.log().Info("downloading server archive from current server node")
sendTransferLog("Received incoming transfer from Panel, attempting to download archive from source node...")
res, err := data.downloadArchive()
if err != nil {
sendTransferLog("Failed to retrieve server archive from remote node: " + err.Error())
data.log().WithField("error", err).Error("failed to download archive for server transfer")
return return
} }
defer res.Body.Close() defer res.Body.Close()
// Handle non-200 status codes.
if res.StatusCode != 200 { if res.StatusCode != 200 {
_, err := ioutil.ReadAll(res.Body) data.log().WithField("error", err).WithField("status", res.StatusCode).Error("unexpected error response from transfer endpoint")
if err != nil {
l.WithField("error", err).WithField("status", res.StatusCode).Error("failed read transfer response body")
return
}
l.WithField("error", err).WithField("status", res.StatusCode).Error("failed to request server archive")
return return
} }
// Get the path to the archive. size := res.ContentLength
archivePath := filepath.Join(config.Get().System.ArchiveDirectory, serverID+".tar.gz") if size == 0 {
data.log().WithField("error", err).Error("received an archive response with Content-Length of 0")
// Check if the archive already exists and delete it if it does. return
if _, err = os.Stat(archivePath); err != nil {
if !os.IsNotExist(err) {
l.WithField("error", err).Error("failed to stat archive file")
return
}
} else {
if err := os.Remove(archivePath); err != nil {
l.WithField("error", err).Warn("failed to remove old archive file")
return
}
} }
sendTransferLog("Got server archive response from remote node. (Content-Length: " + strconv.Itoa(int(size)) + ")")
// Create the file. sendTransferLog("Creating local archive file...")
file, err := os.Create(archivePath) file, err := data.createArchiveFile()
if err != nil { if err != nil {
l.WithField("error", err).Error("failed to open archive on disk") data.log().WithField("error", err).Error("failed to create archive file on local filesystem")
return return
} }
sendTransferLog("Writing archive to disk...")
data.log().Info("writing transfer archive to disk...")
// Copy the file. // Copy the file.
buf := make([]byte, 1024*4) progress := &downloadProgress{size: size}
_, err = io.CopyBuffer(file, res.Body, buf) ticker := time.NewTicker(3 * time.Second)
if err != nil { go func(progress *downloadProgress, t *time.Ticker) {
l.WithField("error", err).Error("failed to copy archive file to disk") for range ticker.C {
return // p = 100 (Downloaded)
} // size = 1000 (Content-Length)
// p / size = 0.1
// Close the file so it can be opened to verify the checksum. // * 100 = 10% (Multiply by 100 to get a percentage of the download)
if err := file.Close(); err != nil { // 10% / tickPercentage = (10% / (100 / 25)) (Divide by tick percentage to get the number of ticks)
l.WithField("error", err).Error("failed to close archive file") // 2.5 (Number of ticks as a float64)
return // 2 (convert to an integer)
} p := atomic.LoadInt64(&progress.progress)
// We have to cast these numbers to float in order to get a float result from the division.
// Whenever the transfer fails or succeeds, delete the temporary transfer archive. width := ((float64(p) / float64(size)) * 100) / tickPercentage
defer func() { bar := strings.Repeat("=", int(width)) + strings.Repeat(" ", ticks-int(width))
log.WithField("server", serverID).Debug("deleting temporary transfer archive..") sendTransferLog("Downloading [" + bar + "] " + system.FormatBytes(p) + " / " + system.FormatBytes(progress.size))
if err := os.Remove(archivePath); err != nil && !os.IsNotExist(err) {
l.WithFields(log.Fields{
"server": serverID,
"error": err,
}).Warn("failed to delete transfer archive")
} else {
l.Debug("deleted temporary transfer archive successfully")
} }
}() }(progress, ticker)
l.Debug("server archive downloaded, computing checksum...") var reader io.Reader
downloadLimit := float64(config.Get().System.Transfers.DownloadLimit) * 1024 * 1024
// Open the archive file for computing a checksum. if downloadLimit > 0 {
file, err = os.Open(archivePath) // Wrap the body with a reader that is limited to the defined download limit speed.
if err != nil { reader = ratelimit.Reader(res.Body, ratelimit.NewBucketWithRate(downloadLimit, int64(downloadLimit)))
l.WithField("error", err).Error("failed to open archive on disk") } else {
return reader = res.Body
} }
// Compute the sha256 checksum of the file. buf := make([]byte, 1024*4)
hash := sha256.New() if _, err := io.CopyBuffer(file, io.TeeReader(reader, progress), buf); err != nil {
buf = make([]byte, 1024*4) ticker.Stop()
if _, err := io.CopyBuffer(hash, file, buf); err != nil { _ = file.Close()
l.WithField("error", err).Error("failed to copy archive file for checksum verification")
sendTransferLog("Failed while writing archive file to disk: " + err.Error())
data.log().WithField("error", err).Error("failed to copy archive file to disk")
return return
} }
ticker.Stop()
// Verify the two checksums. // Show 100% completion.
if hex.EncodeToString(hash.Sum(nil)) != res.Header.Get("X-Checksum") { humanSize := system.FormatBytes(progress.size)
l.Error("checksum verification failed for archive") sendTransferLog("Downloading [" + strings.Repeat("=", ticks) + "] " + humanSize + " / " + humanSize)
return
}
// Close the file.
if err := file.Close(); err != nil { if err := file.Close(); err != nil {
l.WithField("error", err).Error("failed to close archive file after calculating checksum") data.log().WithField("error", err).Error("unable to close archive file on local filesystem")
return
}
data.log().Info("finished writing transfer archive to disk")
sendTransferLog("Successfully wrote archive to disk.")
// Whenever the transfer fails or succeeds, delete the temporary transfer archive that
// was created on the disk.
defer data.removeArchivePath()
sendTransferLog("Verifying checksum of downloaded archive...")
data.log().Info("computing checksum of downloaded archive file")
expected := res.Header.Get("X-Checksum")
if matches, computed, err := data.verifyChecksum(expected); err != nil {
data.log().WithField("error", err).Error("encountered an error while calculating local filesystem archive checksum")
return
} else if !matches {
sendTransferLog("@@@@@ CHECKSUM VERIFICATION FAILED @@@@@")
sendTransferLog(" - Source Checksum: " + expected)
sendTransferLog(" - Computed Checksum: " + computed)
data.log().WithField("expected_sum", expected).WithField("computed_checksum", computed).Error("checksum mismatch when verifying integrity of local archive")
return return
} }
l.Info("server archive transfer was successful") // Create the server's environment.
sendTransferLog("Creating server environment, this could take a while..")
// Get the server data from the request. data.log().Info("creating server environment")
serverData, t, _, _ := jsonparser.Get(data, "server")
if t != jsonparser.Object {
l.Error("invalid server data passed in request")
return
}
// Create a new server installer (note this does not execute the install script)
i, err := installer.New(serverData)
if err != nil {
l.WithField("error", err).Error("failed to validate received server data")
return
}
// Add the server to the collection.
server.GetServers().Add(i.Server())
// Create the server's environment (note this does not execute the install script)
if err := i.Server().CreateEnvironment(); err != nil { if err := i.Server().CreateEnvironment(); err != nil {
l.WithField("error", err).Error("failed to create server environment") data.log().WithField("error", err).Error("failed to create server environment")
return return
} }
// Un-archive the archive, that sounds weird.. sendTransferLog("Server environment has been created, extracting transfer archive..")
if err := archiver.NewTarGz().Unarchive(archivePath, i.Server().Filesystem().Path()); err != nil { data.log().Info("server environment configured, extracting transfer archive")
l.WithField("error", err).Error("failed to extract server archive") if err := archiver.NewTarGz().Unarchive(data.path(), i.Server().Filesystem().Path()); err != nil {
// Un-archiving failed, delete the server's data directory.
if err := os.RemoveAll(i.Server().Filesystem().Path()); err != nil && !os.IsNotExist(err) {
data.log().WithField("error", err).Warn("failed to delete local server files directory")
}
data.log().WithField("error", err).Error("failed to extract server archive")
return return
} }
@@ -313,20 +475,9 @@ func postTransfer(c *gin.Context) {
// hiccup or the fix of whatever error causing the success request to fail. // hiccup or the fix of whatever error causing the success request to fail.
hasError = false hasError = false
// Notify the panel that the transfer succeeded. data.log().Info("archive extracted successfully, notifying Panel of status")
err = api.New().SendTransferSuccess(serverID) sendTransferLog("Archive extracted successfully.")
if err != nil { }(&data)
if !api.IsRequestError(err) {
l.WithField("error", err).Error("failed to notify panel of transfer success")
return
}
l.WithField("error", err.Error()).Error("panel responded with error after transfer success")
return
}
l.Info("successfully notified panel of transfer success")
}(buf.Bytes())
c.Status(http.StatusAccepted) c.Status(http.StatusAccepted)
} }

View File

@@ -45,6 +45,8 @@ var e = []string{
server.InstallCompletedEvent, server.InstallCompletedEvent,
server.DaemonMessageEvent, server.DaemonMessageEvent,
server.BackupCompletedEvent, server.BackupCompletedEvent,
server.TransferLogsEvent,
server.TransferStatusEvent,
} }
// Listens for different events happening on a server and sends them along // Listens for different events happening on a server and sends them along

View File

@@ -2,13 +2,13 @@ package websocket
import ( import (
"context" "context"
"emperror.dev/errors"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/apex/log" "github.com/apex/log"
"github.com/gbrlsnchs/jwt/v3" "github.com/gbrlsnchs/jwt/v3"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/environment/docker" "github.com/pterodactyl/wings/environment/docker"
@@ -28,15 +28,16 @@ const (
PermissionSendPowerRestart = "control.restart" PermissionSendPowerRestart = "control.restart"
PermissionReceiveErrors = "admin.websocket.errors" PermissionReceiveErrors = "admin.websocket.errors"
PermissionReceiveInstall = "admin.websocket.install" PermissionReceiveInstall = "admin.websocket.install"
PermissionReceiveTransfer = "admin.websocket.transfer"
PermissionReceiveBackups = "backup.read" PermissionReceiveBackups = "backup.read"
) )
type Handler struct { type Handler struct {
sync.RWMutex `json:"-"` sync.RWMutex `json:"-"`
Connection *websocket.Conn `json:"-"` Connection *websocket.Conn `json:"-"`
jwt *tokens.WebsocketPayload jwt *tokens.WebsocketPayload
server *server.Server server *server.Server
uuid uuid.UUID uuid uuid.UUID
} }
var ( var (
@@ -83,19 +84,11 @@ func GetHandler(s *server.Server, w http.ResponseWriter, r *http.Request) (*Hand
if o == config.Get().PanelLocation { if o == config.Get().PanelLocation {
return true return true
} }
for _, origin := range config.Get().AllowedOrigins { for _, origin := range config.Get().AllowedOrigins {
if origin == "*" { if origin == "*" || origin == o {
return true return true
} }
if o != origin {
continue
}
return true
} }
return false return false
}, },
} }
@@ -149,6 +142,13 @@ func (h *Handler) SendJson(v *Message) error {
return nil return nil
} }
} }
// If we are sending transfer output, only send it to the user if they have the required permissions.
if v.Event == server.TransferLogsEvent {
if !j.HasPermission(PermissionReceiveTransfer) {
return nil
}
}
} }
if err := h.unsafeSendJson(v); err != nil { if err := h.unsafeSendJson(v); err != nil {
@@ -320,13 +320,15 @@ func (h *Handler) HandleInbound(m Message) error {
// Only send the current disk usage if the server is offline, if docker container is running, // Only send the current disk usage if the server is offline, if docker container is running,
// Environment#EnableResourcePolling() will send this data to all clients. // Environment#EnableResourcePolling() will send this data to all clients.
if state == environment.ProcessOfflineState { if state == environment.ProcessOfflineState {
_ = h.server.Filesystem().HasSpaceAvailable(false) if !h.server.IsInstalling() && !h.server.IsTransferring() {
_ = h.server.Filesystem().HasSpaceAvailable(false)
b, _ := json.Marshal(h.server.Proc()) b, _ := json.Marshal(h.server.Proc())
h.SendJson(&Message{ h.SendJson(&Message{
Event: server.StatsEvent, Event: server.StatsEvent,
Args: []string{string(b)}, Args: []string{string(b)},
}) })
}
} }
return nil return nil

View File

@@ -2,9 +2,9 @@ package server
import ( import (
"crypto/sha256" "crypto/sha256"
"emperror.dev/errors"
"encoding/hex" "encoding/hex"
"github.com/mholt/archiver/v3" "github.com/mholt/archiver/v3"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server/filesystem" "github.com/pterodactyl/wings/server/filesystem"
"io" "io"

View File

@@ -1,13 +1,12 @@
package server package server
import ( import (
"bufio" "emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/server/backup" "github.com/pterodactyl/wings/server/backup"
"io/ioutil"
"os" "os"
"path"
) )
// Notifies the panel of a backup's state and returns an error if one is encountered // Notifies the panel of a backup's state and returns an error if one is encountered
@@ -30,72 +29,51 @@ func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, suc
} }
// Get all of the ignored files for a server based on its .pteroignore file in the root. // Get all of the ignored files for a server based on its .pteroignore file in the root.
func (s *Server) getServerwideIgnoredFiles() ([]string, error) { func (s *Server) getServerwideIgnoredFiles() (string, error) {
var ignored []string f, st, err := s.Filesystem().File(".pteroignore")
f, err := os.Open(path.Join(s.Filesystem().Path(), ".pteroignore"))
if err != nil { if err != nil {
if !os.IsNotExist(err) { if errors.Is(err, os.ErrNotExist) {
return nil, err return "", nil
}
} else {
scanner := bufio.NewScanner(f)
for scanner.Scan() {
// Only include non-empty lines, for the sake of clarity...
if t := scanner.Text(); t != "" {
ignored = append(ignored, t)
}
}
if err := scanner.Err(); err != nil {
return nil, err
} }
return "", err
} }
defer f.Close()
return ignored, nil if st.Mode()&os.ModeSymlink != 0 || st.Size() > 32*1024 {
} // Don't read a symlinked ignore file, or a file larger than 32KiB in size.
return "", nil
// Get the backup files to include when generating it.
func (s *Server) GetIncludedBackupFiles(ignored []string) (*backup.IncludedFiles, error) {
// If no ignored files are present in the request, check for a .pteroignore file in the root
// of the server files directory, and use that to generate the backup.
if len(ignored) == 0 {
if i, err := s.getServerwideIgnoredFiles(); err != nil {
s.Log().WithField("error", err).Warn("failed to retrieve ignored files listing for server")
} else {
ignored = i
}
} }
b, err := ioutil.ReadAll(f)
// Get the included files based on the root path and the ignored files provided. if err != nil {
return s.Filesystem().GetIncludedFiles(s.Filesystem().Path(), ignored) return "", err
}
return string(b), nil
} }
// Performs a server backup and then emits the event over the server websocket. We // Performs a server backup and then emits the event over the server websocket. We
// let the actual backup system handle notifying the panel of the status, but that // let the actual backup system handle notifying the panel of the status, but that
// won't emit a websocket event. // won't emit a websocket event.
func (s *Server) Backup(b backup.BackupInterface) error { func (s *Server) Backup(b backup.BackupInterface) error {
// Get the included files based on the root path and the ignored files provided. ignored := b.Ignored()
inc, err := s.GetIncludedBackupFiles(b.Ignored()) if b.Ignored() == "" {
if err != nil { if i, err := s.getServerwideIgnoredFiles(); err != nil {
return err log.WithField("server", s.Id()).WithField("error", err).Warn("failed to get server-wide ignored files")
} else {
ignored = i
}
} }
ad, err := b.Generate(inc, s.Filesystem().Path()) ad, err := b.Generate(s.Filesystem().Path(), ignored)
if err != nil { if err != nil {
if notifyError := s.notifyPanelOfBackup(b.Identifier(), &backup.ArchiveDetails{}, false); notifyError != nil { if err := s.notifyPanelOfBackup(b.Identifier(), &backup.ArchiveDetails{}, false); err != nil {
s.Log().WithFields(log.Fields{
"backup": b.Identifier(),
"error": notifyError,
}).Warn("failed to notify panel of failed backup state")
} else {
s.Log().WithFields(log.Fields{ s.Log().WithFields(log.Fields{
"backup": b.Identifier(), "backup": b.Identifier(),
"error": err, "error": err,
}).Info("notified panel of failed backup state") }).Warn("failed to notify panel of failed backup state")
} else {
s.Log().WithField("backup", b.Identifier()).Info("notified panel of failed backup state")
} }
s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{ _ = s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{
"uuid": b.Identifier(), "uuid": b.Identifier(),
"is_successful": false, "is_successful": false,
"checksum": "", "checksum": "",
@@ -103,13 +81,14 @@ func (s *Server) Backup(b backup.BackupInterface) error {
"file_size": 0, "file_size": 0,
}) })
return errors.WithMessage(err, "backup: error while generating server backup") return errors.WrapIf(err, "backup: error while generating server backup")
} }
// Try to notify the panel about the status of this backup. If for some reason this request // Try to notify the panel about the status of this backup. If for some reason this request
// fails, delete the archive from the daemon and return that error up the chain to the caller. // fails, delete the archive from the daemon and return that error up the chain to the caller.
if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil { if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil {
b.Remove() _ = b.Remove()
s.Log().WithField("error", notifyError).Info("failed to notify panel of successful backup state") s.Log().WithField("error", notifyError).Info("failed to notify panel of successful backup state")
return err return err
} else { } else {
@@ -118,7 +97,7 @@ func (s *Server) Backup(b backup.BackupInterface) error {
// Emit an event over the socket so we can update the backup in realtime on // Emit an event over the socket so we can update the backup in realtime on
// the frontend for the server. // the frontend for the server.
s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{ _ = s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{
"uuid": b.Identifier(), "uuid": b.Identifier(),
"is_successful": true, "is_successful": true,
"checksum": ad.Checksum, "checksum": ad.Checksum,

View File

@@ -1,130 +0,0 @@
package backup
import (
"archive/tar"
"context"
"github.com/apex/log"
gzip "github.com/klauspost/pgzip"
"github.com/pkg/errors"
"github.com/remeh/sizedwaitgroup"
"golang.org/x/sync/errgroup"
"io"
"os"
"runtime"
"strings"
"sync"
)
type Archive struct {
sync.Mutex
TrimPrefix string
Files *IncludedFiles
}
// Creates an archive at dst with all of the files defined in the included files struct.
func (a *Archive) Create(dst string, ctx context.Context) error {
f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer f.Close()
maxCpu := runtime.NumCPU() / 2
if maxCpu > 4 {
maxCpu = 4
}
gzw, _ := gzip.NewWriterLevel(f, gzip.BestSpeed)
_ = gzw.SetConcurrency(1<<20, maxCpu)
defer gzw.Flush()
defer gzw.Close()
tw := tar.NewWriter(gzw)
defer tw.Flush()
defer tw.Close()
wg := sizedwaitgroup.New(10)
g, ctx := errgroup.WithContext(ctx)
// Iterate over all of the files to be included and put them into the archive. This is
// done as a concurrent goroutine to speed things along. If an error is encountered at
// any step, the entire process is aborted.
for _, p := range a.Files.All() {
p := p
g.Go(func() error {
wg.Add()
defer wg.Done()
select {
case <-ctx.Done():
return ctx.Err()
default:
return a.addToArchive(p, tw)
}
})
}
// Block until the entire routine is completed.
if err := g.Wait(); err != nil {
f.Close()
// Attempt to remove the archive if there is an error, report that error to
// the logger if it fails.
if rerr := os.Remove(dst); rerr != nil && !os.IsNotExist(rerr) {
log.WithField("location", dst).Warn("failed to delete corrupted backup archive")
}
return err
}
return nil
}
// Adds a single file to the existing tar archive writer.
func (a *Archive) addToArchive(p string, w *tar.Writer) error {
f, err := os.Open(p)
if err != nil {
// If you try to backup something that no longer exists (got deleted somewhere during the process
// but not by this process), just skip over it and don't kill the entire backup.
if os.IsNotExist(err) {
return nil
}
return err
}
defer f.Close()
s, err := f.Stat()
if err != nil {
// Same as above, don't kill the process just because the file no longer exists.
if os.IsNotExist(err) {
return nil
}
return err
}
name := strings.TrimPrefix(p, a.TrimPrefix)
header, err := tar.FileInfoHeader(s, name)
if err != nil {
return errors.WithMessage(err, "failed to get tar#FileInfoHeader for "+name)
}
header.Name = name
// These actions must occur sequentially, even if this function is called multiple
// in parallel. You'll get some nasty panic's otherwise.
a.Lock()
defer a.Unlock()
if err := w.WriteHeader(header); err != nil {
return err
}
buf := make([]byte, 4*1024)
if _, err := io.CopyBuffer(w, io.LimitReader(f, header.Size), buf); err != nil {
return errors.WithMessage(err, "failed to copy "+header.Name+" to archive")
}
return nil
}

View File

@@ -12,9 +12,11 @@ import (
"sync" "sync"
) )
type AdapterType string
const ( const (
LocalBackupAdapter = "wings" LocalBackupAdapter AdapterType = "wings"
S3BackupAdapter = "s3" S3BackupAdapter AdapterType = "s3"
) )
type ArchiveDetails struct { type ArchiveDetails struct {
@@ -40,7 +42,10 @@ type Backup struct {
// An array of files to ignore when generating this backup. This should be // An array of files to ignore when generating this backup. This should be
// compatible with a standard .gitignore structure. // compatible with a standard .gitignore structure.
IgnoredFiles []string `json:"ignored_files"` Ignore string `json:"ignore"`
adapter AdapterType
logContext map[string]interface{}
} }
// noinspection GoNameStartsWithPackageName // noinspection GoNameStartsWithPackageName
@@ -48,14 +53,17 @@ type BackupInterface interface {
// Returns the UUID of this backup as tracked by the panel instance. // Returns the UUID of this backup as tracked by the panel instance.
Identifier() string Identifier() string
// Attaches additional context to the log output for this backup.
WithLogContext(map[string]interface{})
// Generates a backup in whatever the configured source for the specific // Generates a backup in whatever the configured source for the specific
// implementation is. // implementation is.
Generate(*IncludedFiles, string) (*ArchiveDetails, error) Generate(string, string) (*ArchiveDetails, error)
// Returns the ignored files for this backup instance. // Returns the ignored files for this backup instance.
Ignored() []string Ignored() string
// Returns a SHA256 checksum for the generated backup. // Returns a SHA1 checksum for the generated backup.
Checksum() ([]byte, error) Checksum() ([]byte, error)
// Returns the size of the generated backup. // Returns the size of the generated backup.
@@ -116,11 +124,14 @@ func (b *Backup) Details() *ArchiveDetails {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(2) wg.Add(2)
l := log.WithField("backup_id", b.Uuid)
var checksum string var checksum string
// Calculate the checksum for the file. // Calculate the checksum for the file.
go func() { go func() {
defer wg.Done() defer wg.Done()
l.Info("computing checksum for backup...")
resp, err := b.Checksum() resp, err := b.Checksum()
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
@@ -131,6 +142,7 @@ func (b *Backup) Details() *ArchiveDetails {
} }
checksum = hex.EncodeToString(resp) checksum = hex.EncodeToString(resp)
l.WithField("checksum", checksum).Info("computed checksum for backup")
}() }()
var sz int64 var sz int64
@@ -153,6 +165,16 @@ func (b *Backup) Details() *ArchiveDetails {
} }
} }
func (b *Backup) Ignored() []string { func (b *Backup) Ignored() string {
return b.IgnoredFiles return b.Ignore
}
// Returns a logger instance for this backup with the additional context fields
// assigned to the output.
func (b *Backup) log() *log.Entry {
l := log.WithField("backup", b.Identifier()).WithField("adapter", b.adapter)
for k, v := range b.logContext {
l = l.WithField(k, v)
}
return l
} }

View File

@@ -1,8 +1,8 @@
package backup package backup
import ( import (
"context"
"errors" "errors"
"github.com/pterodactyl/wings/server/filesystem"
"os" "os"
) )
@@ -17,8 +17,8 @@ var _ BackupInterface = (*LocalBackup)(nil)
func LocateLocal(uuid string) (*LocalBackup, os.FileInfo, error) { func LocateLocal(uuid string) (*LocalBackup, os.FileInfo, error) {
b := &LocalBackup{ b := &LocalBackup{
Backup{ Backup{
Uuid: uuid, Uuid: uuid,
IgnoredFiles: nil, Ignore: "",
}, },
} }
@@ -39,17 +39,24 @@ func (b *LocalBackup) Remove() error {
return os.Remove(b.Path()) return os.Remove(b.Path())
} }
// Attaches additional context to the log output for this backup.
func (b *LocalBackup) WithLogContext(c map[string]interface{}) {
b.logContext = c
}
// Generates a backup of the selected files and pushes it to the defined location // Generates a backup of the selected files and pushes it to the defined location
// for this instance. // for this instance.
func (b *LocalBackup) Generate(included *IncludedFiles, prefix string) (*ArchiveDetails, error) { func (b *LocalBackup) Generate(basePath, ignore string) (*ArchiveDetails, error) {
a := &Archive{ a := &filesystem.Archive{
TrimPrefix: prefix, BasePath: basePath,
Files: included, Ignore: ignore,
} }
if err := a.Create(b.Path(), context.Background()); err != nil { b.log().Info("creating backup for server...")
if err := a.Create(b.Path()); err != nil {
return nil, err return nil, err
} }
b.log().Info("created backup successfully")
return b.Details(), nil return b.Details(), nil
} }

View File

@@ -6,9 +6,9 @@ import (
) )
type Request struct { type Request struct {
Adapter string `json:"adapter"` Adapter AdapterType `json:"adapter"`
Uuid string `json:"uuid"` Uuid string `json:"uuid"`
IgnoredFiles []string `json:"ignored_files"` Ignore string `json:"ignore"`
} }
// Generates a new local backup struct. // Generates a new local backup struct.
@@ -19,8 +19,9 @@ func (r *Request) NewLocalBackup() (*LocalBackup, error) {
return &LocalBackup{ return &LocalBackup{
Backup{ Backup{
Uuid: r.Uuid, Uuid: r.Uuid,
IgnoredFiles: r.IgnoredFiles, Ignore: r.Ignore,
adapter: LocalBackupAdapter,
}, },
}, nil }, nil
} }
@@ -33,8 +34,9 @@ func (r *Request) NewS3Backup() (*S3Backup, error) {
return &S3Backup{ return &S3Backup{
Backup: Backup{ Backup: Backup{
Uuid: r.Uuid, Uuid: r.Uuid,
IgnoredFiles: r.IgnoredFiles, Ignore: r.Ignore,
adapter: S3BackupAdapter,
}, },
}, nil }, nil
} }

View File

@@ -1,10 +1,9 @@
package backup package backup
import ( import (
"context"
"fmt" "fmt"
"github.com/apex/log"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/server/filesystem"
"io" "io"
"net/http" "net/http"
"os" "os"
@@ -17,19 +16,31 @@ type S3Backup struct {
var _ BackupInterface = (*S3Backup)(nil) var _ BackupInterface = (*S3Backup)(nil)
// Removes a backup from the system.
func (s *S3Backup) Remove() error {
return os.Remove(s.Path())
}
// Attaches additional context to the log output for this backup.
func (s *S3Backup) WithLogContext(c map[string]interface{}) {
s.logContext = c
}
// Generates a new backup on the disk, moves it into the S3 bucket via the provided // Generates a new backup on the disk, moves it into the S3 bucket via the provided
// presigned URL, and then deletes the backup from the disk. // presigned URL, and then deletes the backup from the disk.
func (s *S3Backup) Generate(included *IncludedFiles, prefix string) (*ArchiveDetails, error) { func (s *S3Backup) Generate(basePath, ignore string) (*ArchiveDetails, error) {
defer s.Remove() defer s.Remove()
a := &Archive{ a := &filesystem.Archive{
TrimPrefix: prefix, BasePath: basePath,
Files: included, Ignore: ignore,
} }
if err := a.Create(s.Path(), context.Background()); err != nil { s.log().Info("creating backup for server...")
if err := a.Create(s.Path()); err != nil {
return nil, err return nil, err
} }
s.log().Info("created backup successfully")
rc, err := os.Open(s.Path()) rc, err := os.Open(s.Path())
if err != nil { if err != nil {
@@ -44,11 +55,6 @@ func (s *S3Backup) Generate(included *IncludedFiles, prefix string) (*ArchiveDet
return s.Details(), nil return s.Details(), nil
} }
// Removes a backup from the system.
func (s *S3Backup) Remove() error {
return os.Remove(s.Path())
}
// Reader provides a wrapper around an existing io.Reader // Reader provides a wrapper around an existing io.Reader
// but implements io.Closer in order to satisfy an io.ReadCloser. // but implements io.Closer in order to satisfy an io.ReadCloser.
type Reader struct { type Reader struct {
@@ -63,22 +69,20 @@ func (Reader) Close() error {
func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error { func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
defer rc.Close() defer rc.Close()
s.log().Debug("attempting to get size of backup...")
size, err := s.Backup.Size() size, err := s.Backup.Size()
if err != nil { if err != nil {
return err return err
} }
s.log().WithField("size", size).Debug("got size of backup")
s.log().Debug("attempting to get S3 upload urls from Panel...")
urls, err := api.New().GetBackupRemoteUploadURLs(s.Backup.Uuid, size) urls, err := api.New().GetBackupRemoteUploadURLs(s.Backup.Uuid, size)
if err != nil { if err != nil {
return err return err
} }
s.log().Debug("got S3 upload urls from the Panel")
l := log.WithFields(log.Fields{ s.log().WithField("parts", len(urls.Parts)).Info("attempting to upload backup to s3 endpoint...")
"backup_id": s.Uuid,
"adapter": "s3",
})
l.Info("attempting to upload backup..")
handlePart := func(part string, size int64) (string, error) { handlePart := func(part string, size int64) (string, error) {
r, err := http.NewRequest(http.MethodPut, part, nil) r, err := http.NewRequest(http.MethodPut, part, nil)
@@ -111,11 +115,10 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
return res.Header.Get("ETag"), nil return res.Header.Get("ETag"), nil
} }
partCount := len(urls.Parts)
for i, part := range urls.Parts { for i, part := range urls.Parts {
// Get the size for the current part. // Get the size for the current part.
var partSize int64 var partSize int64
if i+1 < partCount { if i+1 < len(urls.Parts) {
partSize = urls.PartSize partSize = urls.PartSize
} else { } else {
// This is the remaining size for the last part, // This is the remaining size for the last part,
@@ -125,12 +128,14 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
// Attempt to upload the part. // Attempt to upload the part.
if _, err := handlePart(part, partSize); err != nil { if _, err := handlePart(part, partSize); err != nil {
l.WithField("part_id", part).WithError(err).Warn("failed to upload part") s.log().WithField("part_id", i+1).WithError(err).Warn("failed to upload part")
return err return err
} }
s.log().WithField("part_id", i+1).Info("successfully uploaded backup part")
} }
l.Info("backup has been successfully uploaded") s.log().WithField("parts", len(urls.Parts)).Info("backup has been successfully uploaded")
return nil return nil
} }

View File

@@ -1,25 +0,0 @@
package backup
import (
"sync"
)
type IncludedFiles struct {
sync.RWMutex
files []string
}
// Pushes an additional file or folder onto the struct.
func (i *IncludedFiles) Push(p string) {
i.Lock()
i.files = append(i.files, p) // ~~
i.Unlock()
}
// Returns all of the files that were marked as being included.
func (i *IncludedFiles) All() []string {
i.RLock()
defer i.RUnlock()
return i.files
}

View File

@@ -60,6 +60,8 @@ func (c *Collection) Find(filter func(*Server) bool) *Server {
} }
// Removes all items from the collection that match the filter function. // Removes all items from the collection that match the filter function.
//
// TODO: cancel the context?
func (c *Collection) Remove(filter func(*Server) bool) { func (c *Collection) Remove(filter func(*Server) bool) {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()

View File

@@ -2,9 +2,9 @@ package server
import ( import (
"context" "context"
"emperror.dev/errors"
"fmt" "fmt"
"github.com/mitchellh/colorstring" "github.com/mitchellh/colorstring"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
"sync" "sync"
@@ -26,7 +26,7 @@ type ConsoleThrottler struct {
// Wether or not the console output is being throttled. It is up to calling code to // Wether or not the console output is being throttled. It is up to calling code to
// determine what to do if it is. // determine what to do if it is.
isThrottled system.AtomicBool isThrottled *system.AtomicBool
// The total number of lines processed so far during the given time period. // The total number of lines processed so far during the given time period.
timerCancel *context.CancelFunc timerCancel *context.CancelFunc
@@ -36,7 +36,7 @@ type ConsoleThrottler struct {
func (ct *ConsoleThrottler) Reset() { func (ct *ConsoleThrottler) Reset() {
atomic.StoreUint64(&ct.count, 0) atomic.StoreUint64(&ct.count, 0)
atomic.StoreUint64(&ct.activations, 0) atomic.StoreUint64(&ct.activations, 0)
ct.isThrottled.Set(false) ct.isThrottled.Store(false)
} }
// Triggers an activation for a server. You can also decrement the number of activations // Triggers an activation for a server. You can also decrement the number of activations
@@ -57,55 +57,21 @@ func (ct *ConsoleThrottler) markActivation(increment bool) uint64 {
// Determines if the console is currently being throttled. Calls to this function can be used to // Determines if the console is currently being throttled. Calls to this function can be used to
// determine if output should be funneled along to the websocket processes. // determine if output should be funneled along to the websocket processes.
func (ct *ConsoleThrottler) Throttled() bool { func (ct *ConsoleThrottler) Throttled() bool {
return ct.isThrottled.Get() return ct.isThrottled.Load()
} }
// Starts a timer that runs in a seperate thread and will continually decrement the lines processed // Starts a timer that runs in a seperate thread and will continually decrement the lines processed
// and number of activations, regardless of the current console message volume. // and number of activations, regardless of the current console message volume. All of the timers
func (ct *ConsoleThrottler) StartTimer() { // are canceled if the context passed through is canceled.
ctx, cancel := context.WithCancel(context.Background()) func (ct *ConsoleThrottler) StartTimer(ctx context.Context) {
system.Every(ctx, time.Duration(int64(ct.LineResetInterval))*time.Millisecond, func(_ time.Time) {
ct.isThrottled.Store(false)
atomic.StoreUint64(&ct.count, 0)
})
reset := time.NewTicker(time.Duration(int64(ct.LineResetInterval)) * time.Millisecond) system.Every(ctx, time.Duration(int64(ct.DecayInterval))*time.Millisecond, func(_ time.Time) {
decay := time.NewTicker(time.Duration(int64(ct.DecayInterval)) * time.Millisecond) ct.markActivation(false)
})
go func() {
for {
select {
case <-ctx.Done():
reset.Stop()
return
case <-reset.C:
ct.isThrottled.Set(false)
atomic.StoreUint64(&ct.count, 0)
}
}
}()
go func() {
for {
select {
case <-ctx.Done():
decay.Stop()
return
case <-decay.C:
ct.markActivation(false)
}
}
}()
ct.timerCancel = &cancel
}
// Stops a running timer processes if one exists. This is only called when the server is deleted since
// we want this to always be running. If there is no process currently running nothing will really happen.
func (ct *ConsoleThrottler) StopTimer() {
ct.mu.Lock()
defer ct.mu.Unlock()
if ct.timerCancel != nil {
c := *ct.timerCancel
c()
ct.timerCancel = nil
}
} }
// Handles output from a server's console. This code ensures that a server is not outputting // Handles output from a server's console. This code ensures that a server is not outputting
@@ -133,7 +99,7 @@ func (ct *ConsoleThrottler) Increment(onTrigger func()) error {
// activation. Once the throttle is triggered and has passed the kill at value we will trigger a server // activation. Once the throttle is triggered and has passed the kill at value we will trigger a server
// stop automatically. // stop automatically.
if atomic.AddUint64(&ct.count, 1) >= ct.Lines && !ct.Throttled() { if atomic.AddUint64(&ct.count, 1) >= ct.Lines && !ct.Throttled() {
ct.isThrottled.Set(true) ct.isThrottled.Store(true)
if ct.markActivation(true) >= ct.MaximumTriggerCount { if ct.markActivation(true) >= ct.MaximumTriggerCount {
return ErrTooMuchConsoleData return ErrTooMuchConsoleData
} }
@@ -146,15 +112,12 @@ func (ct *ConsoleThrottler) Increment(onTrigger func()) error {
// Returns the throttler instance for the server or creates a new one. // Returns the throttler instance for the server or creates a new one.
func (s *Server) Throttler() *ConsoleThrottler { func (s *Server) Throttler() *ConsoleThrottler {
s.throttleLock.Lock() s.throttleOnce.Do(func() {
defer s.throttleLock.Unlock()
if s.throttler == nil {
s.throttler = &ConsoleThrottler{ s.throttler = &ConsoleThrottler{
isThrottled: system.NewAtomicBool(false),
ConsoleThrottles: config.Get().Throttles, ConsoleThrottles: config.Get().Throttles,
} }
} })
return s.throttler return s.throttler
} }

View File

@@ -1,11 +1,15 @@
package server package server
import ( import (
"github.com/pkg/errors" "emperror.dev/errors"
) )
var ErrIsRunning = errors.New("server is running") var (
var ErrSuspended = errors.New("server is currently in a suspended state") ErrIsRunning = errors.New("server is running")
ErrSuspended = errors.New("server is currently in a suspended state")
ErrServerIsInstalling = errors.New("server is currently installing")
ErrServerIsTransferring = errors.New("server is currently being transferred")
)
type crashTooFrequent struct { type crashTooFrequent struct {
} }

View File

@@ -15,6 +15,8 @@ const (
StatusEvent = "status" StatusEvent = "status"
StatsEvent = "stats" StatsEvent = "stats"
BackupCompletedEvent = "backup completed" BackupCompletedEvent = "backup completed"
TransferLogsEvent = "transfer logs"
TransferStatusEvent = "transfer status"
) )
// Returns the server's emitter instance. // Returns the server's emitter instance.

View File

@@ -0,0 +1,224 @@
package filesystem
import (
"archive/tar"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/juju/ratelimit"
"github.com/karrick/godirwalk"
"github.com/klauspost/pgzip"
"github.com/pterodactyl/wings/config"
"github.com/sabhiram/go-gitignore"
"io"
"os"
"path/filepath"
"strings"
"sync"
)
const memory = 4 * 1024
var pool = sync.Pool{
New: func() interface{} {
b := make([]byte, memory)
return b
},
}
type Archive struct {
// BasePath is the absolute path to create the archive from where Files and Ignore are
// relative to.
BasePath string
// Ignore is a gitignore string (most likely read from a file) of files to ignore
// from the archive.
Ignore string
// Files specifies the files to archive, this takes priority over the Ignore option, if
// unspecified, all files in the BasePath will be archived unless Ignore is set.
Files []string
}
// Creates an archive at dst with all of the files defined in the included files struct.
func (a *Archive) Create(dst string) error {
f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer f.Close()
// Select a writer based off of the WriteLimit configuration option. If there is no
// write limit, use the file as the writer.
var writer io.Writer
if writeLimit := int64(config.Get().System.Backups.WriteLimit * 1024 * 1024); writeLimit > 0 {
// Token bucket with a capacity of "writeLimit" MiB, adding "writeLimit" MiB/s
// and then wrap the file writer with the token bucket limiter.
writer = ratelimit.Writer(f, ratelimit.NewBucketWithRate(float64(writeLimit), writeLimit))
} else {
writer = f
}
// Create a new gzip writer around the file.
gw, _ := pgzip.NewWriterLevel(writer, pgzip.BestSpeed)
_ = gw.SetConcurrency(1<<20, 1)
defer gw.Close()
// Create a new tar writer around the gzip writer.
tw := tar.NewWriter(gw)
defer tw.Close()
// Configure godirwalk.
options := &godirwalk.Options{
FollowSymbolicLinks: false,
Unsorted: true,
Callback: a.callback(tw),
}
// If we're specifically looking for only certain files, or have requested
// that certain files be ignored we'll update the callback function to reflect
// that request.
if len(a.Files) == 0 && len(a.Ignore) > 0 {
i := ignore.CompileIgnoreLines(strings.Split(a.Ignore, "\n")...)
options.Callback = a.callback(tw, func(_ string, rp string) error {
if i.MatchesPath(rp) {
return godirwalk.SkipThis
}
return nil
})
} else if len(a.Files) > 0 {
options.Callback = a.withFilesCallback(tw)
}
// Recursively walk the path we are archiving.
return godirwalk.Walk(a.BasePath, options)
}
// Callback function used to determine if a given file should be included in the archive
// being generated.
func (a *Archive) callback(tw *tar.Writer, opts ...func(path string, relative string) error) func(path string, de *godirwalk.Dirent) error {
return func(path string, de *godirwalk.Dirent) error {
// Skip directories because we walking them recursively.
if de.IsDir() {
return nil
}
relative := filepath.ToSlash(strings.TrimPrefix(path, a.BasePath+string(filepath.Separator)))
// Call the additional options passed to this callback function. If any of them return
// a non-nil error we will exit immediately.
for _, opt := range opts {
if err := opt(path, relative); err != nil {
return err
}
}
// Add the file to the archive, if it is nested in a directory,
// the directory will be automatically "created" in the archive.
return a.addToArchive(path, relative, tw)
}
}
// Pushes only files defined in the Files key to the final archive.
func (a *Archive) withFilesCallback(tw *tar.Writer) func(path string, de *godirwalk.Dirent) error {
return a.callback(tw, func(p string, rp string) error {
for _, f := range a.Files {
// If the given doesn't match, or doesn't have the same prefix continue
// to the next item in the loop.
if p != f && !strings.HasPrefix(p, f) {
continue
}
// Once we have a match return a nil value here so that the loop stops and the
// call to this function will correctly include the file in the archive. If there
// are no matches we'll never make it to this line, and the final error returned
// will be the godirwalk.SkipThis error.
return nil
}
return godirwalk.SkipThis
})
}
// Adds a given file path to the final archive being created.
func (a *Archive) addToArchive(p string, rp string, w *tar.Writer) error {
// Lstat the file, this will give us the same information as Stat except that it will not
// follow a symlink to it's target automatically. This is important to avoid including
// files that exist outside the server root unintentionally in the backup.
s, err := os.Lstat(p)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return errors.WrapIff(err, "failed executing os.Lstat on '%s'", rp)
}
// Resolve the symlink target if the file is a symlink.
var target string
if s.Mode()&os.ModeSymlink != 0 {
// Read the target of the symlink. If there are any errors we will dump them out to
// the logs, but we're not going to stop the backup. There are far too many cases of
// symlinks causing all sorts of unnecessary pain in this process. Sucks to suck if
// it doesn't work.
target, err = os.Readlink(s.Name())
if err != nil {
// Ignore the not exist errors specifically, since theres nothing important about that.
if !os.IsNotExist(err) {
log.WithField("path", rp).WithField("readlink_err", err.Error()).Warn("failed reading symlink for target path; skipping...")
}
return nil
}
}
// Get the tar FileInfoHeader in order to add the file to the archive.
header, err := tar.FileInfoHeader(s, filepath.ToSlash(target))
if err != nil {
return errors.WrapIff(err, "failed to get tar#FileInfoHeader for '%s'", rp)
}
// Fix the header name if the file is not a symlink.
if s.Mode()&os.ModeSymlink == 0 {
header.Name = rp
}
// Write the tar FileInfoHeader to the archive.
if err := w.WriteHeader(header); err != nil {
return errors.WrapIff(err, "failed to write tar#FileInfoHeader for '%s'", rp)
}
// If the size of the file is less than 1 (most likely for symlinks), skip writing the file.
if header.Size < 1 {
return nil
}
// If the buffer size is larger than the file size, create a smaller buffer to hold the file.
var buf []byte
if header.Size < memory {
buf = make([]byte, header.Size)
} else {
// Get a fixed-size buffer from the pool to save on allocations.
buf = pool.Get().([]byte)
defer func() {
buf = make([]byte, memory)
pool.Put(buf)
}()
}
// Open the file.
f, err := os.Open(p)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return errors.WrapIff(err, "failed to open '%s' for copying", header.Name)
}
defer f.Close()
// Copy the file's contents to the archive using our buffer.
if _, err := io.CopyBuffer(w, io.LimitReader(f, header.Size), buf); err != nil {
return errors.WrapIff(err, "failed to copy '%s' to archive", header.Name)
}
return nil
}

View File

@@ -1,11 +1,7 @@
package filesystem package filesystem
import ( import (
"context"
"fmt" "fmt"
"github.com/karrick/godirwalk"
"github.com/pterodactyl/wings/server/backup"
ignore "github.com/sabhiram/go-gitignore"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@@ -13,60 +9,6 @@ import (
"time" "time"
) )
// Given a directory, iterate through all of the files and folders within it and determine
// if they should be included in the output based on an array of ignored matches. This uses
// standard .gitignore formatting to make that determination.
//
// If no ignored files are passed through you'll get the entire directory listing.
func (fs *Filesystem) GetIncludedFiles(dir string, ignored []string) (*backup.IncludedFiles, error) {
cleaned, err := fs.SafePath(dir)
if err != nil {
return nil, err
}
i, err := ignore.CompileIgnoreLines(ignored...)
if err != nil {
return nil, err
}
// Walk through all of the files and directories on a server. This callback only returns
// files found, and will keep walking deeper and deeper into directories.
inc := new(backup.IncludedFiles)
err = godirwalk.Walk(cleaned, &godirwalk.Options{
Unsorted: true,
Callback: func(p string, e *godirwalk.Dirent) error {
sp := p
if e.IsSymlink() {
sp, err = fs.SafePath(p)
if err != nil {
if IsBadPathResolutionError(err) {
return godirwalk.SkipThis
}
return err
}
}
// Only push files into the result array since archives can't create an empty directory within them.
if !e.IsDir() {
// Avoid unnecessary parsing if there are no ignored files, nothing will match anyways
// so no reason to call the function.
if len(ignored) == 0 || !i.MatchesPath(strings.TrimPrefix(sp, fs.Path()+"/")) {
inc.Push(sp)
}
}
// We can't just abort if the path is technically ignored. It is possible there is a nested
// file or folder that should not be excluded, so in this case we need to just keep going
// until we get to a final state.
return nil
},
})
return inc, err
}
// Compresses all of the files matching the given paths in the specified directory. This function // Compresses all of the files matching the given paths in the specified directory. This function
// also supports passing nested paths to only compress certain files and folders when working in // also supports passing nested paths to only compress certain files and folders when working in
// a larger directory. This effectively creates a local backup, but rather than ignoring specific // a larger directory. This effectively creates a local backup, but rather than ignoring specific
@@ -90,69 +32,24 @@ func (fs *Filesystem) CompressFiles(dir string, paths []string) (os.FileInfo, er
return nil, err return nil, err
} }
inc := new(backup.IncludedFiles) a := &Archive{BasePath: cleanedRootDir, Files: cleaned}
// Iterate over all of the cleaned paths and merge them into a large object of final file d := path.Join(
// paths to pass into the archiver. As directories are encountered this will drop into them cleanedRootDir,
// and look for all of the files. fmt.Sprintf("archive-%s.tar.gz", strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "")),
for _, p := range cleaned { )
f, err := os.Stat(p)
if err != nil {
fs.error(err).WithField("path", p).Debug("failed to stat file or directory for compression")
continue
}
if !f.IsDir() { if err := a.Create(d); err != nil {
inc.Push(p)
} else {
err := godirwalk.Walk(p, &godirwalk.Options{
Unsorted: true,
Callback: func(p string, e *godirwalk.Dirent) error {
sp := p
if e.IsSymlink() {
// Ensure that any symlinks are properly resolved to their final destination. If
// that destination is outside the server directory skip over this entire item, otherwise
// use the resolved location for the rest of this function.
sp, err = fs.SafePath(p)
if err != nil {
if IsBadPathResolutionError(err) {
return godirwalk.SkipThis
}
return err
}
}
if !e.IsDir() {
inc.Push(sp)
}
return nil
},
})
if err != nil {
return nil, err
}
}
}
a := &backup.Archive{TrimPrefix: fs.Path(), Files: inc}
d := path.Join(cleanedRootDir, fmt.Sprintf("archive-%s.tar.gz", strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "")))
if err := a.Create(d, context.Background()); err != nil {
return nil, err return nil, err
} }
f, err := os.Stat(d) f, err := os.Stat(d)
if err != nil { if err != nil {
_ = os.Remove(d) _ = os.Remove(d)
return nil, err return nil, err
} }
if err := fs.hasSpaceFor(f.Size()); err != nil { if err := fs.HasSpaceFor(f.Size()); err != nil {
_ = os.Remove(d) _ = os.Remove(d)
return nil, err return nil, err
} }

View File

@@ -4,9 +4,9 @@ import (
"archive/tar" "archive/tar"
"archive/zip" "archive/zip"
"compress/gzip" "compress/gzip"
"emperror.dev/errors"
"fmt" "fmt"
"github.com/mholt/archiver/v3" "github.com/mholt/archiver/v3"
"github.com/pkg/errors"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@@ -36,7 +36,7 @@ func (fs *Filesystem) SpaceAvailableForDecompression(dir string, file string) (b
// Walk over the archive and figure out just how large the final output would be from unarchiving it. // Walk over the archive and figure out just how large the final output would be from unarchiving it.
err = archiver.Walk(source, func(f archiver.File) error { err = archiver.Walk(source, func(f archiver.File) error {
if atomic.AddInt64(&size, f.Size())+dirSize > fs.MaxDisk() { if atomic.AddInt64(&size, f.Size())+dirSize > fs.MaxDisk() {
return ErrNotEnoughDiskSpace return &Error{code: ErrCodeDiskSpace}
} }
return nil return nil
@@ -44,7 +44,7 @@ func (fs *Filesystem) SpaceAvailableForDecompression(dir string, file string) (b
if err != nil { if err != nil {
if strings.HasPrefix(err.Error(), "format ") { if strings.HasPrefix(err.Error(), "format ") {
return false, ErrUnknownArchiveFormat return false, &Error{code: ErrCodeUnknownArchive}
} }
return false, err return false, err
@@ -100,7 +100,7 @@ func (fs *Filesystem) DecompressFile(dir string, file string) error {
}) })
if err != nil { if err != nil {
if strings.HasPrefix(err.Error(), "format ") { if strings.HasPrefix(err.Error(), "format ") {
return ErrUnknownArchiveFormat return &Error{code: ErrCodeUnknownArchive}
} }
return err return err

View File

@@ -35,17 +35,21 @@ func (ult *usageLookupTime) Get() time.Time {
// Returns the maximum amount of disk space that this Filesystem instance is allowed to use. // Returns the maximum amount of disk space that this Filesystem instance is allowed to use.
func (fs *Filesystem) MaxDisk() int64 { func (fs *Filesystem) MaxDisk() int64 {
fs.mu.RLock() return atomic.LoadInt64(&fs.diskLimit)
defer fs.mu.RUnlock()
return fs.diskLimit
} }
// Sets the disk space limit for this Filesystem instance. // Sets the disk space limit for this Filesystem instance.
func (fs *Filesystem) SetDiskLimit(i int64) { func (fs *Filesystem) SetDiskLimit(i int64) {
fs.mu.Lock() atomic.SwapInt64(&fs.diskLimit, i)
fs.diskLimit = i }
fs.mu.Unlock()
// The same concept as HasSpaceAvailable however this will return an error if there is
// no space, rather than a boolean value.
func (fs *Filesystem) HasSpaceErr(allowStaleValue bool) error {
if !fs.HasSpaceAvailable(allowStaleValue) {
return &Error{code: ErrCodeDiskSpace}
}
return nil
} }
// Determines if the directory a file is trying to be added to has enough space available // Determines if the directory a file is trying to be added to has enough space available
@@ -78,10 +82,7 @@ func (fs *Filesystem) HasSpaceAvailable(allowStaleValue bool) bool {
// function for critical logical checks. It should only be used in areas where the actual disk usage // function for critical logical checks. It should only be used in areas where the actual disk usage
// does not need to be perfect, e.g. API responses for server resource usage. // does not need to be perfect, e.g. API responses for server resource usage.
func (fs *Filesystem) CachedUsage() int64 { func (fs *Filesystem) CachedUsage() int64 {
fs.mu.RLock() return atomic.LoadInt64(&fs.diskUsed)
defer fs.mu.RUnlock()
return fs.diskUsed
} }
// Internal helper function to allow other parts of the codebase to check the total used disk space // Internal helper function to allow other parts of the codebase to check the total used disk space
@@ -106,7 +107,7 @@ func (fs *Filesystem) DiskUsage(allowStaleValue bool) (int64, error) {
// value. This is a blocking operation to the calling process. // value. This is a blocking operation to the calling process.
if !allowStaleValue { if !allowStaleValue {
return fs.updateCachedDiskUsage() return fs.updateCachedDiskUsage()
} else if !fs.lookupInProgress.Get() { } else if !fs.lookupInProgress.Load() {
// Otherwise, if we allow a stale value and there isn't a valid item in the cache and we aren't // Otherwise, if we allow a stale value and there isn't a valid item in the cache and we aren't
// currently performing a lookup, just do the disk usage calculation in the background. // currently performing a lookup, just do the disk usage calculation in the background.
go func(fs *Filesystem) { go func(fs *Filesystem) {
@@ -132,8 +133,8 @@ func (fs *Filesystem) updateCachedDiskUsage() (int64, error) {
// Signal that we're currently updating the disk size so that other calls to the disk checking // Signal that we're currently updating the disk size so that other calls to the disk checking
// functions can determine if they should queue up additional calls to this function. Ensure that // functions can determine if they should queue up additional calls to this function. Ensure that
// we always set this back to "false" when this process is done executing. // we always set this back to "false" when this process is done executing.
fs.lookupInProgress.Set(true) fs.lookupInProgress.Store(true)
defer fs.lookupInProgress.Set(false) defer fs.lookupInProgress.Store(false)
// If there is no size its either because there is no data (in which case running this function // If there is no size its either because there is no data (in which case running this function
// will have effectively no impact), or there is nothing in the cache, in which case we need to // will have effectively no impact), or there is nothing in the cache, in which case we need to
@@ -171,7 +172,7 @@ func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
// it. Otherwise, allow it to continue. // it. Otherwise, allow it to continue.
if e.IsSymlink() { if e.IsSymlink() {
if _, err := fs.SafePath(p); err != nil { if _, err := fs.SafePath(p); err != nil {
if IsBadPathResolutionError(err) { if IsErrorCode(err, ErrCodePathResolution) {
return godirwalk.SkipThis return godirwalk.SkipThis
} }
@@ -194,7 +195,7 @@ func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
// Helper function to determine if a server has space available for a file of a given size. // Helper function to determine if a server has space available for a file of a given size.
// If space is available, no error will be returned, otherwise an ErrNotEnoughSpace error // If space is available, no error will be returned, otherwise an ErrNotEnoughSpace error
// will be raised. // will be raised.
func (fs *Filesystem) hasSpaceFor(size int64) error { func (fs *Filesystem) HasSpaceFor(size int64) error {
if fs.MaxDisk() == 0 { if fs.MaxDisk() == 0 {
return nil return nil
} }
@@ -205,7 +206,7 @@ func (fs *Filesystem) hasSpaceFor(size int64) error {
} }
if (s + size) > fs.MaxDisk() { if (s + size) > fs.MaxDisk() {
return ErrNotEnoughDiskSpace return &Error{code: ErrCodeDiskSpace}
} }
return nil return nil

View File

@@ -1,49 +1,75 @@
package filesystem package filesystem
import ( import (
"emperror.dev/errors"
"fmt" "fmt"
"github.com/apex/log" "github.com/apex/log"
"github.com/pkg/errors"
"os" "os"
"path/filepath" "path/filepath"
) )
var ErrIsDirectory = errors.New("filesystem: is a directory") type ErrorCode string
var ErrNotEnoughDiskSpace = errors.New("filesystem: not enough disk space")
var ErrUnknownArchiveFormat = errors.New("filesystem: unknown archive format")
type BadPathResolutionError struct { const (
ErrCodeIsDirectory ErrorCode = "E_ISDIR"
ErrCodeDiskSpace ErrorCode = "E_NODISK"
ErrCodeUnknownArchive ErrorCode = "E_UNKNFMT"
ErrCodePathResolution ErrorCode = "E_BADPATH"
)
type Error struct {
code ErrorCode
path string path string
resolved string resolved string
} }
// Returns the specific error for a bad path resolution. // Returns a human-readable error string to identify the Error by.
func (b *BadPathResolutionError) Error() string { func (e *Error) Error() string {
r := b.resolved switch e.code {
if r == "" { case ErrCodeIsDirectory:
r = "<empty>" return "filesystem: is a directory"
case ErrCodeDiskSpace:
return "filesystem: not enough disk space"
case ErrCodeUnknownArchive:
return "filesystem: unknown archive format"
case ErrCodePathResolution:
r := e.resolved
if r == "" {
r = "<empty>"
}
return fmt.Sprintf("filesystem: server path [%s] resolves to a location outside the server root: %s", e.path, r)
} }
return "filesystem: unhandled error type"
}
return fmt.Sprintf("filesystem: server path [%s] resolves to a location outside the server root: %s", b.path, r) // Returns the ErrorCode for this specific error instance.
func (e *Error) Code() ErrorCode {
return e.code
}
// Checks if the given error is one of the Filesystem errors.
func IsFilesystemError(err error) (*Error, bool) {
if e := errors.Unwrap(err); e != nil {
err = e
}
if fserr, ok := err.(*Error); ok {
return fserr, true
}
return nil, false
}
// Checks if "err" is a filesystem Error type. If so, it will then drop in and check
// that the error code is the same as the provided ErrorCode passed in "code".
func IsErrorCode(err error, code ErrorCode) bool {
if e, ok := IsFilesystemError(err); ok {
return e.code == code
}
return false
} }
// Returns a new BadPathResolution error. // Returns a new BadPathResolution error.
func NewBadPathResolution(path string, resolved string) *BadPathResolutionError { func NewBadPathResolution(path string, resolved string) *Error {
return &BadPathResolutionError{path, resolved} return &Error{code: ErrCodePathResolution, path: path, resolved: resolved}
}
// Determines if the given error is a bad path resolution error.
func IsBadPathResolutionError(err error) bool {
e := errors.Unwrap(err)
if e == nil {
e = err
}
if _, ok := e.(*BadPathResolutionError); ok {
return true
}
return false
} }
// Generates an error logger instance with some basic information. // Generates an error logger instance with some basic information.
@@ -57,7 +83,7 @@ func (fs *Filesystem) error(err error) *log.Entry {
// directory, otherwise return nil. Returning this error for a file will stop the walking // directory, otherwise return nil. Returning this error for a file will stop the walking
// for the remainder of the directory. This is assuming an os.FileInfo struct was even returned. // for the remainder of the directory. This is assuming an os.FileInfo struct was even returned.
func (fs *Filesystem) handleWalkerError(err error, f os.FileInfo) error { func (fs *Filesystem) handleWalkerError(err error, f os.FileInfo) error {
if !IsBadPathResolutionError(err) { if !IsErrorCode(err, ErrCodePathResolution) {
return err return err
} }

View File

@@ -11,9 +11,9 @@ func TestFilesystem_PathResolutionError(t *testing.T) {
g.Describe("NewBadPathResolutionError", func() { g.Describe("NewBadPathResolutionError", func() {
g.It("is can detect itself as an error correctly", func() { g.It("is can detect itself as an error correctly", func() {
err := NewBadPathResolution("foo", "bar") err := NewBadPathResolution("foo", "bar")
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
g.Assert(err.Error()).Equal("filesystem: server path [foo] resolves to a location outside the server root: bar") g.Assert(err.Error()).Equal("filesystem: server path [foo] resolves to a location outside the server root: bar")
g.Assert(IsBadPathResolutionError(ErrIsDirectory)).IsFalse() g.Assert(IsErrorCode(&Error{code: ErrCodeIsDirectory}, ErrCodePathResolution)).IsFalse()
}) })
g.It("returns <empty> if no destination path is provided", func() { g.It("returns <empty> if no destination path is provided", func() {

View File

@@ -2,9 +2,9 @@ package filesystem
import ( import (
"bufio" "bufio"
"emperror.dev/errors"
"github.com/gabriel-vasile/mimetype" "github.com/gabriel-vasile/mimetype"
"github.com/karrick/godirwalk" "github.com/karrick/godirwalk"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
"io" "io"
@@ -22,7 +22,7 @@ import (
type Filesystem struct { type Filesystem struct {
mu sync.RWMutex mu sync.RWMutex
lastLookupTime *usageLookupTime lastLookupTime *usageLookupTime
lookupInProgress system.AtomicBool lookupInProgress *system.AtomicBool
diskUsed int64 diskUsed int64
diskCheckInterval time.Duration diskCheckInterval time.Duration
@@ -42,6 +42,7 @@ func New(root string, size int64) *Filesystem {
diskLimit: size, diskLimit: size,
diskCheckInterval: time.Duration(config.Get().System.DiskCheckInterval), diskCheckInterval: time.Duration(config.Get().System.DiskCheckInterval),
lastLookupTime: &usageLookupTime{}, lastLookupTime: &usageLookupTime{},
lookupInProgress: system.NewAtomicBool(false),
} }
} }
@@ -50,29 +51,36 @@ func (fs *Filesystem) Path() string {
return fs.root return fs.root
} }
// Returns a reader for a file instance.
func (fs *Filesystem) File(p string) (*os.File, os.FileInfo, error) {
cleaned, err := fs.SafePath(p)
if err != nil {
return nil, nil, err
}
st, err := os.Stat(cleaned)
if err != nil {
return nil, nil, err
}
if st.IsDir() {
return nil, nil, &Error{code: ErrCodeIsDirectory}
}
f, err := os.Open(cleaned)
if err != nil {
return nil, nil, err
}
return f, st, nil
}
// Reads a file on the system and returns it as a byte representation in a file // Reads a file on the system and returns it as a byte representation in a file
// reader. This is not the most memory efficient usage since it will be reading the // reader. This is not the most memory efficient usage since it will be reading the
// entirety of the file into memory. // entirety of the file into memory.
func (fs *Filesystem) Readfile(p string, w io.Writer) error { func (fs *Filesystem) Readfile(p string, w io.Writer) error {
cleaned, err := fs.SafePath(p) file, _, err := fs.File(p)
if err != nil { if err != nil {
return err return err
} }
defer file.Close()
if st, err := os.Stat(cleaned); err != nil { _, err = bufio.NewReader(file).WriteTo(w)
return err
} else if st.IsDir() {
return ErrIsDirectory
}
f, err := os.Open(cleaned)
if err != nil {
return err
}
defer f.Close()
_, err = bufio.NewReader(f).WriteTo(w)
return err return err
} }
@@ -86,34 +94,36 @@ func (fs *Filesystem) Writefile(p string, r io.Reader) error {
var currentSize int64 var currentSize int64
// If the file does not exist on the system already go ahead and create the pathway // If the file does not exist on the system already go ahead and create the pathway
// to it and an empty file. We'll then write to it later on after this completes. // to it and an empty file. We'll then write to it later on after this completes.
if stat, err := os.Stat(cleaned); err != nil { stat, err := os.Stat(cleaned)
if !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return err return err
} } else if err == nil {
if err := os.MkdirAll(filepath.Dir(cleaned), 0755); err != nil {
return err
}
if err := fs.Chown(filepath.Dir(cleaned)); err != nil {
return err
}
} else {
if stat.IsDir() { if stat.IsDir() {
return ErrIsDirectory return &Error{code: ErrCodeIsDirectory}
} }
currentSize = stat.Size() currentSize = stat.Size()
} }
br := bufio.NewReader(r) br := bufio.NewReader(r)
// Check that the new size we're writing to the disk can fit. If there is currently a file // Check that the new size we're writing to the disk can fit. If there is currently
// we'll subtract that current file size from the size of the buffer to determine the amount // a file we'll subtract that current file size from the size of the buffer to determine
// of new data we're writing (or amount we're removing if smaller). // the amount of new data we're writing (or amount we're removing if smaller).
if err := fs.hasSpaceFor(int64(br.Size()) - currentSize); err != nil { if err := fs.HasSpaceFor(int64(br.Size()) - currentSize); err != nil {
return err return err
} }
// If we were unable to stat the location because it did not exist, go ahead and create
// it now. We do this after checking the disk space so that we do not just create empty
// directories at random.
if err != nil {
if err := os.MkdirAll(filepath.Dir(cleaned), 0755); err != nil {
return err
}
if err := fs.Chown(filepath.Dir(cleaned)); err != nil {
return err
}
}
o := &fileOpener{} o := &fileOpener{}
// This will either create the file if it does not already exist, or open and // This will either create the file if it does not already exist, or open and
// truncate the existing file. // truncate the existing file.
@@ -254,7 +264,7 @@ func (fs *Filesystem) Chmod(path string, mode os.FileMode) error {
// looping endlessly. // looping endlessly.
func (fs *Filesystem) findCopySuffix(dir string, name string, extension string) (string, error) { func (fs *Filesystem) findCopySuffix(dir string, name string, extension string) (string, error) {
var i int var i int
var suffix = " copy" suffix := " copy"
for i = 0; i < 51; i++ { for i = 0; i < 51; i++ {
if i > 0 { if i > 0 {
@@ -298,7 +308,7 @@ func (fs *Filesystem) Copy(p string) error {
} }
// Check that copying this file wouldn't put the server over its limit. // Check that copying this file wouldn't put the server over its limit.
if err := fs.hasSpaceFor(s.Size()); err != nil { if err := fs.HasSpaceFor(s.Size()); err != nil {
return err return err
} }

View File

@@ -98,7 +98,7 @@ func TestFilesystem_Readfile(t *testing.T) {
err = fs.Readfile("test.txt", buf) err = fs.Readfile("test.txt", buf)
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(errors.Is(err, ErrIsDirectory)).IsTrue() g.Assert(IsErrorCode(err, ErrCodeIsDirectory)).IsTrue()
}) })
g.It("cannot open a file outside the root directory", func() { g.It("cannot open a file outside the root directory", func() {
@@ -107,7 +107,7 @@ func TestFilesystem_Readfile(t *testing.T) {
err = fs.Readfile("/../test.txt", buf) err = fs.Readfile("/../test.txt", buf)
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.AfterEach(func() { g.AfterEach(func() {
@@ -168,7 +168,7 @@ func TestFilesystem_Writefile(t *testing.T) {
err := fs.Writefile("/some/../foo/../../test.txt", r) err := fs.Writefile("/some/../foo/../../test.txt", r)
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("cannot write a file that exceeds the disk limits", func() { g.It("cannot write a file that exceeds the disk limits", func() {
@@ -182,7 +182,7 @@ func TestFilesystem_Writefile(t *testing.T) {
r := bytes.NewReader(b) r := bytes.NewReader(b)
err = fs.Writefile("test.txt", r) err = fs.Writefile("test.txt", r)
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(errors.Is(err, ErrNotEnoughDiskSpace)).IsTrue() g.Assert(IsErrorCode(err, ErrCodeDiskSpace)).IsTrue()
}) })
/*g.It("updates the total space used when a file is appended to", func() { /*g.It("updates the total space used when a file is appended to", func() {
@@ -259,7 +259,7 @@ func TestFilesystem_CreateDirectory(t *testing.T) {
g.It("should not allow the creation of directories outside the root", func() { g.It("should not allow the creation of directories outside the root", func() {
err := fs.CreateDirectory("test", "e/../../something") err := fs.CreateDirectory("test", "e/../../something")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("should not increment the disk usage", func() { g.It("should not increment the disk usage", func() {
@@ -309,7 +309,7 @@ func TestFilesystem_Rename(t *testing.T) {
g.It("does not allow renaming to a location outside the root", func() { g.It("does not allow renaming to a location outside the root", func() {
err := fs.Rename("source.txt", "../target.txt") err := fs.Rename("source.txt", "../target.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("does not allow renaming from a location outside the root", func() { g.It("does not allow renaming from a location outside the root", func() {
@@ -317,7 +317,7 @@ func TestFilesystem_Rename(t *testing.T) {
err = fs.Rename("/../ext-source.txt", "target.txt") err = fs.Rename("/../ext-source.txt", "target.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("allows a file to be renamed", func() { g.It("allows a file to be renamed", func() {
@@ -395,7 +395,7 @@ func TestFilesystem_Copy(t *testing.T) {
err = fs.Copy("../ext-source.txt") err = fs.Copy("../ext-source.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("should return an error if the source directory is outside the root", func() { g.It("should return an error if the source directory is outside the root", func() {
@@ -407,11 +407,11 @@ func TestFilesystem_Copy(t *testing.T) {
err = fs.Copy("../nested/in/dir/ext-source.txt") err = fs.Copy("../nested/in/dir/ext-source.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
err = fs.Copy("nested/in/../../../nested/in/dir/ext-source.txt") err = fs.Copy("nested/in/../../../nested/in/dir/ext-source.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("should return an error if the source is a directory", func() { g.It("should return an error if the source is a directory", func() {
@@ -428,7 +428,7 @@ func TestFilesystem_Copy(t *testing.T) {
err := fs.Copy("source.txt") err := fs.Copy("source.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(errors.Is(err, ErrNotEnoughDiskSpace)).IsTrue() g.Assert(IsErrorCode(err, ErrCodeDiskSpace)).IsTrue()
}) })
g.It("should create a copy of the file and increment the disk used", func() { g.It("should create a copy of the file and increment the disk used", func() {
@@ -503,7 +503,7 @@ func TestFilesystem_Delete(t *testing.T) {
err = fs.Delete("../ext-source.txt") err = fs.Delete("../ext-source.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("does not allow the deletion of the root directory", func() { g.It("does not allow the deletion of the root directory", func() {

View File

@@ -2,8 +2,8 @@ package filesystem
import ( import (
"bytes" "bytes"
"emperror.dev/errors"
. "github.com/franela/goblin" . "github.com/franela/goblin"
"github.com/pkg/errors"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@@ -73,22 +73,22 @@ func TestFilesystem_SafePath(t *testing.T) {
g.It("blocks access to files outside the root directory", func() { g.It("blocks access to files outside the root directory", func() {
p, err := fs.SafePath("../test.txt") p, err := fs.SafePath("../test.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
g.Assert(p).Equal("") g.Assert(p).Equal("")
p, err = fs.SafePath("/../test.txt") p, err = fs.SafePath("/../test.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
g.Assert(p).Equal("") g.Assert(p).Equal("")
p, err = fs.SafePath("./foo/../../test.txt") p, err = fs.SafePath("./foo/../../test.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
g.Assert(p).Equal("") g.Assert(p).Equal("")
p, err = fs.SafePath("..") p, err = fs.SafePath("..")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
g.Assert(p).Equal("") g.Assert(p).Equal("")
}) })
}) })
@@ -124,7 +124,7 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
err := fs.Readfile("symlinked.txt", &b) err := fs.Readfile("symlinked.txt", &b)
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
}) })
@@ -134,7 +134,7 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
err := fs.Writefile("symlinked.txt", r) err := fs.Writefile("symlinked.txt", r)
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("cannot write a file to a directory symlinked outside the root", func() { g.It("cannot write a file to a directory symlinked outside the root", func() {
@@ -142,7 +142,7 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
err := fs.Writefile("external_dir/foo.txt", r) err := fs.Writefile("external_dir/foo.txt", r)
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
}) })
@@ -150,19 +150,19 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
g.It("cannot create a directory outside the root", func() { g.It("cannot create a directory outside the root", func() {
err := fs.CreateDirectory("my_dir", "external_dir") err := fs.CreateDirectory("my_dir", "external_dir")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("cannot create a nested directory outside the root", func() { g.It("cannot create a nested directory outside the root", func() {
err := fs.CreateDirectory("my/nested/dir", "external_dir/foo/bar") err := fs.CreateDirectory("my/nested/dir", "external_dir/foo/bar")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("cannot create a nested directory outside the root", func() { g.It("cannot create a nested directory outside the root", func() {
err := fs.CreateDirectory("my/nested/dir", "external_dir/server") err := fs.CreateDirectory("my/nested/dir", "external_dir/server")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
}) })
@@ -170,13 +170,13 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
g.It("cannot rename a file symlinked outside the directory root", func() { g.It("cannot rename a file symlinked outside the directory root", func() {
err := fs.Rename("symlinked.txt", "foo.txt") err := fs.Rename("symlinked.txt", "foo.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("cannot rename a symlinked directory outside the root", func() { g.It("cannot rename a symlinked directory outside the root", func() {
err := fs.Rename("external_dir", "foo") err := fs.Rename("external_dir", "foo")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("cannot rename a file to a location outside the directory root", func() { g.It("cannot rename a file to a location outside the directory root", func() {
@@ -184,7 +184,7 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
err := fs.Rename("my_file.txt", "external_dir/my_file.txt") err := fs.Rename("my_file.txt", "external_dir/my_file.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
}) })
@@ -192,13 +192,13 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
g.It("cannot chown a file symlinked outside the directory root", func() { g.It("cannot chown a file symlinked outside the directory root", func() {
err := fs.Chown("symlinked.txt") err := fs.Chown("symlinked.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
g.It("cannot chown a directory symlinked outside the directory root", func() { g.It("cannot chown a directory symlinked outside the directory root", func() {
err := fs.Chown("external_dir") err := fs.Chown("external_dir")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
}) })
@@ -206,7 +206,7 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
g.It("cannot copy a file symlinked outside the directory root", func() { g.It("cannot copy a file symlinked outside the directory root", func() {
err := fs.Copy("symlinked.txt") err := fs.Copy("symlinked.txt")
g.Assert(err).IsNotNil() g.Assert(err).IsNotNil()
g.Assert(IsBadPathResolutionError(err)).IsTrue() g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
}) })
}) })

View File

@@ -4,23 +4,22 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"golang.org/x/sync/semaphore" "github.com/pterodactyl/wings/system"
"html/template" "html/template"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time"
) )
// Executes the installation stack for a server process. Bubbles any errors up to the calling // Executes the installation stack for a server process. Bubbles any errors up to the calling
@@ -127,75 +126,40 @@ func NewInstallationProcess(s *Server, script *api.InstallationScript) (*Install
Server: s, Server: s,
} }
ctx, cancel := context.WithCancel(context.Background())
s.installer.cancel = &cancel
if c, err := environment.DockerClient(); err != nil { if c, err := environment.DockerClient(); err != nil {
return nil, err return nil, err
} else { } else {
proc.client = c proc.client = c
proc.context = ctx proc.context = s.Context()
} }
return proc, nil return proc, nil
} }
// Try to obtain an exclusive lock on the installation process for the server. Waits up to 10
// seconds before aborting with a context timeout.
func (s *Server) acquireInstallationLock() error {
if s.installer.sem == nil {
s.installer.sem = semaphore.NewWeighted(1)
}
ctx, _ := context.WithTimeout(context.Background(), time.Second*10)
return s.installer.sem.Acquire(ctx, 1)
}
// Determines if the server is actively running the installation process by checking the status // Determines if the server is actively running the installation process by checking the status
// of the semaphore lock. // of the installer lock.
func (s *Server) IsInstalling() bool { func (s *Server) IsInstalling() bool {
if s.installer.sem == nil { return s.installing.Load()
return false
}
if s.installer.sem.TryAcquire(1) {
// If we made it into this block it means we were able to obtain an exclusive lock
// on the semaphore. In that case, go ahead and release that lock immediately, and
// return false.
s.installer.sem.Release(1)
return false
}
return true
} }
// Aborts the server installation process by calling the cancel function on the installer func (s *Server) IsTransferring() bool {
// context. return s.transferring.Load()
func (s *Server) AbortInstallation() { }
if !s.IsInstalling() {
return
}
if s.installer.cancel != nil { func (s *Server) SetTransferring(state bool) {
cancel := *s.installer.cancel s.transferring.Store(state)
s.Log().Warn("aborting running installation process")
cancel()
}
} }
// Removes the installer container for the server. // Removes the installer container for the server.
func (ip *InstallationProcess) RemoveContainer() { func (ip *InstallationProcess) RemoveContainer() error {
err := ip.client.ContainerRemove(ip.context, ip.Server.Id()+"_installer", types.ContainerRemoveOptions{ err := ip.client.ContainerRemove(ip.context, ip.Server.Id()+"_installer", types.ContainerRemoveOptions{
RemoveVolumes: true, RemoveVolumes: true,
Force: true, Force: true,
}) })
if err != nil && !client.IsErrNotFound(err) { if err != nil && !client.IsErrNotFound(err) {
ip.Server.Log().WithField("error", err).Warn("failed to delete server install container") return err
} }
return nil
} }
// Runs the installation process, this is done as in a background thread. This will configure // Runs the installation process, this is done as in a background thread. This will configure
@@ -205,8 +169,8 @@ func (ip *InstallationProcess) RemoveContainer() {
// log in the server's configuration directory. // log in the server's configuration directory.
func (ip *InstallationProcess) Run() error { func (ip *InstallationProcess) Run() error {
ip.Server.Log().Debug("acquiring installation process lock") ip.Server.Log().Debug("acquiring installation process lock")
if err := ip.Server.acquireInstallationLock(); err != nil { if !ip.Server.installing.SwapIf(true) {
return err return errors.New("install: cannot obtain installation lock")
} }
// We now have an exclusive lock on this installation process. Ensure that whenever this // We now have an exclusive lock on this installation process. Ensure that whenever this
@@ -214,8 +178,7 @@ func (ip *InstallationProcess) Run() error {
// without encountering a wait timeout. // without encountering a wait timeout.
defer func() { defer func() {
ip.Server.Log().Debug("releasing installation process lock") ip.Server.Log().Debug("releasing installation process lock")
ip.Server.installer.sem.Release(1) ip.Server.installing.Store(false)
ip.Server.installer.cancel = nil
}() }()
if err := ip.BeforeExecute(); err != nil { if err := ip.BeforeExecute(); err != nil {
@@ -225,7 +188,6 @@ func (ip *InstallationProcess) Run() error {
cid, err := ip.Execute() cid, err := ip.Execute()
if err != nil { if err != nil {
ip.RemoveContainer() ip.RemoveContainer()
return err return err
} }
@@ -352,22 +314,12 @@ func (ip *InstallationProcess) BeforeExecute() error {
if err := ip.writeScriptToDisk(); err != nil { if err := ip.writeScriptToDisk(); err != nil {
return errors.WithMessage(err, "failed to write installation script to disk") return errors.WithMessage(err, "failed to write installation script to disk")
} }
if err := ip.pullInstallationImage(); err != nil { if err := ip.pullInstallationImage(); err != nil {
return errors.WithMessage(err, "failed to pull updated installation container image for server") return errors.WithMessage(err, "failed to pull updated installation container image for server")
} }
if err := ip.RemoveContainer(); err != nil {
opts := types.ContainerRemoveOptions{ return errors.WithMessage(err, "failed to remove existing install container for server")
RemoveVolumes: true,
Force: true,
} }
if err := ip.client.ContainerRemove(ip.context, ip.Server.Id()+"_installer", opts); err != nil {
if !client.IsErrNotFound(err) {
return errors.WithMessage(err, "failed to remove existing install container for server")
}
}
return nil return nil
} }
@@ -440,6 +392,12 @@ func (ip *InstallationProcess) AfterExecute(containerId string) error {
// Executes the installation process inside a specially created docker container. // Executes the installation process inside a specially created docker container.
func (ip *InstallationProcess) Execute() (string, error) { func (ip *InstallationProcess) Execute() (string, error) {
// Create a child context that is canceled once this function is done running. This
// will also be canceled if the parent context (from the Server struct) is canceled
// which occurs if the server is deleted.
ctx, cancel := context.WithCancel(ip.context)
defer cancel()
conf := &container.Config{ conf := &container.Config{
Hostname: "installer", Hostname: "installer",
AttachStdout: true, AttachStdout: true,
@@ -498,28 +456,35 @@ func (ip *InstallationProcess) Execute() (string, error) {
} }
}() }()
r, err := ip.client.ContainerCreate(ip.context, conf, hostConf, nil, ip.Server.Id()+"_installer") r, err := ip.client.ContainerCreate(ctx, conf, hostConf, nil, nil, ip.Server.Id()+"_installer")
if err != nil { if err != nil {
return "", err return "", err
} }
ip.Server.Log().WithField("container_id", r.ID).Info("running installation script for server in container") ip.Server.Log().WithField("container_id", r.ID).Info("running installation script for server in container")
if err := ip.client.ContainerStart(ip.context, r.ID, types.ContainerStartOptions{}); err != nil { if err := ip.client.ContainerStart(ctx, r.ID, types.ContainerStartOptions{}); err != nil {
return "", err return "", err
} }
// Process the install event in the background by listening to the stream output until the
// container has stopped, at which point we'll disconnect from it.
//
// If there is an error during the streaming output just report it and do nothing else, the
// install can still run, the console just won't have any output.
go func(id string) { go func(id string) {
ip.Server.Events().Publish(DaemonMessageEvent, "Starting installation process, this could take a few minutes...") ip.Server.Events().Publish(DaemonMessageEvent, "Starting installation process, this could take a few minutes...")
if err := ip.StreamOutput(id); err != nil { if err := ip.StreamOutput(ctx, id); err != nil {
ip.Server.Log().WithField("error", err).Error("error while handling output stream for server install process") ip.Server.Log().WithField("error", err).Warn("error connecting to server install stream output")
} }
ip.Server.Events().Publish(DaemonMessageEvent, "Installation process completed.")
}(r.ID) }(r.ID)
sChan, eChan := ip.client.ContainerWait(ip.context, r.ID, container.WaitConditionNotRunning) sChan, eChan := ip.client.ContainerWait(ctx, r.ID, container.WaitConditionNotRunning)
select { select {
case err := <-eChan: case err := <-eChan:
if err != nil { // Once the container has stopped running we can mark the install process as being completed.
if err == nil {
ip.Server.Events().Publish(DaemonMessageEvent, "Installation process completed.")
} else {
return "", err return "", err
} }
case <-sChan: case <-sChan:
@@ -531,8 +496,8 @@ func (ip *InstallationProcess) Execute() (string, error) {
// Streams the output of the installation process to a log file in the server configuration // Streams the output of the installation process to a log file in the server configuration
// directory, as well as to a websocket listener so that the process can be viewed in // directory, as well as to a websocket listener so that the process can be viewed in
// the panel by administrators. // the panel by administrators.
func (ip *InstallationProcess) StreamOutput(id string) error { func (ip *InstallationProcess) StreamOutput(ctx context.Context, id string) error {
reader, err := ip.client.ContainerLogs(ip.context, id, types.ContainerLogsOptions{ reader, err := ip.client.ContainerLogs(ctx, id, types.ContainerLogsOptions{
ShowStdout: true, ShowStdout: true,
ShowStderr: true, ShowStderr: true,
Follow: true, Follow: true,
@@ -541,21 +506,15 @@ func (ip *InstallationProcess) StreamOutput(id string) error {
if err != nil { if err != nil {
return err return err
} }
defer reader.Close() defer reader.Close()
s := bufio.NewScanner(reader) evts := ip.Server.Events()
for s.Scan() { err = system.ScanReader(reader, func(line string) {
ip.Server.Events().Publish(InstallOutputEvent, s.Text()) evts.Publish(InstallOutputEvent, line)
})
if err != nil {
ip.Server.Log().WithFields(log.Fields{"container_id": id, "error": err}).Warn("error processing install output lines")
} }
if err := s.Err(); err != nil {
ip.Server.Log().WithFields(log.Fields{
"container_id": id,
"error": err,
}).Warn("error processing scanner line in installation output for server")
}
return nil return nil
} }

View File

@@ -1,12 +1,11 @@
package server package server
import ( import (
"emperror.dev/errors"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/apex/log" "github.com/apex/log"
"github.com/creasty/defaults"
"github.com/gammazero/workerpool" "github.com/gammazero/workerpool"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
@@ -87,25 +86,14 @@ func LoadDirectory() error {
// given struct using a YAML marshaler. This will also configure the given environment // given struct using a YAML marshaler. This will also configure the given environment
// for a server. // for a server.
func FromConfiguration(data api.ServerConfigurationResponse) (*Server, error) { func FromConfiguration(data api.ServerConfigurationResponse) (*Server, error) {
cfg := Configuration{} s, err := New()
if err := defaults.Set(&cfg); err != nil { if err != nil {
return nil, errors.WithMessage(err, "failed to set struct defaults for server configuration") return nil, errors.WithMessage(err, "loader: failed to instantiate empty server struct")
} }
s := new(Server)
if err := defaults.Set(s); err != nil {
return nil, errors.WithMessage(err, "failed to set struct defaults for server")
}
s.cfg = cfg
if err := s.UpdateDataStructure(data.Settings); err != nil { if err := s.UpdateDataStructure(data.Settings); err != nil {
return nil, err return nil, err
} }
s.resources = ResourceUsage{}
defaults.Set(&s.resources)
s.resources.State.Store(environment.ProcessOfflineState)
s.Archiver = Archiver{Server: s} s.Archiver = Archiver{Server: s}
s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.Id()), s.DiskSpace()) s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.Id()), s.DiskSpace())
@@ -128,7 +116,7 @@ func FromConfiguration(data api.ServerConfigurationResponse) (*Server, error) {
} else { } else {
s.Environment = env s.Environment = env
s.StartEventListeners() s.StartEventListeners()
s.Throttler().StartTimer() s.Throttler().StartTimer(s.Context())
} }
// Forces the configuration to be synced with the panel. // Forces the configuration to be synced with the panel.

View File

@@ -2,10 +2,9 @@ package server
import ( import (
"context" "context"
"github.com/pkg/errors" "emperror.dev/errors"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/server/filesystem"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
"os" "os"
"time" "time"
@@ -62,6 +61,14 @@ func (s *Server) ExecutingPowerAction() bool {
// function rather than making direct calls to the start/stop/restart functions on the // function rather than making direct calls to the start/stop/restart functions on the
// environment struct. // environment struct.
func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error { func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error {
if s.IsInstalling() {
return ErrServerIsInstalling
}
if s.IsTransferring() {
return ErrServerIsTransferring
}
if s.powerLock == nil { if s.powerLock == nil {
s.powerLock = semaphore.NewWeighted(1) s.powerLock = semaphore.NewWeighted(1)
} }
@@ -168,8 +175,8 @@ func (s *Server) onBeforeStart() error {
s.Filesystem().HasSpaceAvailable(true) s.Filesystem().HasSpaceAvailable(true)
} else { } else {
s.PublishConsoleOutputFromDaemon("Checking server disk space usage, this could take a few seconds...") s.PublishConsoleOutputFromDaemon("Checking server disk space usage, this could take a few seconds...")
if !s.Filesystem().HasSpaceAvailable(false) { if err := s.Filesystem().HasSpaceErr(false); err != nil {
return filesystem.ErrNotEnoughDiskSpace return err
} }
} }

View File

@@ -1,7 +1,6 @@
package server package server
import ( import (
"encoding/json"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
"sync" "sync"
@@ -18,7 +17,7 @@ type ResourceUsage struct {
environment.Stats environment.Stats
// The current server status. // The current server status.
State *system.AtomicString `json:"state" default:"{}"` State *system.AtomicString `json:"state"`
// The current disk space being used by the server. This value is not guaranteed to be accurate // The current disk space being used by the server. This value is not guaranteed to be accurate
// at all times. It is "manually" set whenever server.Proc() is called. This is kind of just a // at all times. It is "manually" set whenever server.Proc() is called. This is kind of just a
@@ -26,32 +25,27 @@ type ResourceUsage struct {
Disk int64 `json:"disk_bytes"` Disk int64 `json:"disk_bytes"`
} }
// Custom marshaler to ensure that the object is locked when we're converting it to JSON in // Returns the current resource usage stats for the server instance. This returns
// order to avoid race conditions. // a copy of the tracked resources, so making any changes to the response will not
func (ru *ResourceUsage) MarshalJSON() ([]byte, error) { // have the desired outcome for you most likely.
ru.mu.Lock() func (s *Server) Proc() ResourceUsage {
defer ru.mu.Unlock() s.resources.mu.Lock()
defer s.resources.mu.Unlock()
// Alias the resource usage so that we don't infinitely recurse when marshaling the struct.
type alias ResourceUsage
return json.Marshal(alias(*ru))
}
// Returns the resource usage stats for the server instance. If the server is not running, only the
// disk space currently used will be returned. When the server is running all of the other stats will
// be returned.
//
// When a process is stopped all of the stats are zeroed out except for the disk.
func (s *Server) Proc() *ResourceUsage {
// Store the updated disk usage when requesting process usage. // Store the updated disk usage when requesting process usage.
atomic.StoreInt64(&s.resources.Disk, s.Filesystem().CachedUsage()) atomic.StoreInt64(&s.resources.Disk, s.Filesystem().CachedUsage())
//goland:noinspection GoVetCopyLock
return s.resources
}
// Acquire a lock before attempting to return the value of resources. // Resets the usages values to zero, used when a server is stopped to ensure we don't hold
s.resources.mu.RLock() // onto any values incorrectly.
defer s.resources.mu.RUnlock() func (ru *ResourceUsage) Reset() {
ru.mu.Lock()
return &s.resources defer ru.mu.Unlock()
ru.Memory = 0
ru.CpuAbsolute = 0
ru.Network.TxBytes = 0
ru.Network.RxBytes = 0
} }
func (s *Server) emitProcUsage() { func (s *Server) emitProcUsage() {

View File

@@ -2,15 +2,17 @@ package server
import ( import (
"context" "context"
"emperror.dev/errors"
"fmt" "fmt"
"github.com/apex/log" "github.com/apex/log"
"github.com/pkg/errors" "github.com/creasty/defaults"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/environment/docker" "github.com/pterodactyl/wings/environment/docker"
"github.com/pterodactyl/wings/events" "github.com/pterodactyl/wings/events"
"github.com/pterodactyl/wings/server/filesystem" "github.com/pterodactyl/wings/server/filesystem"
"github.com/pterodactyl/wings/system"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
"strings" "strings"
"sync" "sync"
@@ -21,9 +23,12 @@ type Server struct {
// Internal mutex used to block actions that need to occur sequentially, such as // Internal mutex used to block actions that need to occur sequentially, such as
// writing the configuration to the disk. // writing the configuration to the disk.
sync.RWMutex sync.RWMutex
ctx context.Context
ctxCancel *context.CancelFunc
emitterLock sync.Mutex emitterLock sync.Mutex
powerLock *semaphore.Weighted powerLock *semaphore.Weighted
throttleLock sync.Mutex throttleOnce sync.Once
// Maintains the configuration for the server. This is the data that gets returned by the Panel // Maintains the configuration for the server. This is the data that gets returned by the Panel
// such as build settings and container images. // such as build settings and container images.
@@ -50,7 +55,8 @@ type Server struct {
// two installer processes at the same time. This also allows us to cancel a running // two installer processes at the same time. This also allows us to cancel a running
// installation process, for example when a server is deleted from the panel while the // installation process, for example when a server is deleted from the panel while the
// installer process is still running. // installer process is still running.
installer InstallerDetails installing *system.AtomicBool
transferring *system.AtomicBool
// The console throttler instance used to control outputs. // The console throttler instance used to control outputs.
throttler *ConsoleThrottler throttler *ConsoleThrottler
@@ -60,14 +66,24 @@ type Server struct {
wsBagLocker sync.Mutex wsBagLocker sync.Mutex
} }
type InstallerDetails struct { // Returns a new server instance with a context and all of the default values set on
// The cancel function for the installer. This will be a non-nil value while there // the instance.
// is an installer running for the server. func New() (*Server, error) {
cancel *context.CancelFunc ctx, cancel := context.WithCancel(context.Background())
s := Server{
// Installer lock. You should obtain an exclusive lock on this context while running ctx: ctx,
// the installation process and release it when finished. ctxCancel: &cancel,
sem *semaphore.Weighted installing: system.NewAtomicBool(false),
transferring: system.NewAtomicBool(false),
}
if err := defaults.Set(&s); err != nil {
return nil, err
}
if err := defaults.Set(&s.cfg); err != nil {
return nil, err
}
s.resources.State = system.NewAtomicString(environment.ProcessOfflineState)
return &s, nil
} }
// Returns the UUID for the server instance. // Returns the UUID for the server instance.
@@ -75,10 +91,26 @@ func (s *Server) Id() string {
return s.Config().GetUuid() return s.Config().GetUuid()
} }
// Cancels the context assigned to this server instance. Assuming background tasks
// are using this server's context for things, all of the background tasks will be
// stopped as a result.
func (s *Server) CtxCancel() {
if s.ctxCancel != nil {
(*s.ctxCancel)()
}
}
// Returns a context instance for the server. This should be used to allow background
// tasks to be canceled if the server is removed. It will only be canceled when the
// application is stopped or if the server gets deleted.
func (s *Server) Context() context.Context {
return s.ctx
}
// Returns all of the environment variables that should be assigned to a running // Returns all of the environment variables that should be assigned to a running
// server instance. // server instance.
func (s *Server) GetEnvironmentVariables() []string { func (s *Server) GetEnvironmentVariables() []string {
var out = []string{ out := []string{
fmt.Sprintf("TZ=%s", config.Get().System.Timezone), fmt.Sprintf("TZ=%s", config.Get().System.Timezone),
fmt.Sprintf("STARTUP=%s", s.Config().Invocation), fmt.Sprintf("STARTUP=%s", s.Config().Invocation),
fmt.Sprintf("SERVER_MEMORY=%d", s.MemoryLimit()), fmt.Sprintf("SERVER_MEMORY=%d", s.MemoryLimit()),

View File

@@ -62,11 +62,11 @@ func saveServerStates() error {
// Sets the state of the server internally. This function handles crash detection as // Sets the state of the server internally. This function handles crash detection as
// well as reporting to event listeners for the server. // well as reporting to event listeners for the server.
func (s *Server) OnStateChange() { func (s *Server) OnStateChange() {
prevState := s.Proc().State.Load() prevState := s.resources.State.Load()
st := s.Environment.State() st := s.Environment.State()
// Update the currently tracked state for the server. // Update the currently tracked state for the server.
s.Proc().State.Store(st) s.resources.State.Store(st)
// Emit the event to any listeners that are currently registered. // Emit the event to any listeners that are currently registered.
if prevState != s.Environment.State() { if prevState != s.Environment.State() {
@@ -91,10 +91,7 @@ func (s *Server) OnStateChange() {
// Reset the resource usage to 0 when the process fully stops so that all of the UI // Reset the resource usage to 0 when the process fully stops so that all of the UI
// views in the Panel correctly display 0. // views in the Panel correctly display 0.
if st == environment.ProcessOfflineState { if st == environment.ProcessOfflineState {
s.resources.mu.Lock() s.resources.Reset()
s.resources.Empty()
s.resources.mu.Unlock()
s.emitProcUsage() s.emitProcUsage()
} }

View File

@@ -1,10 +1,10 @@
package server package server
import ( import (
"emperror.dev/errors"
"encoding/json" "encoding/json"
"github.com/buger/jsonparser" "github.com/buger/jsonparser"
"github.com/imdario/mergo" "github.com/imdario/mergo"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
) )

View File

@@ -82,7 +82,7 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
return nil, sftp.ErrSshFxNoSuchFile return nil, sftp.ErrSshFxNoSuchFile
} }
var l = fs.logger.WithField("source", p) l := fs.logger.WithField("source", p)
// If the user doesn't have enough space left on the server it should respond with an // If the user doesn't have enough space left on the server it should respond with an
// error since we won't be letting them write this file to the disk. // error since we won't be letting them write this file to the disk.
@@ -183,7 +183,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
return sftp.ErrSshFxNoSuchFile return sftp.ErrSshFxNoSuchFile
} }
var l = fs.logger.WithField("source", p) l := fs.logger.WithField("source", p)
var target string var target string
// If a target is provided in this request validate that it is going to the correct // If a target is provided in this request validate that it is going to the correct
@@ -202,7 +202,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
return sftp.ErrSshFxPermissionDenied return sftp.ErrSshFxPermissionDenied
} }
var mode os.FileMode = 0644 mode := os.FileMode(0644)
// If the client passed a valid file permission use that, otherwise use the // If the client passed a valid file permission use that, otherwise use the
// default of 0644 set above. // default of 0644 set above.
if request.Attributes().FileMode().Perm() != 0000 { if request.Attributes().FileMode().Perm() != 0000 {

View File

@@ -1,8 +1,8 @@
package sftp package sftp
import ( import (
"emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"

View File

@@ -1,25 +1,135 @@
package system package system
import ( import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"strings"
"sync" "sync"
"sync/atomic" "time"
) )
type AtomicBool struct { var cr = []byte(" \r")
flag uint32 var crr = []byte("\r\n")
}
func (ab *AtomicBool) Set(v bool) { func ScanReader(r io.Reader, callback func(line string)) error {
i := 0 br := bufio.NewReader(r)
if v { // Avoid constantly re-allocating memory when we're flooding lines through this
i = 1 // function by using the same buffer for the duration of the call and just truncating
// the value back to 0 every loop.
var str strings.Builder
for {
str.Reset()
var err error
var line []byte
var isPrefix bool
for {
// Read the line and write it to the buffer.
line, isPrefix, err = br.ReadLine()
// Certain games like Minecraft output absolutely random carriage returns in the output seemingly
// in line with that it thinks is the terminal size. Those returns break a lot of output handling,
// so we'll just replace them with proper new-lines and then split it later and send each line as
// its own event in the response.
str.Write(bytes.Replace(line, cr, crr, -1))
// Finish this loop and begin outputting the line if there is no prefix (the line fit into
// the default buffer), or if we hit the end of the line.
if !isPrefix || err == io.EOF {
break
}
// If we encountered an error with something in ReadLine that was not an EOF just abort
// the entire process here.
if err != nil {
return err
}
}
// Publish the line for this loop. Break on new-line characters so every line is sent as a single
// output event, otherwise you get funky handling in the browser console.
for _, line := range strings.Split(str.String(), "\r\n") {
callback(line)
}
// If the error we got previously that lead to the line being output is an io.EOF we want to
// exit the entire looping process.
if err == io.EOF {
break
}
} }
return nil
atomic.StoreUint32(&ab.flag, uint32(i))
} }
func (ab *AtomicBool) Get() bool { // Runs a given work function every "d" duration until the provided context is canceled.
return atomic.LoadUint32(&ab.flag) == 1 func Every(ctx context.Context, d time.Duration, work func(t time.Time)) {
ticker := time.NewTicker(d)
go func() {
for {
select {
case <-ctx.Done():
ticker.Stop()
return
case t := <-ticker.C:
work(t)
}
}
}()
}
func FormatBytes(b int64) string {
if b < 1024 {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(1024), 0
for n := b / 1024; n >= 1024; n /= 1024 {
div *= 1024
exp++
}
return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp])
}
type AtomicBool struct {
v bool
mu sync.RWMutex
}
func NewAtomicBool(v bool) *AtomicBool {
return &AtomicBool{v: v}
}
func (ab *AtomicBool) Store(v bool) {
ab.mu.Lock()
ab.v = v
ab.mu.Unlock()
}
// Stores the value "v" if the current value stored in the AtomicBool is the opposite
// boolean value. If successfully swapped, the response is "true", otherwise "false"
// is returned.
func (ab *AtomicBool) SwapIf(v bool) bool {
ab.mu.Lock()
defer ab.mu.Unlock()
if ab.v != v {
ab.v = v
return true
}
return false
}
func (ab *AtomicBool) Load() bool {
ab.mu.RLock()
defer ab.mu.RUnlock()
return ab.v
}
func (ab *AtomicBool) UnmarshalJSON(b []byte) error {
ab.mu.Lock()
defer ab.mu.Unlock()
return json.Unmarshal(b, &ab.v)
}
func (ab *AtomicBool) MarshalJSON() ([]byte, error) {
return json.Marshal(ab.Load())
} }
// AtomicString allows for reading/writing to a given struct field without having to worry // AtomicString allows for reading/writing to a given struct field without having to worry
@@ -48,11 +158,12 @@ func (as *AtomicString) Load() string {
return as.v return as.v
} }
func (as *AtomicString) UnmarshalText(b []byte) error { func (as *AtomicString) UnmarshalJSON(b []byte) error {
as.Store(string(b)) as.mu.Lock()
return nil defer as.mu.Unlock()
return json.Unmarshal(b, &as.v)
} }
func (as *AtomicString) MarshalText() ([]byte, error) { func (as *AtomicString) MarshalJSON() ([]byte, error) {
return []byte(as.Load()), nil return json.Marshal(as.Load())
} }